| /* |
| * Copyright (C) 2007-2022 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. |
| */ |
| |
| @globalPrivate |
| function createObjectWithoutPrototype(/* key1, value1, key2, value2, ... */) |
| { |
| if (arguments.length % 2 !== 0) |
| @throwRangeError("`createObjectWithoutPrototype` requires that each key argument be followed by a value, resulting in an even number of arguments."); |
| |
| let object = @Object.@create(null) |
| for (let i = 0; i < arguments.length; i += 2) |
| object[arguments[i]] = arguments[i + 1]; |
| |
| return object; |
| } |
| |
| @globalPrivate |
| function createArrayWithoutPrototype(/* value1, value2, ... */) |
| { |
| let array = new @Array(arguments.length); |
| array.__proto__ = null; |
| |
| for (let i = 0; i < arguments.length; ++i) |
| @arrayPush(array, arguments[i]); |
| |
| return array; |
| } |
| |
| @globalPrivate |
| function createInspectorInjectedScript(InjectedScriptHost, inspectedGlobalObject, injectedScriptId) |
| { |
| |
| function toString(obj) |
| { |
| return @String(obj); |
| } |
| |
| function toStringDescription(obj) |
| { |
| if (obj === 0 && 1 / obj < 0) |
| return "-0"; |
| |
| if (isBigInt(obj)) |
| return toString(obj) + "n"; |
| |
| 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(value) |
| { |
| return typeof value === "symbol"; |
| } |
| |
| function isBigInt(value) |
| { |
| return typeof value === "bigint"; |
| } |
| |
| 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; |
| } |
| } |
| |
| function createIterableWithoutPrototypeFromArguments(argumentsObject) { |
| let iteratorFunction = argumentsObject.@@iterator; |
| return @wrappedIterator(iteratorFunction.@call(argumentsObject)); |
| } |
| |
| function max(a, b) { |
| return a > b ? a : b; |
| } |
| |
| // ------- |
| |
| let InjectedScript = class InjectedScript |
| { |
| constructor() |
| { |
| this._lastBoundObjectId = 1; |
| this._idToWrappedObject = @createObjectWithoutPrototype(); |
| this._idToObjectGroupName = @createObjectWithoutPrototype(); |
| this._objectGroups = @createObjectWithoutPrototype(); |
| this._modules = @createObjectWithoutPrototype(); |
| this._nextSavedResultIndex = 1; |
| this._savedResults = @createArrayWithoutPrototype(); |
| } |
| |
| // InjectedScript C++ API |
| |
| execute(functionString, objectGroup, includeCommandLineAPI, returnByValue, generatePreview, saveResult, args) |
| { |
| return this._wrapAndSaveCall(objectGroup, returnByValue, generatePreview, saveResult, () => { |
| const isEvalOnCallFrame = false; |
| return this._evaluateOn(InjectedScriptHost.evaluateWithScopeExtension, InjectedScriptHost, functionString, isEvalOnCallFrame, includeCommandLineAPI).@apply(@undefined, args); |
| }); |
| } |
| |
| evaluate(expression, objectGroup, includeCommandLineAPI, returnByValue, generatePreview, saveResult) |
| { |
| const isEvalOnCallFrame = false; |
| return this._evaluateAndWrap(InjectedScriptHost.evaluateWithScopeExtension, InjectedScriptHost, expression, objectGroup, isEvalOnCallFrame, includeCommandLineAPI, returnByValue, generatePreview, saveResult); |
| } |
| |
| awaitPromise(promiseObjectId, returnByValue, generatePreview, saveResult, callback) |
| { |
| let parsedPromiseObjectId = this._parseObjectId(promiseObjectId); |
| let promiseObject = this._objectForId(parsedPromiseObjectId); |
| let promiseObjectGroupName = this._idToObjectGroupName[parsedPromiseObjectId.id]; |
| |
| if (!isDefined(promiseObject)) { |
| callback("Could not find object with given id"); |
| return; |
| } |
| |
| if (!(promiseObject instanceof @Promise)) { |
| callback("Object with given id is not a Promise"); |
| return; |
| } |
| |
| let resolve = (value) => { |
| let returnObject = @createObjectWithoutPrototype( |
| "wasThrown", false, |
| "result", RemoteObject.create(value, promiseObjectGroupName, returnByValue, generatePreview), |
| ); |
| |
| if (saveResult) { |
| this._savedResultIndex = 0; |
| this._saveResult(returnObject.result); |
| if (this._savedResultIndex) |
| returnObject.savedResultIndex = this._savedResultIndex; |
| } |
| |
| callback(returnObject); |
| }; |
| let reject = (reason) => { |
| callback(this._createThrownValue(reason, promiseObjectGroupName)); |
| }; |
| promiseObject.@then(resolve, reject); |
| } |
| |
| evaluateOnCallFrame(topCallFrame, callFrameId, expression, objectGroup, includeCommandLineAPI, returnByValue, generatePreview, saveResult) |
| { |
| let callFrame = this._callFrameForId(topCallFrame, callFrameId); |
| if (!callFrame) |
| return "Could not find call frame with given id"; |
| const isEvalOnCallFrame = true; |
| return this._evaluateAndWrap(callFrame.evaluateWithScopeExtension, callFrame, expression, objectGroup, isEvalOnCallFrame, includeCommandLineAPI, 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 = @createArrayWithoutPrototype(); |
| 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 @createObjectWithoutPrototype( |
| "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, fetchStart, fetchCount, generatePreview) |
| { |
| let collectionMode = ownProperties ? InjectedScript.CollectionMode.OwnProperties : InjectedScript.CollectionMode.AllProperties; |
| return this._getProperties(objectId, collectionMode, @createObjectWithoutPrototype( |
| "fetchStart", fetchStart, |
| "fetchCount", fetchCount, |
| "generatePreview", generatePreview, |
| )); |
| } |
| |
| getDisplayableProperties(objectId, fetchStart, fetchCount, generatePreview) |
| { |
| let collectionMode = InjectedScript.CollectionMode.OwnProperties | InjectedScript.CollectionMode.NativeGetterProperties; |
| return this._getProperties(objectId, collectionMode, @createObjectWithoutPrototype( |
| "fetchStart", fetchStart, |
| "fetchCount", fetchCount, |
| "generatePreview", generatePreview, |
| "nativeGettersAsValues", true, |
| )); |
| } |
| |
| 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 @createArrayWithoutPrototype(); |
| |
| 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, fetchStart, fetchCount) |
| { |
| 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), fetchStart, fetchCount); |
| for (let i = 0; i < entries.length; ++i) { |
| let entry = entries[i]; |
| entry.value = RemoteObject.create(entry.value, objectGroupName, false, true); |
| if ("key" in entry) |
| entry.key = RemoteObject.create(entry.key, objectGroupName, false, true); |
| } |
| return entries; |
| } |
| |
| saveResult(callArgumentJSON) |
| { |
| this._savedResultIndex = 0; |
| |
| try { |
| let callArgument = InjectedScriptHost.evaluate("(" + callArgumentJSON + ")"); |
| let value = this._resolveCallArgument(callArgument); |
| this._saveResult(value); |
| } catch { } |
| |
| return this._savedResultIndex; |
| } |
| |
| wrapCallFrames(callFrame) |
| { |
| if (!callFrame) |
| return false; |
| |
| let result = @createArrayWithoutPrototype(); |
| let depth = 0; |
| do { |
| @arrayPush(result, 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(@jsonParse(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 = @createArrayWithoutPrototype(columns); |
| |
| if (InjectedScriptHost.subtype(columns) === "array") { |
| columnNames = @createArrayWithoutPrototype(); |
| for (let i = 0; i < columns.length; ++i) |
| @arrayPush(columnNames, toString(columns[i])); |
| } |
| |
| return RemoteObject.create(table, "console", false, true, columnNames); |
| } |
| |
| previewValue(value) |
| { |
| return RemoteObject.createObjectPreviewForValue(value, true); |
| } |
| |
| setEventValue(value) |
| { |
| this._eventValue = value; |
| } |
| |
| clearEventValue() |
| { |
| delete this._eventValue; |
| } |
| |
| setExceptionValue(value) |
| { |
| this._exceptionValue = value; |
| } |
| |
| clearExceptionValue() |
| { |
| delete this._exceptionValue; |
| } |
| |
| findObjectById(objectId) |
| { |
| let parsedObjectId = this._parseObjectId(objectId); |
| return this._objectForId(parsedObjectId); |
| } |
| |
| releaseObject(objectId) |
| { |
| let parsedObjectId = this._parseObjectId(objectId); |
| this._releaseObject(parsedObjectId.id); |
| } |
| |
| releaseObjectGroup(objectGroupName) |
| { |
| if (objectGroupName === "console") { |
| delete this._lastResult; |
| this._nextSavedResultIndex = 1; |
| this._savedResults = @createArrayWithoutPrototype(); |
| } |
| |
| let group = this._objectGroups[objectGroupName]; |
| if (!group) |
| return; |
| |
| for (let i = 0; i < group.length; i++) |
| this._releaseObject(group[i]); |
| |
| delete this._objectGroups[objectGroupName]; |
| } |
| |
| createCommandLineAPIObject(callFrame) |
| { |
| return new CommandLineAPI(callFrame || null); |
| } |
| |
| // CommandLineAPI |
| |
| inspectObject(object) |
| { |
| if (this._inspectObject) |
| this._inspectObject(object); |
| } |
| |
| // InjectedScriptModule C++ API |
| |
| hasInjectedModule(name) |
| { |
| return this._modules[name]; |
| } |
| |
| injectModule(name, moduleFunction, host) |
| { |
| this._modules[name] = false; |
| |
| if (typeof moduleFunction !== "function") |
| throw "Error: Web Inspector: a function was expected for injectModule"; |
| moduleFunction(InjectedScriptHost, inspectedGlobalObject, injectedScriptId, this, @createObjectWithoutPrototype( |
| "RemoteObject", RemoteObject, |
| "CommandLineAPI", CommandLineAPI, |
| ), host); |
| |
| this._modules[name] = true; |
| } |
| |
| // 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 = @createArrayWithoutPrototype(); |
| this._objectGroups[objectGroupName] = group; |
| } |
| @arrayPush(group, id); |
| this._idToObjectGroupName[id] = objectGroupName; |
| } |
| |
| return objectId; |
| } |
| |
| _releaseObject(id) |
| { |
| delete this._idToWrappedObject[id]; |
| delete this._idToObjectGroupName[id]; |
| } |
| |
| _fallbackWrapper(object) |
| { |
| let result = @createObjectWithoutPrototype("type", typeof object); |
| if (isPrimitiveValue(object)) |
| result.value = object; |
| else |
| result.description = toStringDescription(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 { } |
| return @createObjectWithoutPrototype( |
| "wasThrown", true, |
| "result", remoteObject, |
| ); |
| } |
| |
| _evaluateAndWrap(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, includeCommandLineAPI, returnByValue, generatePreview, saveResult) |
| { |
| return this._wrapAndSaveCall(objectGroup, returnByValue, generatePreview, saveResult, () => { |
| return this._evaluateOn(evalFunction, object, expression, isEvalOnCallFrame, includeCommandLineAPI); |
| }); |
| } |
| |
| _wrapAndSaveCall(objectGroup, returnByValue, generatePreview, saveResult, func) |
| { |
| return this._wrapCall(objectGroup, returnByValue, generatePreview, saveResult, () => { |
| let result = func(); |
| if (saveResult) |
| this._saveResult(result); |
| return result; |
| }); |
| } |
| |
| _wrapCall(objectGroup, returnByValue, generatePreview, saveResult, func) |
| { |
| try { |
| this._savedResultIndex = 0; |
| |
| let returnObject = @createObjectWithoutPrototype( |
| "wasThrown", false, |
| "result", RemoteObject.create(func(), objectGroup, returnByValue, generatePreview), |
| ); |
| |
| if (saveResult && this._savedResultIndex) |
| returnObject.savedResultIndex = this._savedResultIndex; |
| |
| return returnObject; |
| } catch (e) { |
| return this._createThrownValue(e, objectGroup); |
| } |
| } |
| |
| _evaluateOn(evalFunction, object, expression, isEvalOnCallFrame, includeCommandLineAPI) |
| { |
| let commandLineAPI = null; |
| if (includeCommandLineAPI) |
| commandLineAPI = this.createCommandLineAPIObject(isEvalOnCallFrame ? object : null); |
| return evalFunction.@call(object, expression, commandLineAPI); |
| } |
| |
| _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, {fetchStart, fetchCount, 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 start = fetchStart || 0; |
| if (start < 0) |
| start = 0; |
| |
| let count = fetchCount || 0; |
| if (count < 0) |
| count = 0; |
| |
| // Always include __proto__ at the end, but only for the first fetch. |
| let includeProto = !start; |
| |
| let descriptors = @createArrayWithoutPrototype(); |
| this._forEachPropertyDescriptor(object, collectionMode, (descriptor) => { |
| if (start > 0) { |
| --start; |
| return InjectedScript.PropertyFetchAction.Continue; |
| } |
| |
| 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 ("symbol" in descriptor) |
| descriptor.symbol = RemoteObject.create(descriptor.symbol, objectGroupName); |
| @arrayPush(descriptors, descriptor); |
| |
| if (includeProto && count && descriptors.length >= count && descriptor.name !== "__proto__") |
| return InjectedScript.PropertyFetchAction.Stop; |
| |
| return (count && descriptors.length >= count) ? InjectedScript.PropertyFetchAction.Stop : InjectedScript.PropertyFetchAction.Continue; |
| }, @createObjectWithoutPrototype( |
| "nativeGettersAsValues", nativeGettersAsValues, |
| "includeProto", includeProto, |
| )); |
| return descriptors; |
| } |
| |
| _internalPropertyDescriptors(object, completeDescriptor) |
| { |
| let internalProperties = InjectedScriptHost.getInternalProperties(object); |
| if (!internalProperties) |
| return null; |
| |
| let descriptors = @createArrayWithoutPrototype(); |
| for (let i = 0; i < internalProperties.length; i++) { |
| let property = internalProperties[i]; |
| let descriptor = @createObjectWithoutPrototype( |
| "name", property.name, |
| "value", property.value, |
| ); |
| if (completeDescriptor) |
| descriptor.isOwn = true; |
| @arrayPush(descriptors, descriptor); |
| } |
| return descriptors; |
| } |
| |
| _forEachPropertyDescriptor(object, collectionMode, callback, {nativeGettersAsValues, includeProto}) |
| { |
| if (InjectedScriptHost.subtype(object) === "proxy") |
| return; |
| |
| let nameProcessed = new @Set; |
| |
| // Handled below when `includeProto`. |
| nameProcessed.@add("__proto__"); |
| |
| function createFakeValueDescriptor(name, symbol, descriptor, isOwnProperty, possibleNativeBindingGetter) |
| { |
| try { |
| let fakeDescriptor = @createObjectWithoutPrototype( |
| "name", name, |
| "value", object[name], |
| ); |
| if (descriptor) { |
| if (descriptor.writable) |
| fakeDescriptor.writable = true; |
| if (descriptor.configurable) |
| fakeDescriptor.configurable = true; |
| if (descriptor.enumerable) |
| fakeDescriptor.enumerable = true; |
| } |
| 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 && InjectedScriptHost.isPromiseRejectedWithNativeGetterTypeError(fakeDescriptor.value)) |
| fakeDescriptor.value.@catch(function(){}); |
| return fakeDescriptor; |
| } catch (e) { |
| let errorDescriptor = @createObjectWithoutPrototype( |
| "name", 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) |
| return callback(descriptor); |
| |
| // Own properties. |
| if (collectionMode & InjectedScript.CollectionMode.OwnProperties && isOwnProperty) |
| return callback(descriptor); |
| |
| // Native Getter properties. |
| if (collectionMode & InjectedScript.CollectionMode.NativeGetterProperties) { |
| if (possibleNativeBindingGetter) |
| return callback(descriptor); |
| } |
| } |
| |
| function processProperty(o, propertyName, isOwnProperty) |
| { |
| if (nameProcessed.@has(propertyName)) |
| return InjectedScript.PropertyFetchAction.Continue; |
| |
| nameProcessed.@add(propertyName); |
| |
| let name = toString(propertyName); |
| let symbol = isSymbol(propertyName) ? propertyName : null; |
| |
| let descriptor = @Object.@getOwnPropertyDescriptor(o, propertyName); |
| if (!descriptor) { |
| // FIXME: Bad descriptor. Can we get here? |
| // Fall back to very restrictive settings. |
| let fakeDescriptor = createFakeValueDescriptor(name, symbol, descriptor, isOwnProperty); |
| return processDescriptor(fakeDescriptor, isOwnProperty); |
| } |
| |
| if (nativeGettersAsValues) { |
| if (@String(descriptor.get).@endsWith("[native code]\n}") || (!descriptor.get && @Object.@hasOwn(descriptor, "get") && !descriptor.set && @Object.@hasOwn(descriptor, "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); |
| return processDescriptor(fakeDescriptor, isOwnProperty, true); |
| } |
| } |
| |
| descriptor.name = name; |
| if (isOwnProperty) |
| descriptor.isOwn = true; |
| if (symbol) |
| descriptor.symbol = symbol; |
| return processDescriptor(descriptor, isOwnProperty); |
| } |
| |
| let isArrayLike = false; |
| try { |
| isArrayLike = RemoteObject.subtype(object) === "array" && @isFinite(object.length) && object.length > 0; |
| } catch { } |
| |
| for (let o = object; isDefined(o); o = @Object.@getPrototypeOf(o)) { |
| let isOwnProperty = o === object; |
| let shouldBreak = false; |
| |
| // FIXME: <https://webkit.org/b/201861> Web Inspector: show autocomplete entries for non-index properties on arrays |
| if (isArrayLike && isOwnProperty) { |
| for (let i = 0; i < o.length; ++i) { |
| if (!(i in o)) |
| continue; |
| |
| let result = processProperty(o, toString(i), isOwnProperty); |
| shouldBreak = result === InjectedScript.PropertyFetchAction.Stop; |
| if (shouldBreak) |
| break; |
| } |
| } else { |
| let propertyNames = @Object.@getOwnPropertyNames(o); |
| for (let i = 0; i < propertyNames.length; ++i) { |
| let result = processProperty(o, propertyNames[i], isOwnProperty); |
| shouldBreak = result === InjectedScript.PropertyFetchAction.Stop; |
| if (shouldBreak) |
| break; |
| } |
| } |
| |
| if (shouldBreak) |
| break; |
| |
| let propertySymbols = @Object.@getOwnPropertySymbols(o); |
| for (let i = 0; i < propertySymbols.length; ++i) { |
| let result = processProperty(o, propertySymbols[i], isOwnProperty); |
| shouldBreak = result === InjectedScript.PropertyFetchAction.Stop; |
| if (shouldBreak) |
| break; |
| } |
| |
| if (shouldBreak) |
| break; |
| |
| if (collectionMode === InjectedScript.CollectionMode.OwnProperties) |
| break; |
| } |
| |
| if (includeProto) { |
| try { |
| if (object.__proto__) |
| callback(@createObjectWithoutPrototype( |
| "name", "__proto__", |
| "value", object.__proto__, |
| "writable", true, |
| "configurable", true, |
| "isOwn", true, |
| )); |
| } catch { } |
| } |
| } |
| |
| _getSetEntries(object, fetchStart, fetchCount) |
| { |
| let entries = @createArrayWithoutPrototype(); |
| |
| for (let value of @builtinSetIterable(object)) { |
| if (fetchStart > 0) { |
| fetchStart--; |
| continue; |
| } |
| |
| @arrayPush(entries, @createObjectWithoutPrototype("value", value)); |
| |
| if (fetchCount && entries.length === fetchCount) |
| break; |
| } |
| |
| return entries; |
| } |
| |
| _getMapEntries(object, fetchStart, fetchCount) |
| { |
| let entries = @createArrayWithoutPrototype(); |
| |
| for (let [key, value] of @builtinMapIterable(object)) { |
| if (fetchStart > 0) { |
| fetchStart--; |
| continue; |
| } |
| |
| @arrayPush(entries, @createObjectWithoutPrototype( |
| "key", key, |
| "value", value, |
| )); |
| |
| if (fetchCount && entries.length === fetchCount) |
| break; |
| } |
| |
| return entries; |
| } |
| |
| _getWeakMapEntries(object, fetchCount) |
| { |
| return InjectedScriptHost.weakMapEntries(object, fetchCount); |
| } |
| |
| _getWeakSetEntries(object, fetchCount) |
| { |
| return InjectedScriptHost.weakSetEntries(object, fetchCount); |
| } |
| |
| _getIteratorEntries(object, fetchCount) |
| { |
| return InjectedScriptHost.iteratorEntries(object, fetchCount); |
| } |
| |
| _entries(object, subtype, fetchStart, fetchCount) |
| { |
| if (subtype === "set") |
| return this._getSetEntries(object, fetchStart, fetchCount); |
| if (subtype === "map") |
| return this._getMapEntries(object, fetchStart, fetchCount); |
| if (subtype === "weakmap") |
| return this._getWeakMapEntries(object, fetchCount); |
| if (subtype === "weakset") |
| return this._getWeakSetEntries(object, fetchCount); |
| if (subtype === "iterator") |
| return this._getIteratorEntries(object, fetchCount); |
| |
| throw "unexpected type"; |
| } |
| |
| _saveResult(result) |
| { |
| this._lastResult = result; |
| |
| if (result === @undefined || result === null) |
| return; |
| |
| let existingIndex = @Array.prototype.@indexOf.@call(this._savedResults, 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; |
| } |
| }; |
| |
| InjectedScript.CollectionMode = @createObjectWithoutPrototype( |
| "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.PropertyFetchAction = @createObjectWithoutPrototype( |
| "Continue", @createPrivateSymbol("continue"), |
| "Stop", @createPrivateSymbol("stop"), |
| ); |
| |
| 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) || isBigInt(object) || object === null || forceValueType) { |
| // We don't send undefined values over JSON. |
| // BigInt values are not JSON serializable. |
| if (this.type !== "undefined" && this.type !== "bigint") |
| 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.type === "bigint") |
| 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) { |
| inspectedGlobalObject.console.error(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) || isBigInt(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 { } |
| |
| return null; |
| } |
| |
| static describe(value) |
| { |
| if (isPrimitiveValue(value)) |
| return null; |
| |
| if (isBigInt(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" && @@toStringTag in value) |
| return value.@@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) |
| { |
| // FIXME: <webkit.org/b/239774> Injected script should use WebCore built-ins. |
| 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 = @createObjectWithoutPrototype( |
| "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 = @createArrayWithoutPrototype(); |
| } |
| } |
| |
| 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 = @createArrayWithoutPrototype(); |
| 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 = @createObjectWithoutPrototype( |
| "properties", isTableRowsRequest ? 1000 : max(5, firstLevelKeysCount), |
| "indexes", isTableRowsRequest ? 1000 : 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 = @createArrayWithoutPrototype(); |
| |
| // Internal Properties. |
| let internalPropertyDescriptors = injectedScript._internalPropertyDescriptors(object, true); |
| if (internalPropertyDescriptors) { |
| for (let i = 0; i < internalPropertyDescriptors.length; ++i) { |
| let result = this._appendPropertyPreview(object, preview, internalPropertyDescriptors[i], propertiesThreshold, firstLevelKeys, secondLevelKeys, @createObjectWithoutPrototype("internal", true)); |
| if (result === InjectedScript.PropertyFetchAction.Stop) |
| return preview; |
| } |
| } |
| |
| if (preview.entries) |
| return preview; |
| |
| // Properties. |
| injectedScript._forEachPropertyDescriptor(object, InjectedScript.CollectionMode.AllProperties, (descriptor) => { |
| return this._appendPropertyPreview(object, preview, descriptor, propertiesThreshold, firstLevelKeys, secondLevelKeys); |
| }, @createObjectWithoutPrototype( |
| "nativeGettersAsValues", true, |
| "includeProto", true, |
| )); |
| } catch { |
| preview.lossless = false; |
| } |
| |
| return preview; |
| } |
| |
| _appendPropertyPreview(object, preview, descriptor, propertiesThreshold, firstLevelKeys, secondLevelKeys, {internal} = @createObjectWithoutPrototype()) |
| { |
| // Error in descriptor. |
| if (descriptor.wasThrown) { |
| preview.lossless = false; |
| return InjectedScript.PropertyFetchAction.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; |
| return InjectedScript.PropertyFetchAction.Continue; |
| } |
| |
| // For arrays, only allow indexes. |
| if (this.subtype === "array" && !isUInt32(name)) |
| return InjectedScript.PropertyFetchAction.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))) |
| return InjectedScript.PropertyFetchAction.Continue; |
| |
| // If we have a filter, only show properties in the filter. |
| // FIXME: Currently these filters do nothing on the backend. |
| if (firstLevelKeys && !@Array.prototype.@includes.@call(firstLevelKeys, name)) |
| return InjectedScript.PropertyFetchAction.Continue; |
| |
| function appendPreview(property) { |
| 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 InjectedScript.PropertyFetchAction.Stop; |
| } |
| |
| if (internal) |
| property.internal = true; |
| |
| @arrayPush(preview.properties, property); |
| return InjectedScript.PropertyFetchAction.Continue; |
| } |
| |
| // Getter/setter. |
| if (!("value" in descriptor)) { |
| preview.lossless = false; |
| return appendPreview(@createObjectWithoutPrototype( |
| "name", name, |
| "type", "accessor", |
| )); |
| } |
| |
| // Null value. |
| let value = descriptor.value; |
| if (value === null) |
| return appendPreview(@createObjectWithoutPrototype( |
| "name", name, |
| "type", "object", |
| "subtype", "null", |
| "value", "null", |
| )); |
| |
| // Ignore non-enumerable functions. |
| let type = typeof value; |
| if (!descriptor.enumerable && type === "function") |
| return InjectedScript.PropertyFetchAction.Continue; |
| |
| // Fix type of document.all. |
| if (InjectedScriptHost.isHTMLAllCollection(value)) |
| type = "object"; |
| |
| // Primitive. |
| const maxLength = 100; |
| if (isPrimitiveValue(value) || isBigInt(value)) { |
| if (type === "string" && value.length > maxLength) { |
| value = this._abbreviateString(value, maxLength, true); |
| preview.lossless = false; |
| } |
| return appendPreview(@createObjectWithoutPrototype( |
| "name", name, |
| "type", type, |
| "value", toStringDescription(value), |
| )); |
| } |
| |
| // Symbol. |
| if (isSymbol(value)) { |
| let symbolString = toString(value); |
| if (symbolString.length > maxLength) { |
| symbolString = this._abbreviateString(symbolString, maxLength, true); |
| preview.lossless = false; |
| } |
| return appendPreview(@createObjectWithoutPrototype( |
| "name", name, |
| "type", type, |
| "value", symbolString, |
| )); |
| } |
| |
| // Object. |
| let property = @createObjectWithoutPrototype( |
| "name", name, |
| "type", 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; |
| } |
| |
| return appendPreview(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) { |
| @Array.prototype.@pop.@call(entries); |
| preview.overflow = true; |
| preview.lossless = false; |
| } |
| |
| function updateMainPreview(subPreview) { |
| if (!subPreview.lossless) |
| preview.lossless = false; |
| } |
| |
| preview.entries = @Array.prototype.@map.@call(entries, 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) || isBigInt(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 value properties. |
| let ownPropertyNames = @Object.@getOwnPropertyNames(object); |
| if (ownPropertyNames.length > 3) |
| return false; |
| for (let i = 0; i < ownPropertyNames.length; ++i) { |
| let propertyName = ownPropertyNames[i]; |
| let descriptor = @Object.@getOwnPropertyDescriptor(object, propertyName); |
| if (descriptor && !("value" in descriptor)) |
| return false; |
| 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 = class CallFrameProxy { |
| constructor(ordinal, callFrame) |
| { |
| this.callFrameId = `{"ordinal":${ordinal},"injectedScriptId":${injectedScriptId}}`; |
| this.functionName = callFrame.functionName; |
| |
| this.location = @createObjectWithoutPrototype( |
| "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; |
| } |
| |
| _wrapScopeChain(callFrame) |
| { |
| let scopeChain = callFrame.scopeChain; |
| let scopeDescriptions = callFrame.scopeDescriptions(); |
| |
| let scopeChainProxy = @createArrayWithoutPrototype(); |
| for (let i = 0; i < scopeChain.length; i++) |
| scopeChainProxy[i] = InjectedScript.CallFrameProxy._createScopeJson(scopeChain[i], scopeDescriptions[i], "backtrace"); |
| return scopeChainProxy; |
| } |
| |
| static _createScopeJson(object, {name, type, location}, groupId) |
| { |
| let scope = @createObjectWithoutPrototype( |
| "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; |
| } |
| }; |
| |
| InjectedScript.CallFrameProxy._scopeTypeNames = @createObjectWithoutPrototype( |
| 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 |
| 6, "nestedLexical", // NESTED_LEXICAL_SCOPE |
| ); |
| |
| // ------- |
| |
| function CommandLineAPI(callFrame) |
| { |
| let savedResultAlias = InjectedScriptHost.savedResultAlias; |
| |
| let defineGetter = (key, value, wrap) => { |
| if (wrap) { |
| let originalValue = value; |
| value = function() { return originalValue; }; |
| } |
| |
| @Object.@defineProperty(this, "$" + key, @createObjectWithoutPrototype("get", value)); |
| if (savedResultAlias && savedResultAlias !== "$") |
| @Object.@defineProperty(this, savedResultAlias + key, @createObjectWithoutPrototype("get", value)); |
| }; |
| |
| if ("_lastResult" in injectedScript) |
| defineGetter("_", injectedScript._lastResult, true); |
| |
| if ("_exceptionValue" in injectedScript) |
| defineGetter("exception", injectedScript._exceptionValue, true); |
| |
| if ("_eventValue" in injectedScript) |
| defineGetter("event", injectedScript._eventValue, true); |
| |
| // $1-$99 |
| for (let i = 1; i < injectedScript._savedResults.length; ++i) |
| defineGetter(i, injectedScript._savedResults[i], true); |
| |
| for (let name in CommandLineAPI.getters) |
| defineGetter(name, CommandLineAPI.getters[name]); |
| |
| for (let name in CommandLineAPI.methods) |
| this[name] = CommandLineAPI.methods[name]; |
| } |
| |
| CommandLineAPI.getters = @createObjectWithoutPrototype(); |
| |
| CommandLineAPI.methods = @createObjectWithoutPrototype(); |
| |
| CommandLineAPI.methods["keys"] = function(object) { return @Object.@keys(object); }; |
| CommandLineAPI.methods["values"] = function(object) { return @Object.@values(object); }; |
| |
| CommandLineAPI.methods["queryInstances"] = function() { return InjectedScriptHost.queryInstances(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["queryObjects"] = function() { return InjectedScriptHost.queryInstances(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["queryHolders"] = function() { return InjectedScriptHost.queryHolders(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| |
| CommandLineAPI.methods["inspect"] = function(object) { return injectedScript.inspectObject(object); }; |
| |
| CommandLineAPI.methods["assert"] = function() { return inspectedGlobalObject.console.assert(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["clear"] = function() { return inspectedGlobalObject.console.clear(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["count"] = function() { return inspectedGlobalObject.console.count(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["countReset"] = function() { return inspectedGlobalObject.console.countReset(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["debug"] = function() { return inspectedGlobalObject.console.debug(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["dir"] = function() { return inspectedGlobalObject.console.dir(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["dirxml"] = function() { return inspectedGlobalObject.console.dirxml(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["error"] = function() { return inspectedGlobalObject.console.error(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["group"] = function() { return inspectedGlobalObject.console.group(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["groupCollapsed"] = function() { return inspectedGlobalObject.console.groupCollapsed(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["groupEnd"] = function() { return inspectedGlobalObject.console.groupEnd(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["info"] = function() { return inspectedGlobalObject.console.info(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["log"] = function() { return inspectedGlobalObject.console.log(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["profile"] = function() { return inspectedGlobalObject.console.profile(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["profileEnd"] = function() { return inspectedGlobalObject.console.profileEnd(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["record"] = function() { return inspectedGlobalObject.console.record(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["recordEnd"] = function() { return inspectedGlobalObject.console.recordEnd(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["screenshot"] = function() { return inspectedGlobalObject.console.screenshot(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["table"] = function() { return inspectedGlobalObject.console.table(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["takeHeapSnapshot"] = function() { return inspectedGlobalObject.console.takeHeapSnapshot(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["time"] = function() { return inspectedGlobalObject.console.time(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["timeEnd"] = function() { return inspectedGlobalObject.console.timeEnd(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["timeLog"] = function() { return inspectedGlobalObject.console.timeLog(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["timeStamp"] = function() { return inspectedGlobalObject.console.timeStamp(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["trace"] = function() { return inspectedGlobalObject.console.trace(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| CommandLineAPI.methods["warn"] = function() { return inspectedGlobalObject.console.warn(...createIterableWithoutPrototypeFromArguments(arguments)); }; |
| |
| for (let name in CommandLineAPI.methods) |
| CommandLineAPI.methods[name].toString = function() { return "function " + name + "() { [Command Line API] }"; }; |
| |
| return injectedScript; |
| } |