diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/JavaScriptCore/inspector/InjectedScriptSource.js | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/JavaScriptCore/inspector/InjectedScriptSource.js')
-rw-r--r-- | Source/JavaScriptCore/inspector/InjectedScriptSource.js | 1475 |
1 files changed, 919 insertions, 556 deletions
diff --git a/Source/JavaScriptCore/inspector/InjectedScriptSource.js b/Source/JavaScriptCore/inspector/InjectedScriptSource.js index 7bff7f6d4..1ef43ebcc 100644 --- a/Source/JavaScriptCore/inspector/InjectedScriptSource.js +++ b/Source/JavaScriptCore/inspector/InjectedScriptSource.js @@ -1,5 +1,6 @@ /* - * Copyright (C) 2007 Apple Inc. All rights reserved. + * 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 @@ -10,7 +11,7 @@ * 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 Computer, Inc. ("Apple") nor the names of + * 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. * @@ -26,21 +27,46 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -//# sourceURL=__WebInspectorInjectedScript__ +//# sourceURL=__InjectedScript_InjectedScriptSource.js -/** - * @param {InjectedScriptHost} InjectedScriptHost - * @param {GlobalObject} inspectedGlobalObject - * @param {number} injectedScriptId - */ (function (InjectedScriptHost, inspectedGlobalObject, injectedScriptId) { -// Protect against Object overwritten by the user code. +// FIXME: <https://webkit.org/b/152294> Web Inspector: Parse InjectedScriptSource as a built-in to get guaranteed non-user-overriden built-ins + var Object = {}.constructor; -/** - * @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 isSymbol(obj) +{ + return typeof obj === "symbol"; +} + +function isEmptyObject(object) +{ + for (let key in object) + return false; + return true; +} + var InjectedScript = function() { this._lastBoundObjectId = 1; @@ -48,37 +74,60 @@ var InjectedScript = function() this._idToObjectGroupName = {}; this._objectGroups = {}; this._modules = {}; + this._nextSavedResultIndex = 1; + this._savedResults = []; } -/** - * @type {Object.<string, boolean>} - * @const - */ InjectedScript.primitiveTypes = { undefined: true, boolean: true, number: true, - string: 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 = { - /** - * @param {*} object - * @return {boolean} - */ isPrimitiveValue: function(object) { // FIXME(33716): typeof document.all is always 'undefined'. return InjectedScript.primitiveTypes[typeof object] && !this._isHTMLAllCollection(object); }, - /** - * @param {*} object - * @param {string} groupName - * @param {boolean} canAccessInspectedGlobalObject - * @param {boolean} generatePreview - * @return {!RuntimeAgent.RemoteObject} - */ + previewValue: function(value) + { + return InjectedScript.RemoteObject.createObjectPreviewForValue(value, true); + }, + + functionDetails: function(func, previewOnly) + { + var details = InjectedScriptHost.functionDetails(func); + if (!details) + return "Cannot resolve function details."; + + // FIXME: provide function scope data in "scopesRaw" property when JSC supports it. + // <https://webkit.org/b/87192> [JSC] expose function (closure) inner context to debugger + if ("rawScopes" in details) { + if (previewOnly) + delete details.rawScopes; + else { + 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; + }, + wrapObject: function(object, groupName, canAccessInspectedGlobalObject, generatePreview) { if (canAccessInspectedGlobalObject) @@ -86,10 +135,16 @@ InjectedScript.prototype = { return this._fallbackWrapper(object); }, - /** - * @param {*} object - * @return {!RuntimeAgent.RemoteObject} - */ + setExceptionValue: function(value) + { + this._exceptionValue = value; + }, + + clearExceptionValue: function() + { + delete this._exceptionValue; + }, + _fallbackWrapper: function(object) { var result = {}; @@ -97,50 +152,38 @@ InjectedScript.prototype = { if (this.isPrimitiveValue(object)) result.value = object; else - result.description = this._toString(object); - return /** @type {!RuntimeAgent.RemoteObject} */ (result); + result.description = toString(object); + return result; }, - /** - * @param {boolean} canAccessInspectedGlobalObject - * @param {Object} table - * @param {Array.<string>|string|boolean} columns - * @return {!RuntimeAgent.RemoteObject} - */ 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. + // We could filter here to avoid sending very large preview objects. + var columnNames = null; if (typeof columns === "string") columns = [columns]; - if (InjectedScriptHost.type(columns) == "array") { + + if (InjectedScriptHost.subtype(columns) === "array") { columnNames = []; for (var i = 0; i < columns.length; ++i) - columnNames.push(String(columns[i])); + columnNames.push(toString(columns[i])); } + return this._wrapObject(table, "console", false, true, columnNames); }, - /** - * @param {*} object - */ inspectObject: function(object) { if (this._commandLineAPIImpl) this._commandLineAPIImpl.inspect(object); }, - /** - * This method cannot throw. - * @param {*} object - * @param {string=} objectGroupName - * @param {boolean=} forceValueType - * @param {boolean=} generatePreview - * @param {?Array.<string>=} columnNames - * @return {!RuntimeAgent.RemoteObject} - * @suppress {checkTypes} - */ _wrapObject: function(object, objectGroupName, forceValueType, generatePreview, columnNames) { try { @@ -155,11 +198,6 @@ InjectedScript.prototype = { } }, - /** - * @param {*} object - * @param {string=} objectGroupName - * @return {string} - */ _bind: function(object, objectGroupName) { var id = this._lastBoundObjectId++; @@ -177,39 +215,34 @@ InjectedScript.prototype = { return objectId; }, - /** - * @param {string} objectId - * @return {Object} - */ _parseObjectId: function(objectId) { return InjectedScriptHost.evaluate("(" + objectId + ")"); }, - /** - * @param {string} objectGroupName - */ 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]; }, - /** - * @param {string} methodName - * @param {string} args - * @return {*} - */ dispatch: function(methodName, args) { var argsArray = InjectedScriptHost.evaluate("(" + args + ")"); var result = this[methodName].apply(this, argsArray); if (typeof result === "undefined") { - // FIXME: JS Context inspection currently does not have a global.console object. if (inspectedGlobalObject.console) inspectedGlobalObject.console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName); result = null; @@ -217,12 +250,7 @@ InjectedScript.prototype = { return result; }, - /** - * @param {string} objectId - * @param {boolean} ownProperties - * @return {Array.<RuntimeAgent.PropertyDescriptor>|boolean} - */ - getProperties: function(objectId, ownProperties) + _getProperties: function(objectId, collectionMode, generatePreview, nativeGettersAsValues) { var parsedObjectId = this._parseObjectId(objectId); var object = this._objectForId(parsedObjectId); @@ -230,7 +258,11 @@ InjectedScript.prototype = { if (!this._isDefined(object)) return false; - var descriptors = this._propertyDescriptors(object, ownProperties); + + if (isSymbol(object)) + return false; + + var descriptors = this._propertyDescriptors(object, collectionMode, nativeGettersAsValues); // Go over properties, wrap object values. for (var i = 0; i < descriptors.length; ++i) { @@ -240,156 +272,119 @@ InjectedScript.prototype = { if ("set" in descriptor) descriptor.set = this._wrapObject(descriptor.set, objectGroupName); if ("value" in descriptor) - descriptor.value = this._wrapObject(descriptor.value, objectGroupName); + 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; }, - /** - * @param {string} objectId - * @return {Array.<Object>|boolean} - */ - getInternalProperties: function(objectId, ownProperties) + getProperties: function(objectId, ownProperties, generatePreview) + { + var nativeGettersAsValues = false; + var collectionMode = ownProperties ? InjectedScript.CollectionMode.OwnProperties : InjectedScript.CollectionMode.AllProperties; + return this._getProperties(objectId, collectionMode, generatePreview, nativeGettersAsValues); + }, + + getDisplayableProperties: function(objectId, generatePreview) + { + var nativeGettersAsValues = true; + var collectionMode = InjectedScript.CollectionMode.OwnProperties | InjectedScript.CollectionMode.NativeGetterProperties; + return this._getProperties(objectId, collectionMode, generatePreview, nativeGettersAsValues); + }, + + 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; - var descriptors = []; - var internalProperties = InjectedScriptHost.getInternalProperties(object); - if (internalProperties) { - for (var i = 0; i < internalProperties.length; i++) { - var property = internalProperties[i]; - var descriptor = { - name: property.name, - value: this._wrapObject(property.value, objectGroupName) - }; - descriptors.push(descriptor); - } + + 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; }, - /** - * @param {string} functionId - * @return {!DebuggerAgent.FunctionDetails|string} - */ + 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; + return injectedScript.functionDetails(func); }, - /** - * @param {string} objectId - */ releaseObject: function(objectId) { var parsedObjectId = this._parseObjectId(objectId); this._releaseObject(parsedObjectId.id); }, - /** - * @param {string} id - */ _releaseObject: function(id) { delete this._idToWrappedObject[id]; delete this._idToObjectGroupName[id]; }, - /** - * @param {Object} object - * @param {boolean} ownProperties - * @return {Array.<Object>} - */ - _propertyDescriptors: function(object, ownProperties) + evaluate: function(expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview, saveResult) { - var descriptors = []; - var nameProcessed = {}; - nameProcessed["__proto__"] = null; - for (var o = object; this._isDefined(o); o = o.__proto__) { - var names = Object.getOwnPropertyNames(/** @type {!Object} */ (o)); - for (var i = 0; i < names.length; ++i) { - var name = names[i]; - if (nameProcessed[name]) - continue; - - try { - nameProcessed[name] = true; - var descriptor = Object.getOwnPropertyDescriptor(/** @type {!Object} */ (object), name); - if (!descriptor) { - // Not all bindings provide proper descriptors. Fall back to the writable, configurable property. - try { - descriptor = { name: name, value: object[name], writable: false, configurable: false, enumerable: false}; - if (o === object) - descriptor.isOwn = true; - descriptors.push(descriptor); - } catch (e) { - // Silent catch. - } - continue; - } - } catch (e) { - var descriptor = {}; - descriptor.value = e; - descriptor.wasThrown = true; - } - - descriptor.name = name; - if (o === object) - descriptor.isOwn = true; - descriptors.push(descriptor); - } - if (ownProperties) { - if (object.__proto__) - descriptors.push({ name: "__proto__", value: object.__proto__, writable: true, configurable: true, enumerable: false, isOwn: true}); - break; - } - } - return descriptors; - }, - - /** - * @param {string} expression - * @param {string} objectGroup - * @param {boolean} injectCommandLineAPI - * @param {boolean} returnByValue - * @param {boolean} generatePreview - * @return {*} - */ - evaluate: function(expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview) - { - return this._evaluateAndWrap(InjectedScriptHost.evaluate, InjectedScriptHost, expression, objectGroup, false, injectCommandLineAPI, returnByValue, generatePreview); + return this._evaluateAndWrap(InjectedScriptHost.evaluateWithScopeExtension, InjectedScriptHost, expression, objectGroup, false, injectCommandLineAPI, returnByValue, generatePreview, saveResult); }, - /** - * @param {string} objectId - * @param {string} expression - * @param {boolean} returnByValue - * @return {Object|string} - */ - callFunctionOn: function(objectId, expression, args, returnByValue) + callFunctionOn: function(objectId, expression, args, returnByValue, generatePreview) { var parsedObjectId = this._parseObjectId(objectId); var object = this._objectForId(parsedObjectId); @@ -398,15 +393,13 @@ InjectedScript.prototype = { if (args) { var resolvedArgs = []; - args = InjectedScriptHost.evaluate(args); - for (var i = 0; i < args.length; ++i) { - var resolvedCallArgument; + var callArgs = InjectedScriptHost.evaluate(args); + for (var i = 0; i < callArgs.length; ++i) { try { - resolvedCallArgument = this._resolveCallArgument(args[i]); + resolvedArgs[i] = this._resolveCallArgument(callArgs[i]); } catch (e) { return String(e); } - resolvedArgs.push(resolvedCallArgument) } } @@ -416,21 +409,21 @@ InjectedScript.prototype = { 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) }; + return { + wasThrown: false, + result: this._wrapObject(func.apply(object, resolvedArgs), objectGroup, returnByValue, generatePreview) + }; } catch (e) { return this._createThrownValue(e, objectGroup); } }, - /** - * Resolves a value from CallArgument description. - * @param {RuntimeAgent.CallArgument} callArgumentJson - * @return {*} resolved value - * @throws {string} error message - */ - _resolveCallArgument: function(callArgumentJson) { - var objectId = callArgumentJson.objectId; + _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) @@ -441,140 +434,58 @@ InjectedScript.prototype = { throw "Could not find object with given id"; return resolvedArg; - } else if ("value" in callArgumentJson) - return callArgumentJson.value; - else - return undefined; + } + + return undefined; }, - /** - * @param {Function} evalFunction - * @param {Object} object - * @param {string} objectGroup - * @param {boolean} isEvalOnCallFrame - * @param {boolean} injectCommandLineAPI - * @param {boolean} returnByValue - * @param {boolean} generatePreview - * @return {*} - */ - _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI, returnByValue, generatePreview) + _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI, returnByValue, generatePreview, saveResult) { try { - return { wasThrown: false, - result: this._wrapObject(this._evaluateOn(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI), objectGroup, returnByValue, generatePreview) }; + 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); } }, - /** - * @param {*} value - * @param {string} objectGroup - * @return {Object} - */ _createThrownValue: function(value, objectGroup) { var remoteObject = this._wrapObject(value, objectGroup); try { - remoteObject.description = this._toString(value); + remoteObject.description = toStringDescription(value); } catch (e) {} - return { wasThrown: true, - result: remoteObject }; + return { + wasThrown: true, + result: remoteObject + }; }, - /** - * @param {Function} evalFunction - * @param {Object} object - * @param {string} objectGroup - * @param {string} expression - * @param {boolean} isEvalOnCallFrame - * @param {boolean} injectCommandLineAPI - * @return {*} - */ - _evaluateOn: function(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI) + _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; + commandLineAPI = new BasicCommandLineAPI(isEvalOnCallFrame ? object : null); } - 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 (objectGroup === "console") - this._lastResult = 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. - - // FIXME: JS Context inspection currently does not have a global.console object. - 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 (objectGroup === "console") - this._lastResult = result; - - return result; - } finally { - if (commandLineAPI) { - if (inspectedGlobalObject.console) - delete inspectedGlobalObject.console.__commandLineAPI; - else - delete inspectedGlobalObject.__commandLineAPI; - } - } + var result = evalFunction.call(object, expression, commandLineAPI); + if (saveResult) + this._saveResult(result); + return result; }, - /** - * @param {Object} callFrame - * @return {Array.<InjectedScript.CallFrameProxy>|boolean} - */ wrapCallFrames: function(callFrame) { if (!callFrame) @@ -589,29 +500,14 @@ InjectedScript.prototype = { return result; }, - /** - * @param {Object} topCallFrame - * @param {string} callFrameId - * @param {string} expression - * @param {string} objectGroup - * @param {boolean} injectCommandLineAPI - * @param {boolean} returnByValue - * @param {boolean} generatePreview - * @return {*} - */ - evaluateOnCallFrame: function(topCallFrame, callFrameId, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview) + 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); + return this._evaluateAndWrap(callFrame.evaluateWithScopeExtension, callFrame, expression, objectGroup, true, injectCommandLineAPI, returnByValue, generatePreview, saveResult); }, - /** - * @param {Object} topCallFrame - * @param {string} callFrameId - * @return {Object} - */ _callFrameForId: function(topCallFrame, callFrameId) { var parsedCallFrameId = InjectedScriptHost.evaluate("(" + callFrameId + ")"); @@ -622,89 +518,215 @@ InjectedScript.prototype = { return callFrame; }, - /** - * @param {Object} objectId - * @return {Object} - */ _objectForId: function(objectId) { return this._idToWrappedObject[objectId.id]; }, - /** - * @param {string} objectId - * @return {Object} - */ findObjectById: function(objectId) { var parsedObjectId = this._parseObjectId(objectId); return this._objectForId(parsedObjectId); }, - /** - * @param {string} name - * @return {Object} - */ module: function(name) { return this._modules[name]; }, - /** - * @param {string} name - * @param {string} source - * @return {Object} - */ injectModule: function(name, source, host) { delete this._modules[name]; + var moduleFunction = InjectedScriptHost.evaluate("(" + source + ")"); if (typeof moduleFunction !== "function") { - // FIXME: JS Context inspection currently does not have a global.console object. 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; }, - /** - * @param {*} object - * @return {boolean} - */ + _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, nativeGettersAsValues) + { + if (InjectedScriptHost.subtype(object) === "proxy") + return []; + + 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) { + if (possibleNativeBindingGetter) { + 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 (nativeGettersAsValues) { + if (String(descriptor.get).endsWith("[native code]\n}") || (!descriptor.get && descriptor.hasOwnProperty("get") && !descriptor.set && descriptor.hasOwnProperty("set"))) { + // Developers may create such a descriptor, 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 = []; + 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 isArrayLike = false; + try { + isArrayLike = injectedScript._subtype(object) === "array" && isFinite(object.length) && object.length > 0; + } catch(e) {} + + for (var o = object; this._isDefined(o); o = Object.getPrototypeOf(o)) { + var isOwnProperty = o === object; + + if (isArrayLike && isOwnProperty) + processProperties(o, arrayIndexPropertyNames(o, Math.min(object.length, 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); }, - /** - * @param {*} object - * @return {boolean} - */ _isHTMLAllCollection: function(object) { // document.all is reported as undefined, but we still want to process it. return (typeof object === "undefined") && InjectedScriptHost.isHTMLAllCollection(object); }, - /** - * @param {Object=} obj - * @return {string?} - */ _subtype: function(obj) { if (obj === null) return "null"; - if (this.isPrimitiveValue(obj)) + if (this.isPrimitiveValue(obj) || isSymbol(obj)) return null; if (this._isHTMLAllCollection(obj)) return "array"; - var preciseType = InjectedScriptHost.type(obj); + var preciseType = InjectedScriptHost.subtype(obj); if (preciseType) return preciseType; @@ -712,322 +734,663 @@ InjectedScript.prototype = { try { if (typeof obj.splice === "function" && isFinite(obj.length)) return "array"; - if (Object.prototype.toString.call(obj) === "[object Arguments]" && isFinite(obj.length)) // arguments. - return "array"; - } catch (e) { - } + } catch (e) {} - // If owning frame has navigated to somewhere else window properties will be undefined. return null; }, - /** - * @param {*} obj - * @return {string?} - */ + _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; - obj = /** @type {Object} */ (obj); + if (isSymbol(obj)) + return toString(obj); - // Type is object, get subtype. var subtype = this._subtype(obj); if (subtype === "regexp") - return this._toString(obj); + return toString(obj); if (subtype === "date") - return this._toString(obj); - - if (subtype === "node") { - var description = obj.nodeName.toLowerCase(); - switch (obj.nodeType) { - case 1 /* Node.ELEMENT_NODE */: - description += obj.id ? "#" + obj.id : ""; - var className = obj.className; - description += className ? "." + className : ""; - break; - case 10 /*Node.DOCUMENT_TYPE_NODE */: - description = "<!DOCTYPE " + description + ">"; - break; - } - return description; - } + return toString(obj); + + if (subtype === "error") + return toString(obj); + + if (subtype === "proxy") + return "Proxy"; + + if (subtype === "node") + return this._nodePreview(obj); var className = InjectedScriptHost.internalConstructorName(obj); - if (subtype === "array") { - if (typeof obj.length === "number") - className += "[" + obj.length + "]"; + if (subtype === "array") return className; - } + + if (subtype === "iterator" && Symbol.toStringTag in obj) + return obj[Symbol.toStringTag]; // NodeList in JSC is a function, check for array prior to this. if (typeof obj === "function") - return this._toString(obj); + return obj.toString(); + // If Object, try for a better name from the constructor. if (className === "Object") { - // In Chromium DOM wrapper prototypes will have Object as their constructor name, - // get the real DOM wrapper name from the constructor property. var constructorName = obj.constructor && obj.constructor.name; if (constructorName) return constructorName; } + return className; }, - /** - * @param {*} obj - * @return {string} - */ - _toString: function(obj) + _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) { - // We don't use String(obj) because inspectedGlobalObject.String is undefined if owning frame navigated to another page. - return "" + obj; + 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]; } } -/** - * @type {InjectedScript} - * @const - */ -var injectedScript = new InjectedScript(); - -/** - * @constructor - * @param {*} object - * @param {string=} objectGroupName - * @param {boolean=} forceValueType - * @param {boolean=} generatePreview - * @param {?Array.<string>=} columnNames - */ +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 (typeof object !== "undefined") + if (this.type !== "undefined") this.value = object; - // Null object is object with 'null' subtype' + // Null object is object with 'null' subtype. if (object === null) this.subtype = "null"; // Provide user-friendly number values. - if (typeof object === "number") - this.description = object + ""; + if (this.type === "number") + this.description = toStringDescription(object); return; } - object = /** @type {Object} */ (object); - 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 (generatePreview && (this.type === "object" || injectedScript._isHTMLAllCollection(object))) - this.preview = this._generatePreview(object, undefined, columnNames); -} + 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") { + if (subtype === "proxy") { + this.preview = this._generatePreview(InjectedScriptHost.proxyTargetValue(object)); + this.preview.lossless = false; + } else + this.preview = this._generatePreview(object, undefined, columnNames); + } +}; + +InjectedScript.RemoteObject.createObjectPreviewForValue = function(value, generatePreview, columnNames) +{ + var remoteObject = new InjectedScript.RemoteObject(value, undefined, false, generatePreview, columnNames); + if (remoteObject.objectId) + injectedScript.releaseObject(remoteObject.objectId); + if (remoteObject.classPrototype && remoteObject.classPrototype.objectId) + injectedScript.releaseObject(remoteObject.classPrototype.objectId); + return remoteObject.preview || remoteObject._emptyPreview(); +}; InjectedScript.RemoteObject.prototype = { - /** - * @param {Object} object - * @param {Array.<string>=} firstLevelKeys - * @param {?Array.<string>=} secondLevelKeys - * @return {Object} preview - */ - _generatePreview: function(object, firstLevelKeys, secondLevelKeys) + _initialPreview: function() { - var preview = {}; - preview.lossless = true; - preview.overflow = false; - preview.properties = []; + 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; + }, + + _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(100, firstLevelKeysCount) + indexes: isTableRowsRequest ? 1000 : Math.max(10, firstLevelKeysCount) }; - for (var o = object; injectedScript._isDefined(o); o = o.__proto__) - this._generateProtoPreview(o, preview, propertiesThreshold, firstLevelKeys, secondLevelKeys); + + 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 nativeGettersAsValues = true; + var descriptors = injectedScript._propertyDescriptors(object, InjectedScript.CollectionMode.AllProperties, nativeGettersAsValues); + 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; }, - /** - * @param {Object} object - * @param {Object} preview - * @param {Object} propertiesThreshold - * @param {Array.<string>=} firstLevelKeys - * @param {Array.<string>=} secondLevelKeys - */ - _generateProtoPreview: function(object, preview, propertiesThreshold, firstLevelKeys, secondLevelKeys) + _appendPropertyPreviews: function(object, preview, descriptors, internal, propertiesThreshold, firstLevelKeys, secondLevelKeys) { - var propertyNames = firstLevelKeys ? firstLevelKeys : Object.keys(/** @type {!Object} */(object)); - try { - for (var i = 0; i < propertyNames.length; ++i) { - if (!propertiesThreshold.properties || !propertiesThreshold.indexes) { - preview.overflow = true; - preview.lossless = false; - break; - } - var name = propertyNames[i]; - if (this.subtype === "array" && name === "length") - continue; + 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; + } - var descriptor = Object.getOwnPropertyDescriptor(/** @type {!Object} */(object), name); - if (!("value" in descriptor) || !descriptor.enumerable) { + // Do not show "__proto__" in preview. + var name = descriptor.name; + if (name === "__proto__") { + // Non basic __proto__ objects may have interesting, non-enumerable, methods to show. + if (descriptor.value && descriptor.value.constructor + && descriptor.value.constructor !== Object + && descriptor.value.constructor !== Array + && descriptor.value.constructor !== RegExp) preview.lossless = false; - continue; - } + continue; + } - var value = descriptor.value; - if (value === null) { - this._appendPropertyPreview(preview, { name: name, type: "object", value: "null" }, propertiesThreshold); - continue; - } + // For arrays, only allow indexes. + if (this.subtype === "array" && !isUInt32(name)) + continue; - const maxLength = 100; - var type = typeof value; + // Do not show non-enumerable non-own properties. + // Special case to allow array indexes that may be on the prototype. + // Special case to allow native getters on non-RegExp objects. + if (!descriptor.enumerable && !descriptor.isOwn && !(this.subtype === "array" || (this.subtype !== "regexp" && descriptor.nativeGetter))) + continue; - if (InjectedScript.primitiveTypes[type]) { - if (type === "string") { - if (value.length > maxLength) { - value = this._abbreviateString(value, maxLength, true); - preview.lossless = false; - } - value = value.replace(/\n/g, "\u21B5"); - } - this._appendPropertyPreview(preview, { name: name, type: type, value: value + "" }, propertiesThreshold); - 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; - if (secondLevelKeys === null || secondLevelKeys) { - var subPreview = this._generatePreview(value, secondLevelKeys || undefined); - var property = { name: name, type: type, valuePreview: subPreview }; - this._appendPropertyPreview(preview, property, propertiesThreshold); - if (!subPreview.lossless) - preview.lossless = false; - if (subPreview.overflow) - preview.overflow = true; - 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; + } - preview.lossless = false; + // 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; + } - var subtype = injectedScript._subtype(value); + // 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 = InjectedScript.RemoteObject.createObjectPreviewForValue(value, value !== object, secondLevelKeys); + property.valuePreview = subPreview; + if (!subPreview.lossless) + preview.lossless = false; + if (subPreview.overflow) + preview.overflow = true; + } else { var description = ""; - if (type !== "function") - description = this._abbreviateString(/** @type {string} */ (injectedScript._describe(value)), maxLength, subtype === "regexp"); - - var property = { name: name, type: type, value: description }; - if (subtype) - property.subtype = subtype; - this._appendPropertyPreview(preview, property, propertiesThreshold); + 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; } - } catch (e) { + + this._appendPropertyPreview(preview, internal, property, propertiesThreshold); } }, - /** - * @param {Object} preview - * @param {Object} property - * @param {Object} propertiesThreshold - */ - _appendPropertyPreview: function(preview, property, propertiesThreshold) + _appendPropertyPreview: function(preview, internal, property, propertiesThreshold) { - if (isNaN(property.name)) - propertiesThreshold.properties--; - else + 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); }, - /** - * @param {string} string - * @param {number} maxLength - * @param {boolean=} middle - * @returns - */ + _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 = InjectedScript.RemoteObject.createObjectPreviewForValue(entry.value, entry.value !== object); + updateMainPreview(entry.value); + if ("key" in entry) { + entry.key = InjectedScript.RemoteObject.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"; } } -/** - * @constructor - * @param {number} ordinal - * @param {Object} callFrame - */ + InjectedScript.CallFrameProxy = function(ordinal, callFrame) { this.callFrameId = "{\"ordinal\":" + ordinal + ",\"injectedScriptId\":" + injectedScriptId + "}"; - this.functionName = (callFrame.type === "function" ? callFrame.functionName : ""); - this.location = { scriptId: String(callFrame.sourceID), lineNumber: callFrame.line, columnNumber: callFrame.column }; + 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"); + this.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace", false, true); + this.isTailDeleted = callFrame.isTailDeleted; } InjectedScript.CallFrameProxy.prototype = { - /** - * @param {Object} callFrame - * @return {!Array.<DebuggerAgent.Scope>} - */ _wrapScopeChain: function(callFrame) { var scopeChain = callFrame.scopeChain; + var scopeDescriptions = callFrame.scopeDescriptions(); + var scopeChainProxy = []; - for (var i = 0; i < scopeChain.length; i++) { - var scope = InjectedScript.CallFrameProxy._createScopeJson(callFrame.scopeType(i), scopeChain[i], "backtrace"); - scopeChainProxy.push(scope); - } + for (var i = 0; i < scopeChain.length; i++) + scopeChainProxy[i] = InjectedScript.CallFrameProxy._createScopeJson(scopeChain[i], scopeDescriptions[i], "backtrace"); return scopeChainProxy; } } -/** - * @param {number} scopeTypeCode - * @param {*} scopeObject - * @param {string} groupId - * @return {!DebuggerAgent.Scope} - */ -InjectedScript.CallFrameProxy._createScopeJson = function(scopeTypeCode, scopeObject, groupId) { - const GLOBAL_SCOPE = 0; - const LOCAL_SCOPE = 1; - const WITH_SCOPE = 2; - const CLOSURE_SCOPE = 3; - const CATCH_SCOPE = 4; - - /** @type {!Object.<number, string>} */ - var scopeTypeNames = {}; - scopeTypeNames[GLOBAL_SCOPE] = "global"; - scopeTypeNames[LOCAL_SCOPE] = "local"; - scopeTypeNames[WITH_SCOPE] = "with"; - scopeTypeNames[CLOSURE_SCOPE] = "closure"; - scopeTypeNames[CATCH_SCOPE] = "catch"; - - return { - object: injectedScript._wrapObject(scopeObject, groupId), - type: /** @type {DebuggerAgent.ScopeType} */ (scopeTypeNames[scopeTypeCode]) +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(object, {name, type, location}, groupId) +{ + let scope = { + object: injectedScript._wrapObject(object, groupId), + type: InjectedScript.CallFrameProxy._scopeTypeNames[type], + }; + + if (name) + scope.name = name; + + if (location) + scope.location = location; + + if (isEmptyObject(object)) + scope.empty = true; + + return scope; +} + + +function bind(func, thisObject, ...outerArgs) +{ + return function(...innerArgs) { + return func.apply(thisObject, outerArgs.concat(innerArgs)); }; } -function BasicCommandLineAPI() +function BasicCommandLineAPI(callFrame) { this.$_ = injectedScript._lastResult; + this.$exception = injectedScript._exceptionValue; + + // $1-$99 + for (let i = 1; i <= injectedScript._savedResults.length; ++i) + this.__defineGetter__("$" + i, bind(injectedScript._savedResult, injectedScript, i)); + + // Command Line API methods. + for (let method of BasicCommandLineAPI.methods) + this[method.name] = method; } +BasicCommandLineAPI.methods = [ + function dir() { return inspectedGlobalObject.console.dir(...arguments); }, + function clear() { return inspectedGlobalObject.console.clear(...arguments); }, + function table() { return inspectedGlobalObject.console.table(...arguments); }, + function profile() { return inspectedGlobalObject.console.profile(...arguments); }, + function profileEnd() { return inspectedGlobalObject.console.profileEnd(...arguments); }, + + function keys(object) { return Object.keys(object); }, + function values(object) { + let result = []; + for (let key in object) + result.push(object[key]); + return result; + }, +]; + +for (let method of BasicCommandLineAPI.methods) + method.toString = function() { return "function " + method.name + "() { [Command Line API] }"; }; + return injectedScript; }) |