/* * 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=__InjectedScript_InjectedScriptSource.js (function (InjectedScriptHost, inspectedGlobalObject, injectedScriptId) { // FIXME: Web Inspector: Parse InjectedScriptSource as a built-in to get guaranteed non-user-overriden built-ins 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); }, 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. // [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) 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 = ""; } 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."; return injectedScript.functionDetails(func); }, 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.evaluateWithScopeExtension, 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(isEvalOnCallFrame ? object : null); } var result = evalFunction.call(object, expression, commandLineAPI); if (saveResult) this._saveResult(result); return result; }, 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.evaluateWithScopeExtension, 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: 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 // 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: 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; }, _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 ""; case 10: // Node.DOCUMENT_TYPE_NODE return ""; 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._nodePreview(obj); var className = InjectedScriptHost.internalConstructorName(obj); 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 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.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(); }; 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; }, _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__") { // 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; } // 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. // 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 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 = InjectedScript.RemoteObject.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 = 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"; } } 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", false, true); this.isTailDeleted = callFrame.isTailDeleted; } 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 bind(func, thisObject, ...outerArgs) { return function(...innerArgs) { return func.apply(thisObject, outerArgs.concat(innerArgs)); }; } 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; })