| /* |
| * 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: <https://webkit.org/b/152294> 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 isSymbol(obj) |
| { |
| return typeof obj === "symbol"; |
| } |
| |
| function isEmptyObject(object) |
| { |
| for (let key in object) |
| return false; |
| return true; |
| } |
| |
| function isDefined(value) |
| { |
| return !!value || InjectedScriptHost.isHTMLAllCollection(value); |
| } |
| |
| function isPrimitiveValue(value) |
| { |
| switch (typeof value) { |
| case "boolean": |
| case "number": |
| case "string": |
| return true; |
| case "undefined": |
| return !InjectedScriptHost.isHTMLAllCollection(value); |
| default: |
| return false; |
| } |
| } |
| |
| // ------- |
| |
| let InjectedScript = class InjectedScript |
| { |
| constructor() |
| { |
| this._lastBoundObjectId = 1; |
| this._idToWrappedObject = {}; |
| this._idToObjectGroupName = {}; |
| this._objectGroups = {}; |
| this._modules = {}; |
| this._nextSavedResultIndex = 1; |
| this._savedResults = []; |
| } |
| |
| // InjectedScript C++ API |
| |
| evaluate(expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview, saveResult) |
| { |
| return this._evaluateAndWrap(InjectedScriptHost.evaluateWithScopeExtension, InjectedScriptHost, expression, objectGroup, false, injectCommandLineAPI, returnByValue, generatePreview, saveResult); |
| } |
| |
| evaluateOnCallFrame(topCallFrame, callFrameId, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview, saveResult) |
| { |
| let 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); |
| } |
| |
| callFunctionOn(objectId, expression, args, returnByValue, generatePreview) |
| { |
| let parsedObjectId = this._parseObjectId(objectId); |
| let object = this._objectForId(parsedObjectId); |
| let objectGroupName = this._idToObjectGroupName[parsedObjectId.id]; |
| |
| if (!isDefined(object)) |
| return "Could not find object with given id"; |
| |
| let resolvedArgs = []; |
| if (args) { |
| let callArgs = InjectedScriptHost.evaluate(args); |
| for (let i = 0; i < callArgs.length; ++i) { |
| try { |
| resolvedArgs[i] = this._resolveCallArgument(callArgs[i]); |
| } catch (e) { |
| return String(e); |
| } |
| } |
| } |
| |
| try { |
| let func = InjectedScriptHost.evaluate("(" + expression + ")"); |
| if (typeof func !== "function") |
| return "Given expression does not evaluate to a function"; |
| |
| return { |
| wasThrown: false, |
| result: RemoteObject.create(func.apply(object, resolvedArgs), objectGroupName, returnByValue, generatePreview) |
| }; |
| } catch (e) { |
| return this._createThrownValue(e, objectGroupName); |
| } |
| } |
| |
| getFunctionDetails(objectId) |
| { |
| let parsedObjectId = this._parseObjectId(objectId); |
| let object = this._objectForId(parsedObjectId); |
| if (typeof object !== "function") |
| return "Cannot resolve function by id."; |
| return this.functionDetails(object); |
| } |
| |
| functionDetails(func) |
| { |
| let details = InjectedScriptHost.functionDetails(func); |
| if (!details) |
| return "Cannot resolve function details."; |
| return details; |
| } |
| |
| getPreview(objectId) |
| { |
| let parsedObjectId = this._parseObjectId(objectId); |
| let object = this._objectForId(parsedObjectId); |
| return RemoteObject.createObjectPreviewForValue(object, true); |
| } |
| |
| getProperties(objectId, ownProperties, generatePreview) |
| { |
| let nativeGettersAsValues = false; |
| let collectionMode = ownProperties ? InjectedScript.CollectionMode.OwnProperties : InjectedScript.CollectionMode.AllProperties; |
| return this._getProperties(objectId, collectionMode, generatePreview, nativeGettersAsValues); |
| } |
| |
| getDisplayableProperties(objectId, generatePreview) |
| { |
| let nativeGettersAsValues = true; |
| let collectionMode = InjectedScript.CollectionMode.OwnProperties | InjectedScript.CollectionMode.NativeGetterProperties; |
| return this._getProperties(objectId, collectionMode, generatePreview, nativeGettersAsValues); |
| } |
| |
| getInternalProperties(objectId, generatePreview) |
| { |
| let parsedObjectId = this._parseObjectId(objectId); |
| let object = this._objectForId(parsedObjectId); |
| let objectGroupName = this._idToObjectGroupName[parsedObjectId.id]; |
| |
| if (!isDefined(object)) |
| return false; |
| |
| if (isSymbol(object)) |
| return false; |
| |
| let descriptors = this._internalPropertyDescriptors(object); |
| if (!descriptors) |
| return []; |
| |
| for (let i = 0; i < descriptors.length; ++i) { |
| let descriptor = descriptors[i]; |
| if ("value" in descriptor) |
| descriptor.value = RemoteObject.create(descriptor.value, objectGroupName, false, generatePreview); |
| } |
| |
| return descriptors; |
| } |
| |
| getCollectionEntries(objectId, objectGroupName, startIndex, numberToFetch) |
| { |
| let parsedObjectId = this._parseObjectId(objectId); |
| let object = this._objectForId(parsedObjectId); |
| objectGroupName = objectGroupName || this._idToObjectGroupName[parsedObjectId.id]; |
| |
| if (!isDefined(object)) |
| return; |
| |
| if (typeof object !== "object") |
| return; |
| |
| let entries = this._entries(object, InjectedScriptHost.subtype(object), startIndex, numberToFetch); |
| return entries.map(function(entry) { |
| entry.value = RemoteObject.create(entry.value, objectGroupName, false, true); |
| if ("key" in entry) |
| entry.key = RemoteObject.create(entry.key, objectGroupName, false, true); |
| return entry; |
| }); |
| } |
| |
| saveResult(callArgumentJSON) |
| { |
| this._savedResultIndex = 0; |
| |
| try { |
| let callArgument = InjectedScriptHost.evaluate("(" + callArgumentJSON + ")"); |
| let value = this._resolveCallArgument(callArgument); |
| this._saveResult(value); |
| } catch (e) {} |
| |
| return this._savedResultIndex; |
| } |
| |
| wrapCallFrames(callFrame) |
| { |
| if (!callFrame) |
| return false; |
| |
| let result = []; |
| let depth = 0; |
| do { |
| result.push(new InjectedScript.CallFrameProxy(depth++, callFrame)); |
| callFrame = callFrame.caller; |
| } while (callFrame); |
| return result; |
| } |
| |
| wrapObject(object, groupName, canAccessInspectedGlobalObject, generatePreview) |
| { |
| if (!canAccessInspectedGlobalObject) |
| return this._fallbackWrapper(object); |
| |
| return RemoteObject.create(object, groupName, false, generatePreview); |
| } |
| |
| wrapJSONString(jsonString, groupName, generatePreview) |
| { |
| try { |
| return this.wrapObject(JSON.parse(jsonString), groupName, true, generatePreview); |
| } catch { |
| return null; |
| } |
| } |
| |
| wrapTable(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. |
| |
| let columnNames = null; |
| if (typeof columns === "string") |
| columns = [columns]; |
| |
| if (InjectedScriptHost.subtype(columns) === "array") { |
| columnNames = []; |
| for (let i = 0; i < columns.length; ++i) |
| columnNames.push(toString(columns[i])); |
| } |
| |
| return RemoteObject.create(table, "console", false, true, columnNames); |
| } |
| |
| previewValue(value) |
| { |
| return RemoteObject.createObjectPreviewForValue(value, true); |
| } |
| |
| setExceptionValue(value) |
| { |
| this._exceptionValue = value; |
| } |
| |
| clearExceptionValue() |
| { |
| delete this._exceptionValue; |
| } |
| |
| findObjectById(objectId) |
| { |
| let parsedObjectId = this._parseObjectId(objectId); |
| return this._objectForId(parsedObjectId); |
| } |
| |
| inspectObject(object) |
| { |
| if (this._commandLineAPIImpl) |
| this._commandLineAPIImpl.inspect(object); |
| } |
| |
| releaseObject(objectId) |
| { |
| let parsedObjectId = this._parseObjectId(objectId); |
| this._releaseObject(parsedObjectId.id); |
| } |
| |
| releaseObjectGroup(objectGroupName) |
| { |
| if (objectGroupName === "console") { |
| delete this._lastResult; |
| this._nextSavedResultIndex = 1; |
| this._savedResults = []; |
| } |
| |
| let group = this._objectGroups[objectGroupName]; |
| if (!group) |
| return; |
| |
| for (let i = 0; i < group.length; i++) |
| this._releaseObject(group[i]); |
| |
| delete this._objectGroups[objectGroupName]; |
| } |
| |
| // InjectedScriptModule C++ API |
| |
| module(name) |
| { |
| return this._modules[name]; |
| } |
| |
| injectModule(name, source, host) |
| { |
| delete this._modules[name]; |
| |
| let 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; |
| } |
| |
| let module = moduleFunction.call(inspectedGlobalObject, InjectedScriptHost, inspectedGlobalObject, injectedScriptId, this, RemoteObject, host); |
| this._modules[name] = module; |
| return module; |
| } |
| |
| // InjectedScriptModule JavaScript API |
| |
| isPrimitiveValue(value) |
| { |
| return isPrimitiveValue(value); |
| } |
| |
| // Private |
| |
| _parseObjectId(objectId) |
| { |
| return InjectedScriptHost.evaluate("(" + objectId + ")"); |
| } |
| |
| _objectForId(objectId) |
| { |
| return this._idToWrappedObject[objectId.id]; |
| } |
| |
| _bind(object, objectGroupName) |
| { |
| let id = this._lastBoundObjectId++; |
| let objectId = `{"injectedScriptId":${injectedScriptId},"id":${id}}`; |
| |
| this._idToWrappedObject[id] = object; |
| |
| if (objectGroupName) { |
| let group = this._objectGroups[objectGroupName]; |
| if (!group) { |
| group = []; |
| this._objectGroups[objectGroupName] = group; |
| } |
| group.push(id); |
| this._idToObjectGroupName[id] = objectGroupName; |
| } |
| |
| return objectId; |
| } |
| |
| _releaseObject(id) |
| { |
| delete this._idToWrappedObject[id]; |
| delete this._idToObjectGroupName[id]; |
| } |
| |
| _fallbackWrapper(object) |
| { |
| let result = {}; |
| result.type = typeof object; |
| if (isPrimitiveValue(object)) |
| result.value = object; |
| else |
| result.description = toString(object); |
| return result; |
| } |
| |
| _resolveCallArgument(callArgumentJSON) |
| { |
| if ("value" in callArgumentJSON) |
| return callArgumentJSON.value; |
| |
| let objectId = callArgumentJSON.objectId; |
| if (objectId) { |
| let parsedArgId = this._parseObjectId(objectId); |
| if (!parsedArgId || parsedArgId["injectedScriptId"] !== injectedScriptId) |
| throw "Arguments should belong to the same JavaScript world as the target object."; |
| |
| let resolvedArg = this._objectForId(parsedArgId); |
| if (!isDefined(resolvedArg)) |
| throw "Could not find object with given id"; |
| |
| return resolvedArg; |
| } |
| |
| return undefined; |
| } |
| |
| _createThrownValue(value, objectGroup) |
| { |
| let remoteObject = RemoteObject.create(value, objectGroup); |
| try { |
| remoteObject.description = toStringDescription(value); |
| } catch (e) {} |
| return { |
| wasThrown: true, |
| result: remoteObject |
| }; |
| } |
| |
| _evaluateAndWrap(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI, returnByValue, generatePreview, saveResult) |
| { |
| try { |
| this._savedResultIndex = 0; |
| |
| let returnObject = { |
| wasThrown: false, |
| result: RemoteObject.create(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); |
| } |
| } |
| |
| _evaluateOn(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI, saveResult) |
| { |
| let commandLineAPI = null; |
| if (injectCommandLineAPI) { |
| if (this.CommandLineAPI) |
| commandLineAPI = new this.CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null); |
| else |
| commandLineAPI = new BasicCommandLineAPI(isEvalOnCallFrame ? object : null); |
| } |
| |
| let result = evalFunction.call(object, expression, commandLineAPI); |
| if (saveResult) |
| this._saveResult(result); |
| return result; |
| } |
| |
| _callFrameForId(topCallFrame, callFrameId) |
| { |
| let parsedCallFrameId = InjectedScriptHost.evaluate("(" + callFrameId + ")"); |
| let ordinal = parsedCallFrameId["ordinal"]; |
| let callFrame = topCallFrame; |
| while (--ordinal >= 0 && callFrame) |
| callFrame = callFrame.caller; |
| return callFrame; |
| } |
| |
| _getProperties(objectId, collectionMode, generatePreview, nativeGettersAsValues) |
| { |
| let parsedObjectId = this._parseObjectId(objectId); |
| let object = this._objectForId(parsedObjectId); |
| let objectGroupName = this._idToObjectGroupName[parsedObjectId.id]; |
| |
| if (!isDefined(object)) |
| return false; |
| |
| if (isSymbol(object)) |
| return false; |
| |
| let descriptors = this._propertyDescriptors(object, collectionMode, nativeGettersAsValues); |
| |
| for (let i = 0; i < descriptors.length; ++i) { |
| let descriptor = descriptors[i]; |
| if ("get" in descriptor) |
| descriptor.get = RemoteObject.create(descriptor.get, objectGroupName); |
| if ("set" in descriptor) |
| descriptor.set = RemoteObject.create(descriptor.set, objectGroupName); |
| if ("value" in descriptor) |
| descriptor.value = RemoteObject.create(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 = RemoteObject.create(descriptor.symbol, objectGroupName); |
| } |
| |
| return descriptors; |
| } |
| |
| _internalPropertyDescriptors(object, completeDescriptor) |
| { |
| let internalProperties = InjectedScriptHost.getInternalProperties(object); |
| if (!internalProperties) |
| return null; |
| |
| let descriptors = []; |
| for (let i = 0; i < internalProperties.length; i++) { |
| let property = internalProperties[i]; |
| let 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(object, collectionMode, nativeGettersAsValues) |
| { |
| if (InjectedScriptHost.subtype(object) === "proxy") |
| return []; |
| |
| let descriptors = []; |
| let nameProcessed = new Set; |
| |
| function createFakeValueDescriptor(name, symbol, descriptor, isOwnProperty, possibleNativeBindingGetter) |
| { |
| try { |
| let fakeDescriptor = {name, value: object[name], writable: descriptor.writable || false, configurable: descriptor.configurable || false, enumerable: descriptor.enumerable || false}; |
| if (possibleNativeBindingGetter) |
| fakeDescriptor.nativeGetter = true; |
| if (isOwnProperty) |
| fakeDescriptor.isOwn = true; |
| if (symbol) |
| fakeDescriptor.symbol = symbol; |
| // Silence any possible unhandledrejection exceptions created from accessing a native accessor with a wrong this object. |
| if (fakeDescriptor.value instanceof Promise) |
| fakeDescriptor.value.catch(function(){}); |
| return fakeDescriptor; |
| } catch (e) { |
| let 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 (let i = 0; i < properties.length; ++i) { |
| let property = properties[i]; |
| if (nameProcessed.has(property) || property === "__proto__") |
| continue; |
| |
| nameProcessed.add(property); |
| |
| let name = toString(property); |
| let symbol = isSymbol(property) ? property : null; |
| |
| let descriptor = Object.getOwnPropertyDescriptor(o, property); |
| if (!descriptor) { |
| // FIXME: Bad descriptor. Can we get here? |
| // Fall back to very restrictive settings. |
| let 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: |
| // let x = {}; Object.defineProperty(x, "p", {get:undefined}); Object.getOwnPropertyDescriptor(x, "p") |
| let 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) |
| { |
| let array = []; |
| for (let 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. |
| let isArrayLike = false; |
| try { |
| isArrayLike = RemoteObject.subtype(object) === "array" && isFinite(object.length) && object.length > 0; |
| } catch(e) {} |
| |
| for (let o = object; isDefined(o); o = Object.getPrototypeOf(o)) { |
| let 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; |
| } |
| |
| _getSetEntries(object, skip, numberToFetch) |
| { |
| let entries = []; |
| |
| // FIXME: This is observable if the page overrides Set.prototype[Symbol.iterator]. |
| for (let value of object) { |
| if (skip > 0) { |
| skip--; |
| continue; |
| } |
| |
| entries.push({value}); |
| |
| if (numberToFetch && entries.length === numberToFetch) |
| break; |
| } |
| |
| return entries; |
| } |
| |
| _getMapEntries(object, skip, numberToFetch) |
| { |
| let entries = []; |
| |
| // FIXME: This is observable if the page overrides Map.prototype[Symbol.iterator]. |
| for (let [key, value] of object) { |
| if (skip > 0) { |
| skip--; |
| continue; |
| } |
| |
| entries.push({key, value}); |
| |
| if (numberToFetch && entries.length === numberToFetch) |
| break; |
| } |
| |
| return entries; |
| } |
| |
| _getWeakMapEntries(object, numberToFetch) |
| { |
| return InjectedScriptHost.weakMapEntries(object, numberToFetch); |
| } |
| |
| _getWeakSetEntries(object, numberToFetch) |
| { |
| return InjectedScriptHost.weakSetEntries(object, numberToFetch); |
| } |
| |
| _getIteratorEntries(object, numberToFetch) |
| { |
| return InjectedScriptHost.iteratorEntries(object, numberToFetch); |
| } |
| |
| _entries(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(result) |
| { |
| this._lastResult = result; |
| |
| if (result === undefined || result === null) |
| return; |
| |
| let 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(index) |
| { |
| return this._savedResults[index]; |
| } |
| } |
| |
| 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. |
| }; |
| |
| var injectedScript = new InjectedScript; |
| |
| // ------- |
| |
| let RemoteObject = class RemoteObject |
| { |
| constructor(object, objectGroupName, forceValueType, generatePreview, columnNames) |
| { |
| this.type = typeof object; |
| |
| if (this.type === "undefined" && InjectedScriptHost.isHTMLAllCollection(object)) |
| this.type = "object"; |
| |
| if (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); |
| |
| let subtype = RemoteObject.subtype(object); |
| if (subtype) |
| this.subtype = subtype; |
| |
| this.className = InjectedScriptHost.internalConstructorName(object); |
| this.description = RemoteObject.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 = RemoteObject.create(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); |
| } |
| } |
| |
| // Static |
| |
| static create(object, objectGroupName, forceValueType, generatePreview, columnNames) |
| { |
| try { |
| return new RemoteObject(object, objectGroupName, forceValueType, generatePreview, columnNames); |
| } catch (e) { |
| let description; |
| try { |
| description = RemoteObject.describe(e); |
| } catch (ex) { |
| alert(ex.message); |
| description = "<failed to convert exception to string>"; |
| } |
| return new RemoteObject(description); |
| } |
| } |
| |
| static createObjectPreviewForValue(value, generatePreview, columnNames) |
| { |
| let remoteObject = new 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(); |
| } |
| |
| static subtype(value) |
| { |
| if (value === null) |
| return "null"; |
| |
| if (isPrimitiveValue(value) || isSymbol(value)) |
| return null; |
| |
| if (InjectedScriptHost.isHTMLAllCollection(value)) |
| return "array"; |
| |
| let preciseType = InjectedScriptHost.subtype(value); |
| if (preciseType) |
| return preciseType; |
| |
| // FireBug's array detection. |
| try { |
| if (typeof value.splice === "function" && isFinite(value.length)) |
| return "array"; |
| } catch (e) {} |
| |
| return null; |
| } |
| |
| static describe(value) |
| { |
| if (isPrimitiveValue(value)) |
| return null; |
| |
| if (isSymbol(value)) |
| return toString(value); |
| |
| let subtype = RemoteObject.subtype(value); |
| |
| if (subtype === "regexp") |
| return toString(value); |
| |
| if (subtype === "date") |
| return toString(value); |
| |
| if (subtype === "error") |
| return toString(value); |
| |
| if (subtype === "proxy") |
| return "Proxy"; |
| |
| if (subtype === "node") |
| return RemoteObject.nodePreview(value); |
| |
| let className = InjectedScriptHost.internalConstructorName(value); |
| if (subtype === "array") |
| return className; |
| |
| if (subtype === "iterator" && Symbol.toStringTag in value) |
| return value[Symbol.toStringTag]; |
| |
| // NodeList in JSC is a function, check for array prior to this. |
| if (typeof value === "function") |
| return value.toString(); |
| |
| // If Object, try for a better name from the constructor. |
| if (className === "Object") { |
| let constructorName = value.constructor && value.constructor.name; |
| if (constructorName) |
| return constructorName; |
| } |
| |
| return className; |
| } |
| |
| static nodePreview(node) |
| { |
| let isXMLDocument = node.ownerDocument && !!node.ownerDocument.xmlVersion; |
| let 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; |
| } |
| } |
| |
| // Private |
| |
| _initialPreview() |
| { |
| let 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() |
| { |
| let 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(object, firstLevelKeys, secondLevelKeys) |
| { |
| let preview = this._initialPreview(); |
| let isTableRowsRequest = secondLevelKeys === null || secondLevelKeys; |
| let firstLevelKeysCount = firstLevelKeys ? firstLevelKeys.length : 0; |
| |
| let 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. |
| let 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. |
| let nativeGettersAsValues = true; |
| let 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; |
| } |
| |
| _appendPropertyPreviews(object, preview, descriptors, internal, propertiesThreshold, firstLevelKeys, secondLevelKeys) |
| { |
| for (let i = 0; i < descriptors.length; ++i) { |
| let descriptor = descriptors[i]; |
| |
| // 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. |
| let 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. |
| let value = descriptor.value; |
| if (value === null) { |
| this._appendPropertyPreview(preview, internal, {name, type: "object", subtype: "null", value: "null"}, propertiesThreshold); |
| continue; |
| } |
| |
| // Ignore non-enumerable functions. |
| let type = typeof value; |
| if (!descriptor.enumerable && type === "function") |
| continue; |
| |
| // Fix type of document.all. |
| if (InjectedScriptHost.isHTMLAllCollection(value)) |
| type = "object"; |
| |
| // Primitive. |
| const maxLength = 100; |
| if (isPrimitiveValue(value)) { |
| 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)) { |
| let 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. |
| let property = {name, type}; |
| let subtype = RemoteObject.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. |
| let subPreview = RemoteObject.createObjectPreviewForValue(value, value !== object, secondLevelKeys); |
| property.valuePreview = subPreview; |
| if (!subPreview.lossless) |
| preview.lossless = false; |
| if (subPreview.overflow) |
| preview.overflow = true; |
| } else { |
| let description = ""; |
| if (type !== "function" || subtype === "class") { |
| let fullDescription; |
| if (subtype === "class") |
| fullDescription = "class " + value.name; |
| else if (subtype === "node") |
| fullDescription = RemoteObject.nodePreview(value); |
| else |
| fullDescription = RemoteObject.describe(value); |
| description = this._abbreviateString(fullDescription, maxLength, subtype === "regexp"); |
| } |
| property.value = description; |
| preview.lossless = false; |
| } |
| |
| this._appendPropertyPreview(preview, internal, property, propertiesThreshold); |
| } |
| } |
| |
| _appendPropertyPreview(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(object, preview) |
| { |
| // Fetch 6, but only return 5, so we can tell if we overflowed. |
| let 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 = RemoteObject.createObjectPreviewForValue(entry.value, entry.value !== object); |
| updateMainPreview(entry.value); |
| if ("key" in entry) { |
| entry.key = RemoteObject.createObjectPreviewForValue(entry.key, entry.key !== object); |
| updateMainPreview(entry.key); |
| } |
| return entry; |
| }); |
| } |
| |
| _isPreviewableObject(value, object) |
| { |
| let set = new Set; |
| set.add(object); |
| |
| return this._isPreviewableObjectInternal(value, set, 1); |
| } |
| |
| _isPreviewableObjectInternal(object, knownObjects, depth) |
| { |
| // Deep object. |
| if (depth > 3) |
| return false; |
| |
| // Primitive. |
| if (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. |
| let subtype = RemoteObject.subtype(object); |
| if (subtype === "array") { |
| let length = object.length; |
| if (length > 5) |
| return false; |
| for (let 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. |
| let ownPropertyNames = Object.getOwnPropertyNames(object); |
| if (ownPropertyNames.length > 3) |
| return false; |
| for (let i = 0; i < ownPropertyNames.length; ++i) { |
| let propertyName = ownPropertyNames[i]; |
| if (!this._isPreviewableObjectInternal(object[propertyName], knownObjects, depth)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| _abbreviateString(string, maxLength, middle) |
| { |
| if (string.length <= maxLength) |
| return string; |
| |
| if (middle) { |
| let leftHalf = maxLength >> 1; |
| let 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 = RemoteObject.create(callFrame.thisObject, "backtrace"); |
| this.isTailDeleted = callFrame.isTailDeleted; |
| } |
| |
| InjectedScript.CallFrameProxy.prototype = { |
| _wrapScopeChain(callFrame) |
| { |
| let scopeChain = callFrame.scopeChain; |
| let scopeDescriptions = callFrame.scopeDescriptions(); |
| |
| let scopeChainProxy = []; |
| for (let i = 0; i < scopeChain.length; i++) |
| scopeChainProxy[i] = InjectedScript.CallFrameProxy._createScopeJson(scopeChain[i], scopeDescriptions[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(object, {name, type, location}, groupId) |
| { |
| let scope = { |
| object: RemoteObject.create(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(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 i = 0; i < BasicCommandLineAPI.methods.length; ++i) { |
| let method = BasicCommandLineAPI.methods[i]; |
| 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 i = 0; i < BasicCommandLineAPI.methods.length; ++i) { |
| let method = BasicCommandLineAPI.methods[i]; |
| method.toString = function() { return "function " + method.name + "() { [Command Line API] }"; }; |
| } |
| |
| return injectedScript; |
| }) |