| /* |
| * Copyright (C) 2007, 2014-2015 Apple Inc. All rights reserved. |
| * Copyright (C) 2013 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of Apple Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| //# sourceURL=__WebInspectorInjectedScript__ |
| |
| (function (InjectedScriptHost, inspectedGlobalObject, injectedScriptId) { |
| |
| // Protect against Object overwritten by the user code. |
| var Object = {}.constructor; |
| |
| function toString(obj) |
| { |
| return String(obj); |
| } |
| |
| function toStringDescription(obj) |
| { |
| if (obj === 0 && 1 / obj < 0) |
| return "-0"; |
| |
| return toString(obj); |
| } |
| |
| function isUInt32(obj) |
| { |
| if (typeof obj === "number") |
| return obj >>> 0 === obj && (obj > 0 || 1 / obj > 0); |
| return "" + (obj >>> 0) === obj; |
| } |
| |
| function endsWith(str, suffix) |
| { |
| var position = str.length - suffix.length; |
| if (position < 0) |
| return false; |
| return str.indexOf(suffix, position) === position; |
| } |
| |
| function isSymbol(obj) |
| { |
| return typeof obj === "symbol"; |
| } |
| |
| var InjectedScript = function() |
| { |
| this._lastBoundObjectId = 1; |
| this._idToWrappedObject = {}; |
| this._idToObjectGroupName = {}; |
| this._objectGroups = {}; |
| this._modules = {}; |
| this._nextSavedResultIndex = 1; |
| this._savedResults = []; |
| } |
| |
| InjectedScript.primitiveTypes = { |
| undefined: true, |
| boolean: true, |
| number: true, |
| string: true, |
| } |
| |
| InjectedScript.CollectionMode = { |
| OwnProperties: 1 << 0, // own properties. |
| NativeGetterProperties: 1 << 1, // native getter properties in the prototype chain. |
| AllProperties: 1 << 2, // all properties in the prototype chain. |
| } |
| |
| InjectedScript.prototype = { |
| isPrimitiveValue: function(object) |
| { |
| // FIXME(33716): typeof document.all is always 'undefined'. |
| return InjectedScript.primitiveTypes[typeof object] && !this._isHTMLAllCollection(object); |
| }, |
| |
| wrapObject: function(object, groupName, canAccessInspectedGlobalObject, generatePreview) |
| { |
| if (canAccessInspectedGlobalObject) |
| return this._wrapObject(object, groupName, false, generatePreview); |
| return this._fallbackWrapper(object); |
| }, |
| |
| setExceptionValue: function(value) |
| { |
| this._exceptionValue = value; |
| }, |
| |
| clearExceptionValue: function() |
| { |
| delete this._exceptionValue; |
| }, |
| |
| _fallbackWrapper: function(object) |
| { |
| var result = {}; |
| result.type = typeof object; |
| if (this.isPrimitiveValue(object)) |
| result.value = object; |
| else |
| result.description = toString(object); |
| return result; |
| }, |
| |
| wrapTable: function(canAccessInspectedGlobalObject, table, columns) |
| { |
| if (!canAccessInspectedGlobalObject) |
| return this._fallbackWrapper(table); |
| |
| // FIXME: Currently columns are ignored. Instead, the frontend filters all |
| // properties based on the provided column names and in the provided order. |
| // Should we filter here too? |
| |
| var columnNames = null; |
| if (typeof columns === "string") |
| columns = [columns]; |
| |
| if (InjectedScriptHost.subtype(columns) === "array") { |
| columnNames = []; |
| for (var i = 0; i < columns.length; ++i) |
| columnNames.push(toString(columns[i])); |
| } |
| |
| return this._wrapObject(table, "console", false, true, columnNames); |
| }, |
| |
| inspectObject: function(object) |
| { |
| if (this._commandLineAPIImpl) |
| this._commandLineAPIImpl.inspect(object); |
| }, |
| |
| _wrapObject: function(object, objectGroupName, forceValueType, generatePreview, columnNames) |
| { |
| try { |
| return new InjectedScript.RemoteObject(object, objectGroupName, forceValueType, generatePreview, columnNames); |
| } catch (e) { |
| try { |
| var description = injectedScript._describe(e); |
| } catch (ex) { |
| var description = "<failed to convert exception to string>"; |
| } |
| return new InjectedScript.RemoteObject(description); |
| } |
| }, |
| |
| _bind: function(object, objectGroupName) |
| { |
| var id = this._lastBoundObjectId++; |
| this._idToWrappedObject[id] = object; |
| var objectId = "{\"injectedScriptId\":" + injectedScriptId + ",\"id\":" + id + "}"; |
| if (objectGroupName) { |
| var group = this._objectGroups[objectGroupName]; |
| if (!group) { |
| group = []; |
| this._objectGroups[objectGroupName] = group; |
| } |
| group.push(id); |
| this._idToObjectGroupName[id] = objectGroupName; |
| } |
| return objectId; |
| }, |
| |
| _parseObjectId: function(objectId) |
| { |
| return InjectedScriptHost.evaluate("(" + objectId + ")"); |
| }, |
| |
| releaseObjectGroup: function(objectGroupName) |
| { |
| if (objectGroupName === "console") { |
| delete this._lastResult; |
| this._nextSavedResultIndex = 1; |
| this._savedResults = []; |
| } |
| |
| var group = this._objectGroups[objectGroupName]; |
| if (!group) |
| return; |
| |
| for (var i = 0; i < group.length; i++) |
| this._releaseObject(group[i]); |
| |
| delete this._objectGroups[objectGroupName]; |
| }, |
| |
| dispatch: function(methodName, args) |
| { |
| var argsArray = InjectedScriptHost.evaluate("(" + args + ")"); |
| var result = this[methodName].apply(this, argsArray); |
| if (typeof result === "undefined") { |
| if (inspectedGlobalObject.console) |
| inspectedGlobalObject.console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName); |
| result = null; |
| } |
| return result; |
| }, |
| |
| _getProperties: function(objectId, collectionMode, generatePreview) |
| { |
| var parsedObjectId = this._parseObjectId(objectId); |
| var object = this._objectForId(parsedObjectId); |
| var objectGroupName = this._idToObjectGroupName[parsedObjectId.id]; |
| |
| if (!this._isDefined(object)) |
| return false; |
| |
| if (isSymbol(object)) |
| return false; |
| |
| var descriptors = this._propertyDescriptors(object, collectionMode); |
| |
| // Go over properties, wrap object values. |
| for (var i = 0; i < descriptors.length; ++i) { |
| var descriptor = descriptors[i]; |
| if ("get" in descriptor) |
| descriptor.get = this._wrapObject(descriptor.get, objectGroupName); |
| if ("set" in descriptor) |
| descriptor.set = this._wrapObject(descriptor.set, objectGroupName); |
| if ("value" in descriptor) |
| descriptor.value = this._wrapObject(descriptor.value, objectGroupName, false, generatePreview); |
| if (!("configurable" in descriptor)) |
| descriptor.configurable = false; |
| if (!("enumerable" in descriptor)) |
| descriptor.enumerable = false; |
| if ("symbol" in descriptor) |
| descriptor.symbol = this._wrapObject(descriptor.symbol, objectGroupName); |
| } |
| |
| return descriptors; |
| }, |
| |
| getProperties: function(objectId, ownProperties, generatePreview) |
| { |
| var collectionMode = ownProperties ? InjectedScript.CollectionMode.OwnProperties : InjectedScript.CollectionMode.AllProperties; |
| return this._getProperties(objectId, collectionMode, generatePreview); |
| }, |
| |
| getDisplayableProperties: function(objectId, generatePreview) |
| { |
| var collectionMode = InjectedScript.CollectionMode.OwnProperties | InjectedScript.CollectionMode.NativeGetterProperties; |
| return this._getProperties(objectId, collectionMode, generatePreview); |
| }, |
| |
| getInternalProperties: function(objectId, generatePreview) |
| { |
| var parsedObjectId = this._parseObjectId(objectId); |
| var object = this._objectForId(parsedObjectId); |
| var objectGroupName = this._idToObjectGroupName[parsedObjectId.id]; |
| |
| if (!this._isDefined(object)) |
| return false; |
| |
| if (isSymbol(object)) |
| return false; |
| |
| var descriptors = this._internalPropertyDescriptors(object); |
| if (!descriptors) |
| return []; |
| |
| // Go over properties, wrap object values. |
| for (var i = 0; i < descriptors.length; ++i) { |
| var descriptor = descriptors[i]; |
| if ("value" in descriptor) |
| descriptor.value = this._wrapObject(descriptor.value, objectGroupName, false, generatePreview); |
| } |
| |
| return descriptors; |
| }, |
| |
| getCollectionEntries: function(objectId, objectGroupName, startIndex, numberToFetch) |
| { |
| var parsedObjectId = this._parseObjectId(objectId); |
| var object = this._objectForId(parsedObjectId); |
| var objectGroupName = objectGroupName || this._idToObjectGroupName[parsedObjectId.id]; |
| |
| if (!this._isDefined(object)) |
| return; |
| |
| if (typeof object !== "object") |
| return; |
| |
| var entries = this._entries(object, InjectedScriptHost.subtype(object), startIndex, numberToFetch); |
| return entries.map(function(entry) { |
| entry.value = injectedScript._wrapObject(entry.value, objectGroupName, false, true); |
| if ("key" in entry) |
| entry.key = injectedScript._wrapObject(entry.key, objectGroupName, false, true); |
| return entry; |
| }); |
| }, |
| |
| saveResult: function(callArgumentJSON) |
| { |
| this._savedResultIndex = 0; |
| |
| try { |
| var callArgument = InjectedScriptHost.evaluate("(" + callArgumentJSON + ")"); |
| var value = this._resolveCallArgument(callArgument); |
| this._saveResult(value); |
| } catch (e) {} |
| |
| return this._savedResultIndex; |
| }, |
| |
| getFunctionDetails: function(functionId) |
| { |
| var parsedFunctionId = this._parseObjectId(functionId); |
| var func = this._objectForId(parsedFunctionId); |
| if (typeof func !== "function") |
| return "Cannot resolve function by id."; |
| var details = InjectedScriptHost.functionDetails(func); |
| if (!details) |
| return "Cannot resolve function details."; |
| if ("rawScopes" in details) { |
| var objectGroupName = this._idToObjectGroupName[parsedFunctionId.id]; |
| var rawScopes = details.rawScopes; |
| var scopes = []; |
| delete details.rawScopes; |
| for (var i = 0; i < rawScopes.length; i++) |
| scopes.push(InjectedScript.CallFrameProxy._createScopeJson(rawScopes[i].type, rawScopes[i].object, objectGroupName)); |
| details.scopeChain = scopes; |
| } |
| return details; |
| }, |
| |
| releaseObject: function(objectId) |
| { |
| var parsedObjectId = this._parseObjectId(objectId); |
| this._releaseObject(parsedObjectId.id); |
| }, |
| |
| _releaseObject: function(id) |
| { |
| delete this._idToWrappedObject[id]; |
| delete this._idToObjectGroupName[id]; |
| }, |
| |
| evaluate: function(expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview, saveResult) |
| { |
| return this._evaluateAndWrap(InjectedScriptHost.evaluate, InjectedScriptHost, expression, objectGroup, false, injectCommandLineAPI, returnByValue, generatePreview, saveResult); |
| }, |
| |
| callFunctionOn: function(objectId, expression, args, returnByValue, generatePreview) |
| { |
| var parsedObjectId = this._parseObjectId(objectId); |
| var object = this._objectForId(parsedObjectId); |
| if (!this._isDefined(object)) |
| return "Could not find object with given id"; |
| |
| if (args) { |
| var resolvedArgs = []; |
| var callArgs = InjectedScriptHost.evaluate(args); |
| for (var i = 0; i < callArgs.length; ++i) { |
| try { |
| resolvedArgs[i] = this._resolveCallArgument(callArgs[i]); |
| } catch (e) { |
| return String(e); |
| } |
| } |
| } |
| |
| try { |
| var objectGroup = this._idToObjectGroupName[parsedObjectId.id]; |
| var func = InjectedScriptHost.evaluate("(" + expression + ")"); |
| if (typeof func !== "function") |
| return "Given expression does not evaluate to a function"; |
| |
| return { |
| wasThrown: false, |
| result: this._wrapObject(func.apply(object, resolvedArgs), objectGroup, returnByValue, generatePreview) |
| }; |
| } catch (e) { |
| return this._createThrownValue(e, objectGroup); |
| } |
| }, |
| |
| _resolveCallArgument: function(callArgumentJSON) |
| { |
| if ("value" in callArgumentJSON) |
| return callArgumentJSON.value; |
| |
| var objectId = callArgumentJSON.objectId; |
| if (objectId) { |
| var parsedArgId = this._parseObjectId(objectId); |
| if (!parsedArgId || parsedArgId["injectedScriptId"] !== injectedScriptId) |
| throw "Arguments should belong to the same JavaScript world as the target object."; |
| |
| var resolvedArg = this._objectForId(parsedArgId); |
| if (!this._isDefined(resolvedArg)) |
| throw "Could not find object with given id"; |
| |
| return resolvedArg; |
| } |
| |
| return undefined; |
| }, |
| |
| _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI, returnByValue, generatePreview, saveResult) |
| { |
| try { |
| this._savedResultIndex = 0; |
| |
| var returnObject = { |
| wasThrown: false, |
| result: this._wrapObject(this._evaluateOn(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI, saveResult), objectGroup, returnByValue, generatePreview) |
| }; |
| |
| if (saveResult && this._savedResultIndex) |
| returnObject.savedResultIndex = this._savedResultIndex; |
| |
| return returnObject; |
| } catch (e) { |
| return this._createThrownValue(e, objectGroup); |
| } |
| }, |
| |
| _createThrownValue: function(value, objectGroup) |
| { |
| var remoteObject = this._wrapObject(value, objectGroup); |
| try { |
| remoteObject.description = toStringDescription(value); |
| } catch (e) {} |
| return { |
| wasThrown: true, |
| result: remoteObject |
| }; |
| }, |
| |
| _evaluateOn: function(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI, saveResult) |
| { |
| var commandLineAPI = null; |
| if (injectCommandLineAPI) { |
| if (this.CommandLineAPI) |
| commandLineAPI = new this.CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null); |
| else |
| commandLineAPI = new BasicCommandLineAPI; |
| } |
| |
| if (isEvalOnCallFrame) { |
| // We can only use this approach if the evaluate function is the true 'eval'. That allows us to use it with |
| // the 'eval' identifier when calling it. Using 'eval' grants access to the local scope of the closure we |
| // create that provides the command line APIs. |
| |
| var parameters = [InjectedScriptHost.evaluate, expression]; |
| var expressionFunctionBody = "" + |
| "var global = Function('return this')() || (1, eval)('this');" + |
| "var __originalEval = global.eval; global.eval = __eval;" + |
| "try { return eval(__currentExpression); }" + |
| "finally { global.eval = __originalEval; }"; |
| |
| if (commandLineAPI) { |
| // To avoid using a 'with' statement (which fails in strict mode and requires injecting the API object) |
| // we instead create a closure where we evaluate the expression. The command line APIs are passed as |
| // parameters to the closure so they are in scope but not injected. This allows the code evaluated in |
| // the console to stay in strict mode (if is was already set), or to get strict mode by prefixing |
| // expressions with 'use strict';. |
| |
| var parameterNames = Object.getOwnPropertyNames(commandLineAPI); |
| for (var i = 0; i < parameterNames.length; ++i) |
| parameters.push(commandLineAPI[parameterNames[i]]); |
| |
| var expressionFunctionString = "(function(__eval, __currentExpression, " + parameterNames.join(", ") + ") { " + expressionFunctionBody + " })"; |
| } else { |
| // Use a closure in this case too to keep the same behavior of 'var' being captured by the closure instead |
| // of leaking out into the calling scope. |
| var expressionFunctionString = "(function(__eval, __currentExpression) { " + expressionFunctionBody + " })"; |
| } |
| |
| // Bind 'this' to the function expression using another closure instead of Function.prototype.bind. This ensures things will work if the page replaces bind. |
| var boundExpressionFunctionString = "(function(__function, __thisObject) { return function() { return __function.apply(__thisObject, arguments) }; })(" + expressionFunctionString + ", this)"; |
| var expressionFunction = evalFunction.call(object, boundExpressionFunctionString); |
| var result = expressionFunction.apply(null, parameters); |
| |
| if (saveResult) |
| this._saveResult(result); |
| |
| return result; |
| } |
| |
| // When not evaluating on a call frame we use a 'with' statement to allow var and function statements to leak |
| // into the global scope. This allow them to stick around between evaluations. |
| |
| try { |
| if (commandLineAPI) { |
| if (inspectedGlobalObject.console) |
| inspectedGlobalObject.console.__commandLineAPI = commandLineAPI; |
| else |
| inspectedGlobalObject.__commandLineAPI = commandLineAPI; |
| expression = "with ((this && (this.console ? this.console.__commandLineAPI : this.__commandLineAPI)) || {}) { " + expression + "\n}"; |
| } |
| |
| var result = evalFunction.call(inspectedGlobalObject, expression); |
| |
| if (saveResult) |
| this._saveResult(result); |
| |
| return result; |
| } finally { |
| if (commandLineAPI) { |
| if (inspectedGlobalObject.console) |
| delete inspectedGlobalObject.console.__commandLineAPI; |
| else |
| delete inspectedGlobalObject.__commandLineAPI; |
| } |
| } |
| }, |
| |
| wrapCallFrames: function(callFrame) |
| { |
| if (!callFrame) |
| return false; |
| |
| var result = []; |
| var depth = 0; |
| do { |
| result.push(new InjectedScript.CallFrameProxy(depth++, callFrame)); |
| callFrame = callFrame.caller; |
| } while (callFrame); |
| return result; |
| }, |
| |
| evaluateOnCallFrame: function(topCallFrame, callFrameId, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview, saveResult) |
| { |
| var callFrame = this._callFrameForId(topCallFrame, callFrameId); |
| if (!callFrame) |
| return "Could not find call frame with given id"; |
| return this._evaluateAndWrap(callFrame.evaluate, callFrame, expression, objectGroup, true, injectCommandLineAPI, returnByValue, generatePreview, saveResult); |
| }, |
| |
| _callFrameForId: function(topCallFrame, callFrameId) |
| { |
| var parsedCallFrameId = InjectedScriptHost.evaluate("(" + callFrameId + ")"); |
| var ordinal = parsedCallFrameId["ordinal"]; |
| var callFrame = topCallFrame; |
| while (--ordinal >= 0 && callFrame) |
| callFrame = callFrame.caller; |
| return callFrame; |
| }, |
| |
| _objectForId: function(objectId) |
| { |
| return this._idToWrappedObject[objectId.id]; |
| }, |
| |
| findObjectById: function(objectId) |
| { |
| var parsedObjectId = this._parseObjectId(objectId); |
| return this._objectForId(parsedObjectId); |
| }, |
| |
| module: function(name) |
| { |
| return this._modules[name]; |
| }, |
| |
| injectModule: function(name, source, host) |
| { |
| delete this._modules[name]; |
| |
| var moduleFunction = InjectedScriptHost.evaluate("(" + source + ")"); |
| if (typeof moduleFunction !== "function") { |
| if (inspectedGlobalObject.console) |
| inspectedGlobalObject.console.error("Web Inspector error: A function was expected for module %s evaluation", name); |
| return null; |
| } |
| |
| var module = moduleFunction.call(inspectedGlobalObject, InjectedScriptHost, inspectedGlobalObject, injectedScriptId, this, host); |
| this._modules[name] = module; |
| return module; |
| }, |
| |
| _internalPropertyDescriptors: function(object, completeDescriptor) |
| { |
| var internalProperties = InjectedScriptHost.getInternalProperties(object); |
| if (!internalProperties) |
| return null; |
| |
| var descriptors = []; |
| for (var i = 0; i < internalProperties.length; i++) { |
| var property = internalProperties[i]; |
| var descriptor = {name: property.name, value: property.value}; |
| if (completeDescriptor) { |
| descriptor.writable = false; |
| descriptor.configurable = false; |
| descriptor.enumerable = false; |
| descriptor.isOwn = true; |
| } |
| descriptors.push(descriptor); |
| } |
| return descriptors; |
| }, |
| |
| _propertyDescriptors: function(object, collectionMode) |
| { |
| var descriptors = []; |
| var nameProcessed = new Set; |
| |
| function createFakeValueDescriptor(name, symbol, descriptor, isOwnProperty, possibleNativeBindingGetter) |
| { |
| try { |
| var descriptor = {name, value: object[name], writable: descriptor.writable || false, configurable: descriptor.configurable || false, enumerable: descriptor.enumerable || false}; |
| if (possibleNativeBindingGetter) |
| descriptor.nativeGetter = true; |
| if (isOwnProperty) |
| descriptor.isOwn = true; |
| if (symbol) |
| descriptor.symbol = symbol; |
| return descriptor; |
| } catch (e) { |
| var errorDescriptor = {name, value: e, wasThrown: true}; |
| if (isOwnProperty) |
| errorDescriptor.isOwn = true; |
| if (symbol) |
| errorDescriptor.symbol = symbol; |
| return errorDescriptor; |
| } |
| } |
| |
| function processDescriptor(descriptor, isOwnProperty, possibleNativeBindingGetter) |
| { |
| // All properties. |
| if (collectionMode & InjectedScript.CollectionMode.AllProperties) { |
| descriptors.push(descriptor); |
| return; |
| } |
| |
| // Own properties. |
| if (collectionMode & InjectedScript.CollectionMode.OwnProperties && isOwnProperty) { |
| descriptors.push(descriptor); |
| return; |
| } |
| |
| // Native Getter properties. |
| if (collectionMode & InjectedScript.CollectionMode.NativeGetterProperties) { |
| // FIXME: <https://webkit.org/b/140575> Web Inspector: Native Bindings Descriptors are Incomplete |
| // if (descriptor.hasOwnProperty("get") && descriptor.get && isNativeFunction(descriptor.get)) { ... } |
| |
| if (possibleNativeBindingGetter) { |
| // Possible getter property in the prototype chain. |
| descriptors.push(descriptor); |
| return; |
| } |
| } |
| } |
| |
| function processProperties(o, properties, isOwnProperty) |
| { |
| for (var i = 0; i < properties.length; ++i) { |
| var property = properties[i]; |
| if (nameProcessed.has(property) || property === "__proto__") |
| continue; |
| |
| nameProcessed.add(property); |
| |
| var name = toString(property); |
| var symbol = isSymbol(property) ? property : null; |
| |
| var descriptor = Object.getOwnPropertyDescriptor(o, property); |
| if (!descriptor) { |
| // FIXME: Bad descriptor. Can we get here? |
| // Fall back to very restrictive settings. |
| var fakeDescriptor = createFakeValueDescriptor(name, symbol, {writable: false, configurable: false, enumerable: false}, isOwnProperty); |
| processDescriptor(fakeDescriptor, isOwnProperty); |
| continue; |
| } |
| |
| if (endsWith(String(descriptor.get), "[native code]\n}") || |
| (!descriptor.get && descriptor.hasOwnProperty("get") && !descriptor.set && descriptor.hasOwnProperty("set"))) { |
| // FIXME: Some Native Bindings Descriptors are Incomplete |
| // <https://webkit.org/b/141585> Some IDL attributes appear on the instances instead of on prototypes |
| // Developers may create such a descriptors, so we should be resilient: |
| // var x = {}; Object.defineProperty(x, "p", {get:undefined}); Object.getOwnPropertyDescriptor(x, "p") |
| var fakeDescriptor = createFakeValueDescriptor(name, symbol, descriptor, isOwnProperty, true); |
| processDescriptor(fakeDescriptor, isOwnProperty, true); |
| continue; |
| } |
| |
| descriptor.name = name; |
| if (isOwnProperty) |
| descriptor.isOwn = true; |
| if (symbol) |
| descriptor.symbol = symbol; |
| processDescriptor(descriptor, isOwnProperty); |
| } |
| } |
| |
| function arrayIndexPropertyNames(o, length) |
| { |
| var array = new Array(length); |
| for (var i = 0; i < length; ++i) { |
| if (i in o) |
| array.push("" + i); |
| } |
| return array; |
| } |
| |
| // FIXME: <https://webkit.org/b/143589> Web Inspector: Better handling for large collections in Object Trees |
| // For array types with a large length we attempt to skip getOwnPropertyNames and instead just sublist of indexes. |
| var isArrayTypeWithLargeLength = false; |
| try { |
| isArrayTypeWithLargeLength = injectedScript._subtype(object) === "array" && isFinite(object.length) && object.length > 100; |
| } catch(e) {} |
| |
| for (var o = object; this._isDefined(o); o = o.__proto__) { |
| var isOwnProperty = o === object; |
| |
| if (isArrayTypeWithLargeLength && isOwnProperty) |
| processProperties(o, arrayIndexPropertyNames(o, 100), isOwnProperty); |
| else { |
| processProperties(o, Object.getOwnPropertyNames(o), isOwnProperty); |
| if (Object.getOwnPropertySymbols) |
| processProperties(o, Object.getOwnPropertySymbols(o), isOwnProperty); |
| } |
| |
| if (collectionMode === InjectedScript.CollectionMode.OwnProperties) |
| break; |
| } |
| |
| // Always include __proto__ at the end. |
| try { |
| if (object.__proto__) |
| descriptors.push({name: "__proto__", value: object.__proto__, writable: true, configurable: true, enumerable: false, isOwn: true}); |
| } catch (e) {} |
| |
| return descriptors; |
| }, |
| |
| _isDefined: function(object) |
| { |
| return !!object || this._isHTMLAllCollection(object); |
| }, |
| |
| _isHTMLAllCollection: function(object) |
| { |
| // document.all is reported as undefined, but we still want to process it. |
| return (typeof object === "undefined") && InjectedScriptHost.isHTMLAllCollection(object); |
| }, |
| |
| _subtype: function(obj) |
| { |
| if (obj === null) |
| return "null"; |
| |
| if (this.isPrimitiveValue(obj) || isSymbol(obj)) |
| return null; |
| |
| if (this._isHTMLAllCollection(obj)) |
| return "array"; |
| |
| var preciseType = InjectedScriptHost.subtype(obj); |
| if (preciseType) |
| return preciseType; |
| |
| // FireBug's array detection. |
| try { |
| if (typeof obj.splice === "function" && isFinite(obj.length)) |
| return "array"; |
| } catch (e) { |
| } |
| |
| return null; |
| }, |
| |
| _nodeDescription: function(node) |
| { |
| var isXMLDocument = node.ownerDocument && !!node.ownerDocument.xmlVersion; |
| var description = isXMLDocument ? node.nodeName : node.nodeName.toLowerCase(); |
| |
| switch (node.nodeType) { |
| case 1: // Node.ELEMENT_NODE |
| if (node.id) |
| description += "#" + node.id; |
| if (node.hasAttribute("class")) { |
| // Using .getAttribute() is a workaround for SVG*Element.className returning SVGAnimatedString, |
| // which doesn't have any useful String methods. See <https://webkit.org/b/145363/>. |
| description += "." + node.getAttribute("class").trim().replace(/\s+/g, "."); |
| } |
| return description; |
| |
| default: |
| return description; |
| } |
| }, |
| |
| _classPreview: function(classConstructorValue) |
| { |
| return "class " + classConstructorValue.name; |
| }, |
| |
| _nodePreview: function(node) |
| { |
| var isXMLDocument = node.ownerDocument && !!node.ownerDocument.xmlVersion; |
| var nodeName = isXMLDocument ? node.nodeName : node.nodeName.toLowerCase(); |
| |
| switch (node.nodeType) { |
| case 1: // Node.ELEMENT_NODE |
| if (node.id) |
| return "<" + nodeName + " id=\"" + node.id + "\">"; |
| if (node.classList.length) |
| return "<" + nodeName + " class=\"" + node.classList.toString().replace(/\s+/, " ") + "\">"; |
| if (nodeName === "input" && node.type) |
| return "<" + nodeName + " type=\"" + node.type + "\">"; |
| return "<" + nodeName + ">"; |
| |
| case 3: // Node.TEXT_NODE |
| return nodeName + " \"" + node.nodeValue + "\""; |
| |
| case 8: // Node.COMMENT_NODE |
| return "<!--" + node.nodeValue + "-->"; |
| |
| case 10: // Node.DOCUMENT_TYPE_NODE |
| return "<!DOCTYPE " + nodeName + ">"; |
| |
| default: |
| return nodeName; |
| } |
| }, |
| |
| _describe: function(obj) |
| { |
| if (this.isPrimitiveValue(obj)) |
| return null; |
| |
| if (isSymbol(obj)) |
| return toString(obj); |
| |
| var subtype = this._subtype(obj); |
| |
| if (subtype === "regexp") |
| return toString(obj); |
| |
| if (subtype === "date") |
| return toString(obj); |
| |
| if (subtype === "error") |
| return toString(obj); |
| |
| if (subtype === "node") |
| return this._nodeDescription(obj); |
| |
| var className = InjectedScriptHost.internalConstructorName(obj); |
| if (subtype === "array") |
| return className; |
| |
| // NodeList in JSC is a function, check for array prior to this. |
| if (typeof obj === "function") |
| return toString(obj); |
| |
| // If Object, try for a better name from the constructor. |
| if (className === "Object") { |
| var constructorName = obj.constructor && obj.constructor.name; |
| if (constructorName) |
| return constructorName; |
| } |
| |
| return className; |
| }, |
| |
| _getSetEntries: function(object, skip, numberToFetch) |
| { |
| var entries = []; |
| |
| for (var value of object) { |
| if (skip > 0) { |
| skip--; |
| continue; |
| } |
| |
| entries.push({value}); |
| |
| if (numberToFetch && entries.length === numberToFetch) |
| break; |
| } |
| |
| return entries; |
| }, |
| |
| _getMapEntries: function(object, skip, numberToFetch) |
| { |
| var entries = []; |
| |
| for (var [key, value] of object) { |
| if (skip > 0) { |
| skip--; |
| continue; |
| } |
| |
| entries.push({key, value}); |
| |
| if (numberToFetch && entries.length === numberToFetch) |
| break; |
| } |
| |
| return entries; |
| }, |
| |
| _getWeakMapEntries: function(object, numberToFetch) |
| { |
| return InjectedScriptHost.weakMapEntries(object, numberToFetch); |
| }, |
| |
| _getWeakSetEntries: function(object, numberToFetch) |
| { |
| return InjectedScriptHost.weakSetEntries(object, numberToFetch); |
| }, |
| |
| _getIteratorEntries: function(object, numberToFetch) |
| { |
| return InjectedScriptHost.iteratorEntries(object, numberToFetch); |
| }, |
| |
| _entries: function(object, subtype, startIndex, numberToFetch) |
| { |
| if (subtype === "set") |
| return this._getSetEntries(object, startIndex, numberToFetch); |
| if (subtype === "map") |
| return this._getMapEntries(object, startIndex, numberToFetch); |
| if (subtype === "weakmap") |
| return this._getWeakMapEntries(object, numberToFetch); |
| if (subtype === "weakset") |
| return this._getWeakSetEntries(object, numberToFetch); |
| if (subtype === "iterator") |
| return this._getIteratorEntries(object, numberToFetch); |
| |
| throw "unexpected type"; |
| }, |
| |
| _saveResult: function(result) |
| { |
| this._lastResult = result; |
| |
| if (result === undefined || result === null) |
| return; |
| |
| var existingIndex = this._savedResults.indexOf(result); |
| if (existingIndex !== -1) { |
| this._savedResultIndex = existingIndex; |
| return; |
| } |
| |
| this._savedResultIndex = this._nextSavedResultIndex; |
| this._savedResults[this._nextSavedResultIndex++] = result; |
| |
| // $n is limited from $1-$99. $0 is special. |
| if (this._nextSavedResultIndex >= 100) |
| this._nextSavedResultIndex = 1; |
| }, |
| |
| _savedResult: function(index) |
| { |
| return this._savedResults[index]; |
| } |
| } |
| |
| var injectedScript = new InjectedScript; |
| |
| |
| InjectedScript.RemoteObject = function(object, objectGroupName, forceValueType, generatePreview, columnNames) |
| { |
| this.type = typeof object; |
| |
| if (this.type === "undefined" && injectedScript._isHTMLAllCollection(object)) |
| this.type = "object"; |
| |
| if (injectedScript.isPrimitiveValue(object) || object === null || forceValueType) { |
| // We don't send undefined values over JSON. |
| if (this.type !== "undefined") |
| this.value = object; |
| |
| // Null object is object with 'null' subtype. |
| if (object === null) |
| this.subtype = "null"; |
| |
| // Provide user-friendly number values. |
| if (this.type === "number") |
| this.description = toStringDescription(object); |
| return; |
| } |
| |
| this.objectId = injectedScript._bind(object, objectGroupName); |
| |
| var subtype = injectedScript._subtype(object); |
| if (subtype) |
| this.subtype = subtype; |
| |
| this.className = InjectedScriptHost.internalConstructorName(object); |
| this.description = injectedScript._describe(object); |
| |
| if (subtype === "array") |
| this.size = typeof object.length === "number" ? object.length : 0; |
| else if (subtype === "set" || subtype === "map") |
| this.size = object.size; |
| else if (subtype === "weakmap") |
| this.size = InjectedScriptHost.weakMapSize(object); |
| else if (subtype === "weakset") |
| this.size = InjectedScriptHost.weakSetSize(object); |
| else if (subtype === "class") { |
| this.classPrototype = injectedScript._wrapObject(object.prototype, objectGroupName); |
| this.className = object.name; |
| } |
| |
| if (generatePreview && this.type === "object") |
| this.preview = this._generatePreview(object, undefined, columnNames); |
| } |
| |
| InjectedScript.RemoteObject.prototype = { |
| _initialPreview: function() |
| { |
| var preview = { |
| type: this.type, |
| description: this.description || toString(this.value), |
| lossless: true, |
| }; |
| |
| if (this.subtype) { |
| preview.subtype = this.subtype; |
| if (this.subtype !== "null") { |
| preview.overflow = false; |
| preview.properties = []; |
| } |
| } |
| |
| if ("size" in this) |
| preview.size = this.size; |
| |
| return preview; |
| }, |
| |
| _emptyPreview: function() |
| { |
| var preview = this._initialPreview(); |
| |
| if (this.subtype === "map" || this.subtype === "set" || this.subtype === "weakmap" || this.subtype === "weakset" || this.subtype === "iterator") { |
| if (this.size) { |
| preview.entries = []; |
| preview.lossless = false; |
| preview.overflow = true; |
| } |
| } |
| |
| return preview; |
| }, |
| |
| _createObjectPreviewForValue: function(value, generatePreview) |
| { |
| var remoteObject = new InjectedScript.RemoteObject(value, undefined, false, generatePreview, undefined); |
| if (remoteObject.objectId) |
| injectedScript.releaseObject(remoteObject.objectId); |
| if (remoteObject.classPrototype && remoteObject.classPrototype.objectId) |
| injectedScript.releaseObject(remoteObject.classPrototype.objectId); |
| |
| return remoteObject.preview || remoteObject._emptyPreview(); |
| }, |
| |
| _generatePreview: function(object, firstLevelKeys, secondLevelKeys) |
| { |
| var preview = this._initialPreview(); |
| var isTableRowsRequest = secondLevelKeys === null || secondLevelKeys; |
| var firstLevelKeysCount = firstLevelKeys ? firstLevelKeys.length : 0; |
| |
| var propertiesThreshold = { |
| properties: isTableRowsRequest ? 1000 : Math.max(5, firstLevelKeysCount), |
| indexes: isTableRowsRequest ? 1000 : Math.max(10, firstLevelKeysCount) |
| }; |
| |
| try { |
| // Maps, Sets, and Iterators have entries. |
| if (this.subtype === "map" || this.subtype === "set" || this.subtype === "weakmap" || this.subtype === "weakset" || this.subtype === "iterator") |
| this._appendEntryPreviews(object, preview); |
| |
| preview.properties = []; |
| |
| // Internal Properties. |
| var internalPropertyDescriptors = injectedScript._internalPropertyDescriptors(object, true); |
| if (internalPropertyDescriptors) { |
| this._appendPropertyPreviews(object, preview, internalPropertyDescriptors, true, propertiesThreshold, firstLevelKeys, secondLevelKeys); |
| if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0) |
| return preview; |
| } |
| |
| if (preview.entries) |
| return preview; |
| |
| // Properties. |
| var descriptors = injectedScript._propertyDescriptors(object, InjectedScript.CollectionMode.AllProperties); |
| this._appendPropertyPreviews(object, preview, descriptors, false, propertiesThreshold, firstLevelKeys, secondLevelKeys); |
| if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0) |
| return preview; |
| } catch (e) { |
| preview.lossless = false; |
| } |
| |
| return preview; |
| }, |
| |
| _appendPropertyPreviews: function(object, preview, descriptors, internal, propertiesThreshold, firstLevelKeys, secondLevelKeys) |
| { |
| for (var descriptor of descriptors) { |
| // Seen enough. |
| if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0) |
| break; |
| |
| // Error in descriptor. |
| if (descriptor.wasThrown) { |
| preview.lossless = false; |
| continue; |
| } |
| |
| // Do not show "__proto__" in preview. |
| var name = descriptor.name; |
| if (name === "__proto__") |
| continue; |
| |
| // For arrays, only allow indexes. |
| if (this.subtype === "array" && !isUInt32(name)) |
| continue; |
| |
| // Do not show non-enumerable non-own properties. Special case to allow array indexes that may be on the prototype. |
| if (!descriptor.enumerable && !descriptor.isOwn && this.subtype !== "array") |
| continue; |
| |
| // If we have a filter, only show properties in the filter. |
| // FIXME: Currently these filters do nothing on the backend. |
| if (firstLevelKeys && !firstLevelKeys.includes(name)) |
| continue; |
| |
| // Getter/setter. |
| if (!("value" in descriptor)) { |
| preview.lossless = false; |
| this._appendPropertyPreview(preview, internal, {name, type: "accessor"}, propertiesThreshold); |
| continue; |
| } |
| |
| // Null value. |
| var value = descriptor.value; |
| if (value === null) { |
| this._appendPropertyPreview(preview, internal, {name, type: "object", subtype: "null", value: "null"}, propertiesThreshold); |
| continue; |
| } |
| |
| // Ignore non-enumerable functions. |
| var type = typeof value; |
| if (!descriptor.enumerable && type === "function") |
| continue; |
| |
| // Fix type of document.all. |
| if (type === "undefined" && injectedScript._isHTMLAllCollection(value)) |
| type = "object"; |
| |
| // Primitive. |
| const maxLength = 100; |
| if (InjectedScript.primitiveTypes[type]) { |
| if (type === "string" && value.length > maxLength) { |
| value = this._abbreviateString(value, maxLength, true); |
| preview.lossless = false; |
| } |
| this._appendPropertyPreview(preview, internal, {name, type, value: toStringDescription(value)}, propertiesThreshold); |
| continue; |
| } |
| |
| // Symbol. |
| if (isSymbol(value)) { |
| var symbolString = toString(value); |
| if (symbolString.length > maxLength) { |
| symbolString = this._abbreviateString(symbolString, maxLength, true); |
| preview.lossless = false; |
| } |
| this._appendPropertyPreview(preview, internal, {name, type, value: symbolString}, propertiesThreshold); |
| continue; |
| } |
| |
| // Object. |
| var property = {name, type}; |
| var subtype = injectedScript._subtype(value); |
| if (subtype) |
| property.subtype = subtype; |
| |
| // Second level. |
| if ((secondLevelKeys === null || secondLevelKeys) || this._isPreviewableObject(value, object)) { |
| // FIXME: If we want secondLevelKeys filter to continue we would need some refactoring. |
| var subPreview = this._createObjectPreviewForValue(value, value !== object); |
| property.valuePreview = subPreview; |
| if (!subPreview.lossless) |
| preview.lossless = false; |
| if (subPreview.overflow) |
| preview.overflow = true; |
| } else { |
| var description = ""; |
| if (type !== "function" || subtype === "class") { |
| var fullDescription; |
| if (subtype === "class") |
| fullDescription = "class " + value.name; |
| else if (subtype === "node") |
| fullDescription = injectedScript._nodePreview(value); |
| else |
| fullDescription = injectedScript._describe(value); |
| description = this._abbreviateString(fullDescription, maxLength, subtype === "regexp"); |
| } |
| property.value = description; |
| preview.lossless = false; |
| } |
| |
| this._appendPropertyPreview(preview, internal, property, propertiesThreshold); |
| } |
| }, |
| |
| _appendPropertyPreview: function(preview, internal, property, propertiesThreshold) |
| { |
| if (toString(property.name >>> 0) === property.name) |
| propertiesThreshold.indexes--; |
| else |
| propertiesThreshold.properties--; |
| |
| if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0) { |
| preview.overflow = true; |
| preview.lossless = false; |
| return; |
| } |
| |
| if (internal) |
| property.internal = true; |
| |
| preview.properties.push(property); |
| }, |
| |
| _appendEntryPreviews: function(object, preview) |
| { |
| // Fetch 6, but only return 5, so we can tell if we overflowed. |
| var entries = injectedScript._entries(object, this.subtype, 0, 6); |
| if (!entries) |
| return; |
| |
| if (entries.length > 5) { |
| entries.pop(); |
| preview.overflow = true; |
| preview.lossless = false; |
| } |
| |
| function updateMainPreview(subPreview) { |
| if (!subPreview.lossless) |
| preview.lossless = false; |
| } |
| |
| preview.entries = entries.map(function(entry) { |
| entry.value = this._createObjectPreviewForValue(entry.value, entry.value !== object); |
| updateMainPreview(entry.value); |
| if ("key" in entry) { |
| entry.key = this._createObjectPreviewForValue(entry.key, entry.key !== object); |
| updateMainPreview(entry.key); |
| } |
| return entry; |
| }, this); |
| }, |
| |
| _isPreviewableObject: function(value, object) |
| { |
| return this._isPreviewableObjectInternal(value, new Set([object]), 1); |
| }, |
| |
| _isPreviewableObjectInternal: function(object, knownObjects, depth) |
| { |
| // Deep object. |
| if (depth > 3) |
| return false; |
| |
| // Primitive. |
| if (injectedScript.isPrimitiveValue(object) || isSymbol(object)) |
| return true; |
| |
| // Null. |
| if (object === null) |
| return true; |
| |
| // Cyclic objects. |
| if (knownObjects.has(object)) |
| return false; |
| |
| ++depth; |
| knownObjects.add(object); |
| |
| // Arrays are simple if they have 5 or less simple objects. |
| var subtype = injectedScript._subtype(object); |
| if (subtype === "array") { |
| var length = object.length; |
| if (length > 5) |
| return false; |
| for (var i = 0; i < length; ++i) { |
| if (!this._isPreviewableObjectInternal(object[i], knownObjects, depth)) |
| return false; |
| } |
| return true; |
| } |
| |
| // Not a basic object. |
| if (object.__proto__ && object.__proto__.__proto__) |
| return false; |
| |
| // Objects are simple if they have 3 or less simple properties. |
| var ownPropertyNames = Object.getOwnPropertyNames(object); |
| if (ownPropertyNames.length > 3) |
| return false; |
| for (var propertyName of ownPropertyNames) { |
| if (!this._isPreviewableObjectInternal(object[propertyName], knownObjects, depth)) |
| return false; |
| } |
| |
| return true; |
| }, |
| |
| _abbreviateString: function(string, maxLength, middle) |
| { |
| if (string.length <= maxLength) |
| return string; |
| |
| if (middle) { |
| var leftHalf = maxLength >> 1; |
| var rightHalf = maxLength - leftHalf - 1; |
| return string.substr(0, leftHalf) + "\u2026" + string.substr(string.length - rightHalf, rightHalf); |
| } |
| |
| return string.substr(0, maxLength) + "\u2026"; |
| } |
| } |
| |
| InjectedScript.CallFrameProxy = function(ordinal, callFrame) |
| { |
| this.callFrameId = "{\"ordinal\":" + ordinal + ",\"injectedScriptId\":" + injectedScriptId + "}"; |
| this.functionName = callFrame.functionName; |
| this.location = {scriptId: String(callFrame.sourceID), lineNumber: callFrame.line, columnNumber: callFrame.column}; |
| this.scopeChain = this._wrapScopeChain(callFrame); |
| this.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace"); |
| } |
| |
| InjectedScript.CallFrameProxy.prototype = { |
| _wrapScopeChain: function(callFrame) |
| { |
| var scopeChain = callFrame.scopeChain; |
| var scopeChainProxy = []; |
| for (var i = 0; i < scopeChain.length; i++) |
| scopeChainProxy[i] = InjectedScript.CallFrameProxy._createScopeJson(callFrame.scopeType(i), scopeChain[i], "backtrace"); |
| return scopeChainProxy; |
| } |
| } |
| |
| InjectedScript.CallFrameProxy._scopeTypeNames = { |
| 0: "global", // GLOBAL_SCOPE |
| 1: "with", // WITH_SCOPE |
| 2: "closure", // CLOSURE_SCOPE |
| 3: "catch", // CATCH_SCOPE |
| 4: "functionName", // FUNCTION_NAME_SCOPE |
| 5: "globalLexicalEnvironment", // GLOBAL_LEXICAL_ENVIRONMENT_SCOPE |
| 6: "nestedLexical", // NESTED_LEXICAL_SCOPE |
| } |
| |
| InjectedScript.CallFrameProxy._createScopeJson = function(scopeTypeCode, scopeObject, groupId) |
| { |
| return { |
| object: injectedScript._wrapObject(scopeObject, groupId), |
| type: InjectedScript.CallFrameProxy._scopeTypeNames[scopeTypeCode] |
| }; |
| } |
| |
| |
| function slice(array, index) |
| { |
| var result = []; |
| for (var i = index || 0; i < array.length; ++i) |
| result.push(array[i]); |
| return result; |
| } |
| |
| function bind(func, thisObject, var_args) |
| { |
| var args = slice(arguments, 2); |
| return function(var_args) { |
| return func.apply(thisObject, args.concat(slice(arguments))); |
| } |
| } |
| |
| function BasicCommandLineAPI() |
| { |
| this.$_ = injectedScript._lastResult; |
| this.$exception = injectedScript._exceptionValue; |
| |
| // $1-$99 |
| for (var i = 1; i <= injectedScript._savedResults.length; ++i) { |
| var member = "$" + i; |
| if (member in inspectedGlobalObject) |
| continue; |
| this.__defineGetter__("$" + i, bind(injectedScript._savedResult, injectedScript, i)); |
| } |
| } |
| |
| return injectedScript; |
| }) |