| /* |
| * Copyright (C) 2009 Google Inc. All rights reserved. |
| * Copyright (C) 2015 Apple 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: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * 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. |
| * * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT |
| * OWNER OR 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. |
| */ |
| |
| WI.RemoteObject = class RemoteObject |
| { |
| constructor(target, objectId, type, subtype, value, description, size, classPrototype, className, preview) |
| { |
| console.assert(type); |
| console.assert(!preview || preview instanceof WI.ObjectPreview); |
| console.assert(!target || target instanceof WI.Target); |
| |
| this._target = target || WI.mainTarget; |
| this._type = type; |
| this._subtype = subtype; |
| |
| if (objectId) { |
| // Object, Function, or Symbol. |
| console.assert(!subtype || typeof subtype === "string"); |
| console.assert(!description || typeof description === "string"); |
| console.assert(!value); |
| |
| this._objectId = objectId; |
| this._description = description || ""; |
| this._hasChildren = type !== "symbol"; |
| this._size = size; |
| this._classPrototype = classPrototype; |
| this._preview = preview; |
| |
| if (subtype === "class") { |
| this._functionDescription = this._description; |
| this._description = "class " + className; |
| } |
| } else { |
| // Primitive, BigInt, or null. |
| console.assert(type !== "object" || value === null); |
| console.assert(!preview); |
| |
| this._description = description || (value + ""); |
| this._hasChildren = false; |
| this._value = value; |
| |
| if (type === "bigint") { |
| console.assert(value === undefined); |
| console.assert(description.endsWith("n")); |
| if (window.BigInt) |
| this._value = BigInt(description.substring(0, description.length - 1)); |
| else |
| this._value = `${description} [BigInt Not Enabled in Web Inspector]`; |
| } |
| } |
| } |
| |
| // Static |
| |
| static createFakeRemoteObject() |
| { |
| return new WI.RemoteObject(undefined, WI.RemoteObject.FakeRemoteObjectId, "object"); |
| } |
| |
| static fromPrimitiveValue(value) |
| { |
| return new WI.RemoteObject(undefined, undefined, typeof value, undefined, value, undefined, undefined, undefined, undefined); |
| } |
| |
| static createBigIntFromDescriptionString(description) |
| { |
| console.assert(description.endsWith("n")); |
| |
| return new WI.RemoteObject(undefined, undefined, "bigint", undefined, undefined, description, undefined, undefined, undefined); |
| } |
| |
| static fromPayload(payload, target) |
| { |
| console.assert(typeof payload === "object", "Remote object payload should only be an object"); |
| |
| if (payload.subtype === "array") { |
| // COMPATIBILITY (iOS 8): Runtime.RemoteObject did not have size property, |
| // instead it was tacked onto the end of the description, like "Array[#]". |
| var match = payload.description.match(/\[(\d+)\]$/); |
| if (match) { |
| payload.size = parseInt(match[1]); |
| payload.description = payload.description.replace(/\[\d+\]$/, ""); |
| } |
| } |
| |
| if (payload.classPrototype) |
| payload.classPrototype = WI.RemoteObject.fromPayload(payload.classPrototype, target); |
| |
| if (payload.preview) { |
| // COMPATIBILITY (iOS 8): Did not have type/subtype/description on |
| // Runtime.ObjectPreview. Copy them over from the RemoteObject. |
| if (!payload.preview.type) { |
| payload.preview.type = payload.type; |
| payload.preview.subtype = payload.subtype; |
| payload.preview.description = payload.description; |
| payload.preview.size = payload.size; |
| } |
| |
| payload.preview = WI.ObjectPreview.fromPayload(payload.preview); |
| } |
| |
| return new WI.RemoteObject(target, payload.objectId, payload.type, payload.subtype, payload.value, payload.description, payload.size, payload.classPrototype, payload.className, payload.preview); |
| } |
| |
| static createCallArgument(valueOrObject) |
| { |
| if (valueOrObject instanceof WI.RemoteObject) { |
| if (valueOrObject.objectId) |
| return {objectId: valueOrObject.objectId}; |
| return {value: valueOrObject.value}; |
| } |
| |
| return {value: valueOrObject}; |
| } |
| |
| static resolveNode(node, objectGroup) |
| { |
| return DOMAgent.resolveNode(node.id, objectGroup) |
| .then(({object}) => WI.RemoteObject.fromPayload(object, WI.mainTarget)); |
| } |
| |
| static resolveWebSocket(webSocketResource, objectGroup, callback) |
| { |
| console.assert(typeof callback === "function"); |
| |
| NetworkAgent.resolveWebSocket(webSocketResource.requestIdentifier, objectGroup, (error, object) => { |
| if (error || !object) |
| callback(null); |
| else |
| callback(WI.RemoteObject.fromPayload(object, webSocketResource.target)); |
| }); |
| } |
| |
| static resolveCanvasContext(canvas, objectGroup, callback) |
| { |
| console.assert(typeof callback === "function"); |
| |
| function wrapCallback(error, object) { |
| if (error || !object) |
| callback(null); |
| else |
| callback(WI.RemoteObject.fromPayload(object, WI.mainTarget)); |
| } |
| |
| // COMPATIBILITY (iOS 13): Canvas.resolveCanvasContext was renamed to Canvas.resolveContext. |
| if (!CanvasAgent.resolveContext) { |
| CanvasAgent.resolveCanvasContext(canvas.identifier, objectGroup, wrapCallback); |
| return; |
| } |
| |
| CanvasAgent.resolveContext(canvas.identifier, objectGroup, wrapCallback); |
| } |
| |
| // Public |
| |
| get target() |
| { |
| return this._target; |
| } |
| |
| get objectId() |
| { |
| return this._objectId; |
| } |
| |
| get type() |
| { |
| return this._type; |
| } |
| |
| get subtype() |
| { |
| return this._subtype; |
| } |
| |
| get description() |
| { |
| return this._description; |
| } |
| |
| get functionDescription() |
| { |
| console.assert(this.type === "function"); |
| |
| return this._functionDescription || this._description; |
| } |
| |
| get hasChildren() |
| { |
| return this._hasChildren; |
| } |
| |
| get value() |
| { |
| return this._value; |
| } |
| |
| get size() |
| { |
| return this._size || 0; |
| } |
| |
| get classPrototype() |
| { |
| return this._classPrototype; |
| } |
| |
| get preview() |
| { |
| return this._preview; |
| } |
| |
| hasSize() |
| { |
| return this.isArray() || this.isCollectionType(); |
| } |
| |
| hasValue() |
| { |
| return "_value" in this; |
| } |
| |
| canLoadPreview() |
| { |
| if (this._failedToLoadPreview) |
| return false; |
| |
| if (this._type !== "object") |
| return false; |
| |
| if (!this._objectId || this._isSymbol() || this._isFakeObject()) |
| return false; |
| |
| return true; |
| } |
| |
| updatePreview(callback) |
| { |
| if (!this.canLoadPreview()) { |
| callback(null); |
| return; |
| } |
| |
| if (!this._target.RuntimeAgent.getPreview) { |
| this._failedToLoadPreview = true; |
| callback(null); |
| return; |
| } |
| |
| this._target.RuntimeAgent.getPreview(this._objectId, (error, payload) => { |
| if (error) { |
| this._failedToLoadPreview = true; |
| callback(null); |
| return; |
| } |
| |
| this._preview = WI.ObjectPreview.fromPayload(payload); |
| callback(this._preview); |
| }); |
| } |
| |
| getPropertyDescriptors(callback, options = {}) |
| { |
| if (!this._objectId || this._isSymbol() || this._isFakeObject()) { |
| callback([]); |
| return; |
| } |
| |
| this._getProperties(this._getPropertyDescriptorsResolver.bind(this, callback), options); |
| } |
| |
| getDisplayablePropertyDescriptors(callback, options = {}) |
| { |
| if (!this._objectId || this._isSymbol() || this._isFakeObject()) { |
| callback([]); |
| return; |
| } |
| |
| // COMPATIBILITY (iOS 8): RuntimeAgent.getDisplayableProperties did not exist. |
| // Here we do our best to reimplement it by getting all properties and reducing them down. |
| if (!this._target.RuntimeAgent.getDisplayableProperties) { |
| this._getProperties(options, (error, allProperties) => { |
| var ownOrGetterPropertiesList = []; |
| if (allProperties) { |
| for (var property of allProperties) { |
| if (property.isOwn || property.name === "__proto__") { |
| // Own property or getter property in prototype chain. |
| ownOrGetterPropertiesList.push(property); |
| } else if (property.value && property.name !== property.name.toUpperCase()) { |
| var type = property.value.type; |
| if (type && type !== "function" && property.name !== "constructor") { |
| // Possible native binding getter property converted to a value. Also, no CONSTANT name style and not "constructor". |
| // There is no way of knowing if this is native or not, so just go with it. |
| ownOrGetterPropertiesList.push(property); |
| } |
| } |
| } |
| } |
| this._getPropertyDescriptorsResolver(callback, error, ownOrGetterPropertiesList); |
| }); |
| return; |
| } |
| |
| this._getDisplayableProperties(this._getPropertyDescriptorsResolver.bind(this, callback), options); |
| } |
| |
| setPropertyValue(name, value, callback) |
| { |
| if (!this._objectId || this._isSymbol() || this._isFakeObject()) { |
| callback("Can't set a property of non-object."); |
| return; |
| } |
| |
| // FIXME: It doesn't look like setPropertyValue is used yet. This will need to be tested when it is again (editable ObjectTrees). |
| this._target.RuntimeAgent.evaluate.invoke({expression: appendWebInspectorSourceURL(value), doNotPauseOnExceptionsAndMuteConsole: true}, evaluatedCallback.bind(this), this._target.RuntimeAgent); |
| |
| function evaluatedCallback(error, result, wasThrown) |
| { |
| if (error || wasThrown) { |
| callback(error || result.description); |
| return; |
| } |
| |
| function setPropertyValue(propertyName, propertyValue) |
| { |
| this[propertyName] = propertyValue; |
| } |
| |
| delete result.description; // Optimize on traffic. |
| |
| this._target.RuntimeAgent.callFunctionOn(this._objectId, appendWebInspectorSourceURL(setPropertyValue.toString()), [{value: name}, result], true, undefined, propertySetCallback.bind(this)); |
| |
| if (result._objectId) |
| this._target.RuntimeAgent.releaseObject(result._objectId); |
| } |
| |
| function propertySetCallback(error, result, wasThrown) |
| { |
| if (error || wasThrown) { |
| callback(error || result.description); |
| return; |
| } |
| |
| callback(); |
| } |
| } |
| |
| isUndefined() |
| { |
| return this._type === "undefined"; |
| } |
| |
| isNode() |
| { |
| return this._subtype === "node"; |
| } |
| |
| isArray() |
| { |
| return this._subtype === "array"; |
| } |
| |
| isClass() |
| { |
| return this._subtype === "class"; |
| } |
| |
| isCollectionType() |
| { |
| return this._subtype === "map" || this._subtype === "set" || this._subtype === "weakmap" || this._subtype === "weakset"; |
| } |
| |
| isWeakCollection() |
| { |
| return this._subtype === "weakmap" || this._subtype === "weakset"; |
| } |
| |
| getCollectionEntries(callback, {fetchStart, fetchCount} = {}) |
| { |
| console.assert(this.isCollectionType()); |
| console.assert(typeof fetchStart === "undefined" || (typeof fetchStart === "number" && fetchStart >= 0), fetchStart); |
| console.assert(typeof fetchCount === "undefined" || (typeof fetchCount === "number" && fetchCount > 0), fetchCount); |
| |
| // WeakMaps and WeakSets are not ordered. We should never send a non-zero start. |
| console.assert(!this.isWeakCollection() || typeof fetchStart === "undefined" || fetchStart === 0, fetchStart); |
| |
| let objectGroup = this.isWeakCollection() ? this._weakCollectionObjectGroup() : ""; |
| |
| // COMPATIBILITY (iOS 13): `startIndex` and `numberToFetch` were renamed to `fetchStart` and `fetchCount` (but kept in the same position). |
| this._target.RuntimeAgent.getCollectionEntries(this._objectId, objectGroup, fetchStart, fetchCount, (error, entries) => { |
| callback(entries.map((x) => WI.CollectionEntry.fromPayload(x, this._target))); |
| }); |
| } |
| |
| releaseWeakCollectionEntries() |
| { |
| console.assert(this.isWeakCollection()); |
| |
| this._target.RuntimeAgent.releaseObjectGroup(this._weakCollectionObjectGroup()); |
| } |
| |
| pushNodeToFrontend(callback) |
| { |
| if (this._objectId) |
| WI.domManager.pushNodeToFrontend(this._objectId, callback); |
| else |
| callback(0); |
| } |
| |
| async fetchProperties(propertyNames, resultObject = {}) |
| { |
| let seenPropertyNames = new Set; |
| let requestedValues = []; |
| for (let propertyName of propertyNames) { |
| // Check this here, otherwise things like '{}' would be valid Set keys. |
| if (typeof propertyName !== "string" && typeof propertyName !== "number") |
| throw new Error(`Tried to get property using key is not a string or number: ${propertyName}`); |
| |
| if (seenPropertyNames.has(propertyName)) |
| continue; |
| |
| seenPropertyNames.add(propertyName); |
| requestedValues.push(this.getProperty(propertyName)); |
| } |
| |
| // Return primitive values directly, otherwise return a WI.RemoteObject instance. |
| function maybeUnwrapValue(remoteObject) { |
| return remoteObject.hasValue() ? remoteObject.value : remoteObject; |
| } |
| |
| // Request property values one by one, since returning an array of property |
| // values would then be subject to arbitrary object preview size limits. |
| let fetchedKeys = Array.from(seenPropertyNames); |
| let fetchedValues = await Promise.all(requestedValues); |
| for (let i = 0; i < fetchedKeys.length; ++i) |
| resultObject[fetchedKeys[i]] = maybeUnwrapValue(fetchedValues[i]); |
| |
| return resultObject; |
| } |
| |
| getProperty(propertyName, callback = null) |
| { |
| function inspectedPage_object_getProperty(property) { |
| if (typeof property !== "string" && typeof property !== "number") |
| throw new Error(`Tried to get property using key is not a string or number: ${property}`); |
| |
| return this[property]; |
| } |
| |
| if (callback && typeof callback === "function") |
| this.callFunction(inspectedPage_object_getProperty, [propertyName], true, callback); |
| else |
| return this.callFunction(inspectedPage_object_getProperty, [propertyName], true); |
| } |
| |
| callFunction(functionDeclaration, args, generatePreview, callback = null) |
| { |
| let translateResult = (result) => result ? WI.RemoteObject.fromPayload(result, this._target) : null; |
| |
| if (args) |
| args = args.map(WI.RemoteObject.createCallArgument); |
| |
| if (callback && typeof callback === "function") { |
| this._target.RuntimeAgent.callFunctionOn(this._objectId, appendWebInspectorSourceURL(functionDeclaration.toString()), args, true, undefined, !!generatePreview, (error, result, wasThrown) => { |
| callback(error, translateResult(result), wasThrown); |
| }); |
| } else { |
| // Protocol errors and results that were thrown should cause promise rejection with the same. |
| return this._target.RuntimeAgent.callFunctionOn(this._objectId, appendWebInspectorSourceURL(functionDeclaration.toString()), args, true, undefined, !!generatePreview) |
| .then(({result, wasThrown}) => { |
| result = translateResult(result); |
| if (result && wasThrown) |
| return Promise.reject(result); |
| return Promise.resolve(result); |
| }); |
| } |
| } |
| |
| callFunctionJSON(functionDeclaration, args, callback) |
| { |
| function mycallback(error, result, wasThrown) |
| { |
| callback((error || wasThrown) ? null : result.value); |
| } |
| |
| this._target.RuntimeAgent.callFunctionOn(this._objectId, appendWebInspectorSourceURL(functionDeclaration.toString()), args, true, true, mycallback); |
| } |
| |
| invokeGetter(getterRemoteObject, callback) |
| { |
| console.assert(getterRemoteObject instanceof WI.RemoteObject); |
| |
| function backendInvokeGetter(getter) |
| { |
| return getter ? getter.call(this) : undefined; |
| } |
| |
| this.callFunction(backendInvokeGetter, [getterRemoteObject], true, callback); |
| } |
| |
| getOwnPropertyDescriptor(propertyName, callback) |
| { |
| function backendGetOwnPropertyDescriptor(propertyName) |
| { |
| return this[propertyName]; |
| } |
| |
| function wrappedCallback(error, result, wasThrown) |
| { |
| if (error || wasThrown || !(result instanceof WI.RemoteObject)) { |
| callback(null); |
| return; |
| } |
| |
| var fakeDescriptor = {name: propertyName, value: result, writable: true, configurable: true}; |
| var fakePropertyDescriptor = new WI.PropertyDescriptor(fakeDescriptor, null, true, false, false, false); |
| callback(fakePropertyDescriptor); |
| } |
| |
| // FIXME: Implement a real RuntimeAgent.getOwnPropertyDescriptor? |
| this.callFunction(backendGetOwnPropertyDescriptor, [propertyName], false, wrappedCallback.bind(this)); |
| } |
| |
| release() |
| { |
| if (this._objectId && !this._isFakeObject()) |
| this._target.RuntimeAgent.releaseObject(this._objectId); |
| } |
| |
| arrayLength() |
| { |
| if (this._subtype !== "array") |
| return 0; |
| |
| var matches = this._description.match(/\[([0-9]+)\]/); |
| if (!matches) |
| return 0; |
| |
| return parseInt(matches[1], 10); |
| } |
| |
| asCallArgument() |
| { |
| return WI.RemoteObject.createCallArgument(this); |
| } |
| |
| findFunctionSourceCodeLocation() |
| { |
| var result = new WI.WrappedPromise; |
| |
| if (!this._isFunction() || !this._objectId) { |
| result.resolve(WI.RemoteObject.SourceCodeLocationPromise.MissingObjectId); |
| return result.promise; |
| } |
| |
| this._target.DebuggerAgent.getFunctionDetails(this._objectId, (error, response) => { |
| if (error) { |
| result.resolve(WI.RemoteObject.SourceCodeLocationPromise.NoSourceFound); |
| return; |
| } |
| |
| var location = response.location; |
| var sourceCode = WI.debuggerManager.scriptForIdentifier(location.scriptId, this._target); |
| |
| if (!sourceCode || ((!WI.isEngineeringBuild || !WI.settings.engineeringShowInternalScripts.value) && isWebKitInternalScript(sourceCode.sourceURL))) { |
| result.resolve(WI.RemoteObject.SourceCodeLocationPromise.NoSourceFound); |
| return; |
| } |
| |
| var sourceCodeLocation = sourceCode.createSourceCodeLocation(location.lineNumber, location.columnNumber || 0); |
| result.resolve(sourceCodeLocation); |
| }); |
| |
| return result.promise; |
| } |
| |
| // Private |
| |
| _isFakeObject() |
| { |
| return this._objectId === WI.RemoteObject.FakeRemoteObjectId; |
| } |
| |
| _isSymbol() |
| { |
| return this._type === "symbol"; |
| } |
| |
| _isFunction() |
| { |
| return this._type === "function"; |
| } |
| |
| _weakCollectionObjectGroup() |
| { |
| return JSON.stringify(this._objectId) + "-" + this._subtype; |
| } |
| |
| _getProperties(callback, {ownProperties, fetchStart, fetchCount, generatePreview} = {}) |
| { |
| // COMPATIBILITY (iOS 13): `result` was renamed to `properties` (but kept in the same position). |
| this._target.RuntimeAgent.getProperties.invoke({ |
| objectId: this._objectId, |
| ownProperties, |
| fetchStart, |
| fetchCount, |
| generatePreview, |
| }, callback, this._target.RuntimeAgent); |
| } |
| |
| _getDisplayableProperties(callback, {fetchStart, fetchCount, generatePreview} = {}) |
| { |
| console.assert(this._target.RuntimeAgent.getDisplayableProperties); |
| |
| this._target.RuntimeAgent.getDisplayableProperties.invoke({ |
| objectId: this._objectId, |
| fetchStart, |
| fetchCount, |
| generatePreview, |
| }, callback, this._target.RuntimeAgent); |
| } |
| |
| _getPropertyDescriptorsResolver(callback, error, properties, internalProperties) |
| { |
| if (error) { |
| callback(null); |
| return; |
| } |
| |
| let descriptors = properties.map((payload) => WI.PropertyDescriptor.fromPayload(payload, false, this._target)); |
| |
| if (internalProperties) { |
| for (let payload of internalProperties) |
| descriptors.push(WI.PropertyDescriptor.fromPayload(payload, true, this._target)); |
| } |
| |
| callback(descriptors); |
| } |
| }; |
| |
| WI.RemoteObject.FakeRemoteObjectId = "fake-remote-object"; |
| |
| WI.RemoteObject.SourceCodeLocationPromise = { |
| NoSourceFound: "remote-object-source-code-location-promise-no-source-found", |
| MissingObjectId: "remote-object-source-code-location-promise-missing-object-id" |
| }; |