| /* | |
| * Copyright (C) 2009 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: | |
| * | |
| * * 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. | |
| */ | |
| export default class RemoteObject { | |
| /** | |
| * This may not be an interface due to "instanceof SDK.RemoteObject" checks in the code. | |
| */ | |
| /** | |
| * @param {*} value | |
| * @return {!RemoteObject} | |
| */ | |
| static fromLocalObject(value) { | |
| return new LocalJSONObject(value); | |
| } | |
| /** | |
| * @param {!RemoteObject} remoteObject | |
| * @return {string} | |
| */ | |
| static type(remoteObject) { | |
| if (remoteObject === null) { | |
| return 'null'; | |
| } | |
| const type = typeof remoteObject; | |
| if (type !== 'object' && type !== 'function') { | |
| return type; | |
| } | |
| return remoteObject.type; | |
| } | |
| /** | |
| * @param {string} description | |
| * @return {string} | |
| */ | |
| static arrayNameFromDescription(description) { | |
| return description.replace(_descriptionLengthParenRegex, '').replace(_descriptionLengthSquareRegex, ''); | |
| } | |
| /** | |
| * @param {!RemoteObject|!Protocol.Runtime.RemoteObject|!Protocol.Runtime.ObjectPreview} object | |
| * @return {number} | |
| */ | |
| static arrayLength(object) { | |
| if (object.subtype !== 'array' && object.subtype !== 'typedarray') { | |
| return 0; | |
| } | |
| // Array lengths in V8-generated descriptions switched from square brackets to parentheses. | |
| // Both formats are checked in case the front end is dealing with an old version of V8. | |
| const parenMatches = object.description.match(_descriptionLengthParenRegex); | |
| const squareMatches = object.description.match(_descriptionLengthSquareRegex); | |
| return parenMatches ? parseInt(parenMatches[1], 10) : (squareMatches ? parseInt(squareMatches[1], 10) : 0); | |
| } | |
| /** | |
| * @param {*} object | |
| * @return {?string} | |
| */ | |
| static unserializableDescription(object) { | |
| const type = typeof object; | |
| if (type === 'number') { | |
| const description = String(object); | |
| if (object === 0 && 1 / object < 0) { | |
| return UnserializableNumber.Negative0; | |
| } | |
| if (description === UnserializableNumber.NaN || description === UnserializableNumber.Infinity || | |
| description === UnserializableNumber.NegativeInfinity) { | |
| return description; | |
| } | |
| } | |
| if (type === 'bigint') { | |
| return object + 'n'; | |
| } | |
| return null; | |
| } | |
| /** | |
| * @param {!Protocol.Runtime.RemoteObject|!RemoteObject|number|string|boolean|undefined|null|bigint} object | |
| * @return {!Protocol.Runtime.CallArgument} | |
| */ | |
| static toCallArgument(object) { | |
| const type = typeof object; | |
| if (type === 'undefined') { | |
| return {}; | |
| } | |
| const unserializableDescription = RemoteObject.unserializableDescription(object); | |
| if (type === 'number') { | |
| if (unserializableDescription !== null) { | |
| return {unserializableValue: unserializableDescription}; | |
| } | |
| return {value: object}; | |
| } | |
| if (type === 'bigint') { | |
| return {unserializableValue: /** @type {!Protocol.Runtime.UnserializableValue} */ (unserializableDescription)}; | |
| } | |
| if (type === 'string' || type === 'boolean') { | |
| return {value: object}; | |
| } | |
| if (!object) { | |
| return {value: null}; | |
| } | |
| // The unserializableValue is a function on RemoteObject's and a simple property on | |
| // Protocol.Runtime.RemoteObject's. | |
| if (object instanceof RemoteObject) { | |
| const unserializableValue = object.unserializableValue(); | |
| if (unserializableValue !== undefined) { | |
| return {unserializableValue: unserializableValue}; | |
| } | |
| } else if (object.unserializableValue !== undefined) { | |
| return {unserializableValue: object.unserializableValue}; | |
| } | |
| if (typeof object.objectId !== 'undefined') { | |
| return {objectId: object.objectId}; | |
| } | |
| return {value: object.value}; | |
| } | |
| /** | |
| * @param {!RemoteObject} object | |
| * @param {boolean} generatePreview | |
| * @return {!Promise<!SDK.GetPropertiesResult>} | |
| */ | |
| static async loadFromObjectPerProto(object, generatePreview) { | |
| const result = await Promise.all([ | |
| object.getAllProperties(true /* accessorPropertiesOnly */, generatePreview), | |
| object.getOwnProperties(generatePreview) | |
| ]); | |
| const accessorProperties = result[0].properties; | |
| const ownProperties = result[1].properties; | |
| const internalProperties = result[1].internalProperties; | |
| if (!ownProperties || !accessorProperties) { | |
| return /** @type {!SDK.GetPropertiesResult} */ ({properties: null, internalProperties: null}); | |
| } | |
| const propertiesMap = new Map(); | |
| const propertySymbols = []; | |
| for (let i = 0; i < accessorProperties.length; i++) { | |
| const property = accessorProperties[i]; | |
| if (property.symbol) { | |
| propertySymbols.push(property); | |
| } else { | |
| propertiesMap.set(property.name, property); | |
| } | |
| } | |
| for (let i = 0; i < ownProperties.length; i++) { | |
| const property = ownProperties[i]; | |
| if (property.isAccessorProperty()) { | |
| continue; | |
| } | |
| if (property.symbol) { | |
| propertySymbols.push(property); | |
| } else { | |
| propertiesMap.set(property.name, property); | |
| } | |
| } | |
| return { | |
| properties: propertiesMap.valuesArray().concat(propertySymbols), | |
| internalProperties: internalProperties ? internalProperties : null | |
| }; | |
| } | |
| /** | |
| * @return {?Protocol.Runtime.CustomPreview} | |
| */ | |
| customPreview() { | |
| return null; | |
| } | |
| /** @return {!Protocol.Runtime.RemoteObjectId|undefined} */ | |
| get objectId() { | |
| return 'Not implemented'; | |
| } | |
| /** @return {string} */ | |
| get type() { | |
| throw 'Not implemented'; | |
| } | |
| /** @return {string|undefined} */ | |
| get subtype() { | |
| throw 'Not implemented'; | |
| } | |
| /** @return {*} */ | |
| get value() { | |
| throw 'Not implemented'; | |
| } | |
| /** @return {string|undefined} */ | |
| unserializableValue() { | |
| throw 'Not implemented'; | |
| } | |
| /** @return {string|undefined} */ | |
| get description() { | |
| throw 'Not implemented'; | |
| } | |
| /** @return {boolean} */ | |
| get hasChildren() { | |
| throw 'Not implemented'; | |
| } | |
| /** | |
| * @return {!Protocol.Runtime.ObjectPreview|undefined} | |
| */ | |
| get preview() { | |
| return undefined; | |
| } | |
| /** | |
| * @return {?string} | |
| */ | |
| get className() { | |
| return null; | |
| } | |
| /** | |
| * @return {number} | |
| */ | |
| arrayLength() { | |
| throw 'Not implemented'; | |
| } | |
| /** | |
| * @param {boolean} generatePreview | |
| * @return {!Promise<!SDK.GetPropertiesResult>} | |
| */ | |
| getOwnProperties(generatePreview) { | |
| throw 'Not implemented'; | |
| } | |
| /** | |
| * @param {boolean} accessorPropertiesOnly | |
| * @param {boolean} generatePreview | |
| * @return {!Promise<!SDK.GetPropertiesResult>} | |
| */ | |
| getAllProperties(accessorPropertiesOnly, generatePreview) { | |
| throw 'Not implemented'; | |
| } | |
| /** | |
| * @param {!Protocol.Runtime.CallArgument} name | |
| * @return {!Promise<string|undefined>} | |
| */ | |
| async deleteProperty(name) { | |
| throw 'Not implemented'; | |
| } | |
| /** | |
| * @param {string|!Protocol.Runtime.CallArgument} name | |
| * @param {string} value | |
| * @return {!Promise<string|undefined>} | |
| */ | |
| async setPropertyValue(name, value) { | |
| throw 'Not implemented'; | |
| } | |
| /** | |
| * @param {function(this:Object, ...)} functionDeclaration | |
| * @param {!Array<!Protocol.Runtime.CallArgument>=} args | |
| * @return {!Promise<!SDK.CallFunctionResult>} | |
| */ | |
| callFunction(functionDeclaration, args) { | |
| throw 'Not implemented'; | |
| } | |
| /** | |
| * @param {function(this:Object, ...):T} functionDeclaration | |
| * @param {!Array<!Protocol.Runtime.CallArgument>|undefined} args | |
| * @return {!Promise<T>} | |
| * @template T | |
| */ | |
| callFunctionJSON(functionDeclaration, args) { | |
| throw 'Not implemented'; | |
| } | |
| release() { | |
| } | |
| /** | |
| * @return {!SDK.DebuggerModel} | |
| */ | |
| debuggerModel() { | |
| throw new Error('DebuggerModel-less object'); | |
| } | |
| /** | |
| * @return {!SDK.RuntimeModel} | |
| */ | |
| runtimeModel() { | |
| throw new Error('RuntimeModel-less object'); | |
| } | |
| /** | |
| * @return {boolean} | |
| */ | |
| isNode() { | |
| return false; | |
| } | |
| } | |
| export class RemoteObjectImpl extends RemoteObject { | |
| /** | |
| * @param {!SDK.RuntimeModel} runtimeModel | |
| * @param {string|undefined} objectId | |
| * @param {string} type | |
| * @param {string|undefined} subtype | |
| * @param {*} value | |
| * @param {!Protocol.Runtime.UnserializableValue=} unserializableValue | |
| * @param {string=} description | |
| * @param {!Protocol.Runtime.ObjectPreview=} preview | |
| * @param {!Protocol.Runtime.CustomPreview=} customPreview | |
| * @param {string=} className | |
| */ | |
| constructor( | |
| runtimeModel, objectId, type, subtype, value, unserializableValue, description, preview, customPreview, | |
| className) { | |
| super(); | |
| this._runtimeModel = runtimeModel; | |
| this._runtimeAgent = runtimeModel.target().runtimeAgent(); | |
| this._type = type; | |
| this._subtype = subtype; | |
| if (objectId) { | |
| // handle | |
| this._objectId = objectId; | |
| this._description = description; | |
| this._hasChildren = (type !== 'symbol'); | |
| this._preview = preview; | |
| } else { | |
| this._description = description; | |
| if (!this.description && unserializableValue) { | |
| this._description = unserializableValue; | |
| } | |
| if (!this._description && (typeof value !== 'object' || value === null)) { | |
| this._description = value + ''; | |
| } | |
| this._hasChildren = false; | |
| if (typeof unserializableValue === 'string') { | |
| this._unserializableValue = unserializableValue; | |
| if (unserializableValue === UnserializableNumber.Infinity || | |
| unserializableValue === UnserializableNumber.NegativeInfinity || | |
| unserializableValue === UnserializableNumber.Negative0 || | |
| unserializableValue === UnserializableNumber.NaN) { | |
| this._value = Number(unserializableValue); | |
| } else if (type === 'bigint' && unserializableValue.endsWith('n')) { | |
| this._value = BigInt(unserializableValue.substring(0, unserializableValue.length - 1)); | |
| } else { | |
| this._value = unserializableValue; | |
| } | |
| } else { | |
| this._value = value; | |
| } | |
| } | |
| this._customPreview = customPreview || null; | |
| this._className = typeof className === 'string' ? className : null; | |
| } | |
| /** | |
| * @override | |
| * @return {?Protocol.Runtime.CustomPreview} | |
| */ | |
| customPreview() { | |
| return this._customPreview; | |
| } | |
| /** | |
| * @override | |
| * @return {!Protocol.Runtime.RemoteObjectId|undefined} | |
| */ | |
| get objectId() { | |
| return this._objectId; | |
| } | |
| /** | |
| * @override | |
| * @return {string} | |
| */ | |
| get type() { | |
| return this._type; | |
| } | |
| /** | |
| * @override | |
| * @return {string|undefined} | |
| */ | |
| get subtype() { | |
| return this._subtype; | |
| } | |
| /** | |
| * @override | |
| * @return {*} | |
| */ | |
| get value() { | |
| return this._value; | |
| } | |
| /** | |
| * @override | |
| * @return {string|undefined} | |
| */ | |
| unserializableValue() { | |
| return this._unserializableValue; | |
| } | |
| /** | |
| * @override | |
| * @return {string|undefined} | |
| */ | |
| get description() { | |
| return this._description; | |
| } | |
| /** | |
| * @override | |
| * @return {boolean} | |
| */ | |
| get hasChildren() { | |
| return this._hasChildren; | |
| } | |
| /** | |
| * @override | |
| * @return {!Protocol.Runtime.ObjectPreview|undefined} | |
| */ | |
| get preview() { | |
| return this._preview; | |
| } | |
| /** | |
| * @override | |
| * @return {?string} | |
| */ | |
| get className() { | |
| return this._className; | |
| } | |
| /** | |
| * @override | |
| * @param {boolean} generatePreview | |
| * @return {!Promise<!SDK.GetPropertiesResult>} | |
| */ | |
| getOwnProperties(generatePreview) { | |
| return this.doGetProperties(true, false, generatePreview); | |
| } | |
| /** | |
| * @override | |
| * @param {boolean} accessorPropertiesOnly | |
| * @param {boolean} generatePreview | |
| * @return {!Promise<!SDK.GetPropertiesResult>} | |
| */ | |
| getAllProperties(accessorPropertiesOnly, generatePreview) { | |
| return this.doGetProperties(false, accessorPropertiesOnly, generatePreview); | |
| } | |
| /** | |
| * @param {boolean} ownProperties | |
| * @param {boolean} accessorPropertiesOnly | |
| * @param {boolean} generatePreview | |
| * @return {!Promise<!SDK.GetPropertiesResult>} | |
| */ | |
| async doGetProperties(ownProperties, accessorPropertiesOnly, generatePreview) { | |
| if (!this._objectId) { | |
| return /** @type {!SDK.GetPropertiesResult} */ ({properties: null, internalProperties: null}); | |
| } | |
| const response = await this._runtimeAgent.invoke_getProperties( | |
| {objectId: this._objectId, ownProperties, accessorPropertiesOnly, generatePreview}); | |
| if (response[Protocol.Error]) { | |
| return /** @type {!SDK.GetPropertiesResult} */ ({properties: null, internalProperties: null}); | |
| } | |
| if (response.exceptionDetails) { | |
| this._runtimeModel.exceptionThrown(Date.now(), response.exceptionDetails); | |
| return /** @type {!SDK.GetPropertiesResult} */ ({properties: null, internalProperties: null}); | |
| } | |
| const {result: properties = [], internalProperties = [], privateProperties = []} = response; | |
| const result = []; | |
| for (const property of properties) { | |
| const propertyValue = property.value ? this._runtimeModel.createRemoteObject(property.value) : null; | |
| const propertySymbol = property.symbol ? this._runtimeModel.createRemoteObject(property.symbol) : null; | |
| const remoteProperty = new RemoteObjectProperty( | |
| property.name, propertyValue, !!property.enumerable, !!property.writable, !!property.isOwn, | |
| !!property.wasThrown, propertySymbol); | |
| if (typeof property.value === 'undefined') { | |
| if (property.get && property.get.type !== 'undefined') { | |
| remoteProperty.getter = this._runtimeModel.createRemoteObject(property.get); | |
| } | |
| if (property.set && property.set.type !== 'undefined') { | |
| remoteProperty.setter = this._runtimeModel.createRemoteObject(property.set); | |
| } | |
| } | |
| result.push(remoteProperty); | |
| } | |
| for (const property of privateProperties) { | |
| const propertyValue = this._runtimeModel.createRemoteObject(property.value); | |
| const remoteProperty = new RemoteObjectProperty( | |
| property.name, propertyValue, true, true, true, false, undefined, false, undefined, true); | |
| result.push(remoteProperty); | |
| } | |
| const internalPropertiesResult = []; | |
| for (const property of internalProperties) { | |
| if (!property.value) { | |
| continue; | |
| } | |
| if (property.name === '[[StableObjectId]]') { | |
| continue; | |
| } | |
| const propertyValue = this._runtimeModel.createRemoteObject(property.value); | |
| internalPropertiesResult.push( | |
| new RemoteObjectProperty(property.name, propertyValue, true, false, undefined, undefined, undefined, true)); | |
| } | |
| return {properties: result, internalProperties: internalPropertiesResult}; | |
| } | |
| /** | |
| * @override | |
| * @param {string|!Protocol.Runtime.CallArgument} name | |
| * @param {string} value | |
| * @return {!Promise<string|undefined>} | |
| */ | |
| async setPropertyValue(name, value) { | |
| if (!this._objectId) { | |
| return `Can't set a property of non-object.`; | |
| } | |
| const response = await this._runtimeAgent.invoke_evaluate({expression: value, silent: true}); | |
| if (response[Protocol.Error] || response.exceptionDetails) { | |
| return response[Protocol.Error] || | |
| (response.result.type !== 'string' ? response.result.description : | |
| /** @type {string} */ (response.result.value)); | |
| } | |
| if (typeof name === 'string') { | |
| name = RemoteObject.toCallArgument(name); | |
| } | |
| const resultPromise = this.doSetObjectPropertyValue(response.result, name); | |
| if (response.result.objectId) { | |
| this._runtimeAgent.releaseObject(response.result.objectId); | |
| } | |
| return resultPromise; | |
| } | |
| /** | |
| * @param {!Protocol.Runtime.RemoteObject} result | |
| * @param {!Protocol.Runtime.CallArgument} name | |
| * @return {!Promise<string|undefined>} | |
| */ | |
| async doSetObjectPropertyValue(result, name) { | |
| // This assignment may be for a regular (data) property, and for an accessor property (with getter/setter). | |
| // Note the sensitive matter about accessor property: the property may be physically defined in some proto object, | |
| // but logically it is bound to the object in question. JavaScript passes this object to getters/setters, not the object | |
| // where property was defined; so do we. | |
| const setPropertyValueFunction = 'function(a, b) { this[a] = b; }'; | |
| const argv = [name, RemoteObject.toCallArgument(result)]; | |
| const response = await this._runtimeAgent.invoke_callFunctionOn( | |
| {objectId: this._objectId, functionDeclaration: setPropertyValueFunction, arguments: argv, silent: true}); | |
| const error = response[Protocol.Error]; | |
| return error || response.exceptionDetails ? error || response.result.description : undefined; | |
| } | |
| /** | |
| * @override | |
| * @param {!Protocol.Runtime.CallArgument} name | |
| * @return {!Promise<string|undefined>} | |
| */ | |
| async deleteProperty(name) { | |
| if (!this._objectId) { | |
| return `Can't delete a property of non-object.`; | |
| } | |
| const deletePropertyFunction = 'function(a) { delete this[a]; return !(a in this); }'; | |
| const response = await this._runtimeAgent.invoke_callFunctionOn( | |
| {objectId: this._objectId, functionDeclaration: deletePropertyFunction, arguments: [name], silent: true}); | |
| if (response[Protocol.Error] || response.exceptionDetails) { | |
| return response[Protocol.Error] || response.result.description; | |
| } | |
| if (!response.result.value) { | |
| return 'Failed to delete property.'; | |
| } | |
| } | |
| /** | |
| * @override | |
| * @param {function(this:Object, ...)} functionDeclaration | |
| * @param {!Array<!Protocol.Runtime.CallArgument>=} args | |
| * @return {!Promise<!SDK.CallFunctionResult>} | |
| */ | |
| async callFunction(functionDeclaration, args) { | |
| const response = await this._runtimeAgent.invoke_callFunctionOn( | |
| {objectId: this._objectId, functionDeclaration: functionDeclaration.toString(), arguments: args, silent: true}); | |
| if (response[Protocol.Error]) { | |
| return {object: null, wasThrown: false}; | |
| } | |
| // TODO: release exceptionDetails object | |
| return {object: this._runtimeModel.createRemoteObject(response.result), wasThrown: !!response.exceptionDetails}; | |
| } | |
| /** | |
| * @override | |
| * @param {function(this:Object, ...):T} functionDeclaration | |
| * @param {!Array<!Protocol.Runtime.CallArgument>|undefined} args | |
| * @return {!Promise<T>} | |
| * @template T | |
| */ | |
| async callFunctionJSON(functionDeclaration, args) { | |
| const response = await this._runtimeAgent.invoke_callFunctionOn({ | |
| objectId: this._objectId, | |
| functionDeclaration: functionDeclaration.toString(), | |
| arguments: args, | |
| silent: true, | |
| returnByValue: true | |
| }); | |
| return response[Protocol.Error] || response.exceptionDetails ? null : response.result.value; | |
| } | |
| /** | |
| * @override | |
| */ | |
| release() { | |
| if (!this._objectId) { | |
| return; | |
| } | |
| this._runtimeAgent.releaseObject(this._objectId); | |
| } | |
| /** | |
| * @override | |
| * @return {number} | |
| */ | |
| arrayLength() { | |
| return RemoteObject.arrayLength(this); | |
| } | |
| /** | |
| * @override | |
| * @return {!SDK.DebuggerModel} | |
| */ | |
| debuggerModel() { | |
| return this._runtimeModel.debuggerModel(); | |
| } | |
| /** | |
| * @override | |
| * @return {!SDK.RuntimeModel} | |
| */ | |
| runtimeModel() { | |
| return this._runtimeModel; | |
| } | |
| /** | |
| * @override | |
| * @return {boolean} | |
| */ | |
| isNode() { | |
| return !!this._objectId && this.type === 'object' && this.subtype === 'node'; | |
| } | |
| } | |
| export class ScopeRemoteObject extends RemoteObjectImpl { | |
| /** | |
| * @param {!SDK.RuntimeModel} runtimeModel | |
| * @param {string|undefined} objectId | |
| * @param {!ScopeRef} scopeRef | |
| * @param {string} type | |
| * @param {string|undefined} subtype | |
| * @param {*} value | |
| * @param {!Protocol.Runtime.UnserializableValue=} unserializableValue | |
| * @param {string=} description | |
| * @param {!Protocol.Runtime.ObjectPreview=} preview | |
| */ | |
| constructor(runtimeModel, objectId, scopeRef, type, subtype, value, unserializableValue, description, preview) { | |
| super(runtimeModel, objectId, type, subtype, value, unserializableValue, description, preview); | |
| this._scopeRef = scopeRef; | |
| this._savedScopeProperties = undefined; | |
| } | |
| /** | |
| * @override | |
| * @param {boolean} ownProperties | |
| * @param {boolean} accessorPropertiesOnly | |
| * @param {boolean} generatePreview | |
| * @return {!Promise<!SDK.GetPropertiesResult>} | |
| */ | |
| async doGetProperties(ownProperties, accessorPropertiesOnly, generatePreview) { | |
| if (accessorPropertiesOnly) { | |
| return /** @type {!SDK.GetPropertiesResult} */ ({properties: [], internalProperties: []}); | |
| } | |
| if (this._savedScopeProperties) { | |
| // No need to reload scope variables, as the remote object never | |
| // changes its properties. If variable is updated, the properties | |
| // array is patched locally. | |
| return {properties: this._savedScopeProperties.slice(), internalProperties: null}; | |
| } | |
| const allProperties = | |
| await super.doGetProperties(ownProperties, accessorPropertiesOnly, true /* generatePreview */); | |
| if (this._scopeRef && Array.isArray(allProperties.properties)) { | |
| this._savedScopeProperties = allProperties.properties.slice(); | |
| if (!this._scopeRef.callFrameId) { | |
| for (const property of this._savedScopeProperties) { | |
| property.writable = false; | |
| } | |
| } | |
| } | |
| return allProperties; | |
| } | |
| /** | |
| * @override | |
| * @param {!Protocol.Runtime.RemoteObject} result | |
| * @param {!Protocol.Runtime.CallArgument} argumentName | |
| * @return {!Promise<string|undefined>} | |
| */ | |
| async doSetObjectPropertyValue(result, argumentName) { | |
| const name = /** @type {string} */ (argumentName.value); | |
| const error = await this.debuggerModel().setVariableValue( | |
| this._scopeRef.number, name, RemoteObject.toCallArgument(result), this._scopeRef.callFrameId); | |
| if (error) { | |
| return error; | |
| } | |
| if (this._savedScopeProperties) { | |
| for (const property of this._savedScopeProperties) { | |
| if (property.name === name) { | |
| property.value = this._runtimeModel.createRemoteObject(result); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| export class ScopeRef { | |
| /** | |
| * @param {number} number | |
| * @param {string=} callFrameId | |
| */ | |
| constructor(number, callFrameId) { | |
| this.number = number; | |
| this.callFrameId = callFrameId; | |
| } | |
| } | |
| /** | |
| * @unrestricted | |
| */ | |
| export class RemoteObjectProperty { | |
| /** | |
| * @param {string} name | |
| * @param {?RemoteObject} value | |
| * @param {boolean=} enumerable | |
| * @param {boolean=} writable | |
| * @param {boolean=} isOwn | |
| * @param {boolean=} wasThrown | |
| * @param {?RemoteObject=} symbol | |
| * @param {boolean=} synthetic | |
| * @param {function(string):!Promise<?RemoteObject>=} syntheticSetter | |
| * @param {boolean=} isPrivate | |
| */ | |
| constructor(name, value, enumerable, writable, isOwn, wasThrown, symbol, synthetic, syntheticSetter, isPrivate) { | |
| this.name = name; | |
| if (value !== null) { | |
| this.value = value; | |
| } | |
| this.enumerable = typeof enumerable !== 'undefined' ? enumerable : true; | |
| const isNonSyntheticOrSyntheticWritable = !synthetic || !!syntheticSetter; | |
| this.writable = typeof writable !== 'undefined' ? writable : isNonSyntheticOrSyntheticWritable; | |
| this.isOwn = !!isOwn; | |
| this.wasThrown = !!wasThrown; | |
| if (symbol) { | |
| this.symbol = symbol; | |
| } | |
| this.synthetic = !!synthetic; | |
| if (syntheticSetter) { | |
| this.syntheticSetter = syntheticSetter; | |
| } | |
| this.private = !!isPrivate; | |
| } | |
| /** | |
| * @param {string} expression | |
| * @return {!Promise<boolean>} | |
| */ | |
| async setSyntheticValue(expression) { | |
| if (!this.syntheticSetter) { | |
| return false; | |
| } | |
| const result = await this.syntheticSetter(expression); | |
| if (result) { | |
| this.value = result; | |
| } | |
| return !!result; | |
| } | |
| /** | |
| * @return {boolean} | |
| */ | |
| isAccessorProperty() { | |
| return !!(this.getter || this.setter); | |
| } | |
| } | |
| // Below is a wrapper around a local object that implements the RemoteObject interface, | |
| // which can be used by the UI code (primarily ObjectPropertiesSection). | |
| // Note that only JSON-compliant objects are currently supported, as there's no provision | |
| // for traversing prototypes, extracting class names via constructor, handling properties | |
| // or functions. | |
| export class LocalJSONObject extends RemoteObject { | |
| /** | |
| * @param {*} value | |
| */ | |
| constructor(value) { | |
| super(); | |
| this._value = value; | |
| /** @type {string} */ | |
| this._cachedDescription; | |
| /** @type {!Array<!RemoteObjectProperty>} */ | |
| this._cachedChildren; | |
| } | |
| /** | |
| * @override | |
| * @return {!Protocol.Runtime.RemoteObjectId|undefined} | |
| * */ | |
| get objectId() { | |
| return undefined; | |
| } | |
| /** | |
| * @override | |
| * @return {*} | |
| */ | |
| get value() { | |
| return this._value; | |
| } | |
| /** | |
| * @override | |
| * @return {string|undefined} | |
| */ | |
| unserializableValue() { | |
| const unserializableDescription = RemoteObject.unserializableDescription(this._value); | |
| return unserializableDescription || undefined; | |
| } | |
| /** | |
| * @override | |
| * @return {string} | |
| */ | |
| get description() { | |
| if (this._cachedDescription) { | |
| return this._cachedDescription; | |
| } | |
| /** | |
| * @param {!RemoteObjectProperty} property | |
| * @return {string} | |
| * @this {LocalJSONObject} | |
| */ | |
| function formatArrayItem(property) { | |
| return this._formatValue(property.value); | |
| } | |
| /** | |
| * @param {!RemoteObjectProperty} property | |
| * @return {string} | |
| * @this {LocalJSONObject} | |
| */ | |
| function formatObjectItem(property) { | |
| let name = property.name; | |
| if (/^\s|\s$|^$|\n/.test(name)) { | |
| name = '"' + name.replace(/\n/g, '\u21B5') + '"'; | |
| } | |
| return name + ': ' + this._formatValue(property.value); | |
| } | |
| if (this.type === 'object') { | |
| switch (this.subtype) { | |
| case 'array': | |
| this._cachedDescription = this._concatenate('[', ']', formatArrayItem.bind(this)); | |
| break; | |
| case 'date': | |
| this._cachedDescription = '' + this._value; | |
| break; | |
| case 'null': | |
| this._cachedDescription = 'null'; | |
| break; | |
| default: | |
| this._cachedDescription = this._concatenate('{', '}', formatObjectItem.bind(this)); | |
| } | |
| } else { | |
| this._cachedDescription = String(this._value); | |
| } | |
| return this._cachedDescription; | |
| } | |
| /** | |
| * @param {?RemoteObject} value | |
| * @return {string} | |
| */ | |
| _formatValue(value) { | |
| if (!value) { | |
| return 'undefined'; | |
| } | |
| const description = value.description || ''; | |
| if (value.type === 'string') { | |
| return '"' + description.replace(/\n/g, '\u21B5') + '"'; | |
| } | |
| return description; | |
| } | |
| /** | |
| * @param {string} prefix | |
| * @param {string} suffix | |
| * @param {function(!RemoteObjectProperty)} formatProperty | |
| * @return {string} | |
| */ | |
| _concatenate(prefix, suffix, formatProperty) { | |
| const previewChars = 100; | |
| let buffer = prefix; | |
| const children = this._children(); | |
| for (let i = 0; i < children.length; ++i) { | |
| const itemDescription = formatProperty(children[i]); | |
| if (buffer.length + itemDescription.length > previewChars) { | |
| buffer += ',\u2026'; | |
| break; | |
| } | |
| if (i) { | |
| buffer += ', '; | |
| } | |
| buffer += itemDescription; | |
| } | |
| buffer += suffix; | |
| return buffer; | |
| } | |
| /** | |
| * @override | |
| * @return {string} | |
| */ | |
| get type() { | |
| return typeof this._value; | |
| } | |
| /** | |
| * @override | |
| * @return {string|undefined} | |
| */ | |
| get subtype() { | |
| if (this._value === null) { | |
| return 'null'; | |
| } | |
| if (Array.isArray(this._value)) { | |
| return 'array'; | |
| } | |
| if (this._value instanceof Date) { | |
| return 'date'; | |
| } | |
| return undefined; | |
| } | |
| /** | |
| * @override | |
| * @return {boolean} | |
| */ | |
| get hasChildren() { | |
| if ((typeof this._value !== 'object') || (this._value === null)) { | |
| return false; | |
| } | |
| return !!Object.keys(/** @type {!Object} */ (this._value)).length; | |
| } | |
| /** | |
| * @override | |
| * @param {boolean} generatePreview | |
| * @return {!Promise<!SDK.GetPropertiesResult>} | |
| */ | |
| getOwnProperties(generatePreview) { | |
| return Promise.resolve( | |
| /** @type {!SDK.GetPropertiesResult} */ ({properties: this._children(), internalProperties: null})); | |
| } | |
| /** | |
| * @override | |
| * @param {boolean} accessorPropertiesOnly | |
| * @param {boolean} generatePreview | |
| * @return {!Promise<!SDK.GetPropertiesResult>} | |
| */ | |
| getAllProperties(accessorPropertiesOnly, generatePreview) { | |
| if (accessorPropertiesOnly) { | |
| return Promise.resolve(/** @type {!SDK.GetPropertiesResult} */ ({properties: [], internalProperties: null})); | |
| } else { | |
| return Promise.resolve( | |
| /** @type {!SDK.GetPropertiesResult} */ ({properties: this._children(), internalProperties: null})); | |
| } | |
| } | |
| /** | |
| * @return {!Array.<!RemoteObjectProperty>} | |
| */ | |
| _children() { | |
| if (!this.hasChildren) { | |
| return []; | |
| } | |
| const value = /** @type {!Object} */ (this._value); | |
| /** | |
| * @param {string} propName | |
| * @return {!RemoteObjectProperty} | |
| */ | |
| function buildProperty(propName) { | |
| let propValue = value[propName]; | |
| if (!(propValue instanceof RemoteObject)) { | |
| propValue = RemoteObject.fromLocalObject(propValue); | |
| } | |
| return new RemoteObjectProperty(propName, propValue); | |
| } | |
| if (!this._cachedChildren) { | |
| this._cachedChildren = Object.keys(value).map(buildProperty); | |
| } | |
| return this._cachedChildren; | |
| } | |
| /** | |
| * @override | |
| * @return {number} | |
| */ | |
| arrayLength() { | |
| return Array.isArray(this._value) ? this._value.length : 0; | |
| } | |
| /** | |
| * @override | |
| * @param {function(this:Object, ...)} functionDeclaration | |
| * @param {!Array<!Protocol.Runtime.CallArgument>=} args | |
| * @return {!Promise<!SDK.CallFunctionResult>} | |
| */ | |
| callFunction(functionDeclaration, args) { | |
| const target = /** @type {?Object} */ (this._value); | |
| const rawArgs = args ? args.map(arg => arg.value) : []; | |
| let result; | |
| let wasThrown = false; | |
| try { | |
| result = functionDeclaration.apply(target, rawArgs); | |
| } catch (e) { | |
| wasThrown = true; | |
| } | |
| const object = RemoteObject.fromLocalObject(result); | |
| return Promise.resolve( | |
| /** @type {!SDK.CallFunctionResult} */ ({object, wasThrown})); | |
| } | |
| /** | |
| * @override | |
| * @param {function(this:Object, ...):T} functionDeclaration | |
| * @param {!Array<!Protocol.Runtime.CallArgument>|undefined} args | |
| * @return {!Promise<T>} | |
| * @template T | |
| */ | |
| callFunctionJSON(functionDeclaration, args) { | |
| const target = /** @type {?Object} */ (this._value); | |
| const rawArgs = args ? args.map(arg => arg.value) : []; | |
| let result; | |
| try { | |
| result = functionDeclaration.apply(target, rawArgs); | |
| } catch (e) { | |
| result = null; | |
| } | |
| return Promise.resolve(result); | |
| } | |
| } | |
| export class RemoteArray { | |
| /** | |
| * @param {!RemoteObject} object | |
| */ | |
| constructor(object) { | |
| this._object = object; | |
| } | |
| /** | |
| * @param {?RemoteObject} object | |
| * @return {!RemoteArray} | |
| */ | |
| static objectAsArray(object) { | |
| if (!object || object.type !== 'object' || (object.subtype !== 'array' && object.subtype !== 'typedarray')) { | |
| throw new Error('Object is empty or not an array'); | |
| } | |
| return new RemoteArray(object); | |
| } | |
| /** | |
| * @param {!Array<!RemoteObject>} objects | |
| * @return {!Promise<!RemoteArray>} | |
| */ | |
| static createFromRemoteObjects(objects) { | |
| if (!objects.length) { | |
| throw new Error('Input array is empty'); | |
| } | |
| const objectArguments = []; | |
| for (let i = 0; i < objects.length; ++i) { | |
| objectArguments.push(RemoteObject.toCallArgument(objects[i])); | |
| } | |
| return objects[0].callFunction(createArray, objectArguments).then(returnRemoteArray); | |
| /** | |
| * @return {!Array<*>} | |
| */ | |
| function createArray() { | |
| if (arguments.length > 1) { | |
| return new Array(arguments); | |
| } | |
| return [arguments[0]]; | |
| } | |
| /** | |
| * @param {!SDK.CallFunctionResult} result | |
| * @return {!RemoteArray} | |
| */ | |
| function returnRemoteArray(result) { | |
| if (result.wasThrown || !result.object) { | |
| throw new Error('Call function throws exceptions or returns empty value'); | |
| } | |
| return RemoteArray.objectAsArray(result.object); | |
| } | |
| } | |
| /** | |
| * @param {number} index | |
| * @return {!Promise<!RemoteObject>} | |
| */ | |
| at(index) { | |
| if (index < 0 || index > this._object.arrayLength()) { | |
| throw new Error('Out of range'); | |
| } | |
| return this._object.callFunction(at, [RemoteObject.toCallArgument(index)]).then(assertCallFunctionResult); | |
| /** | |
| * @suppressReceiverCheck | |
| * @param {number} index | |
| * @return {*} | |
| * @this {!Object} | |
| */ | |
| function at(index) { | |
| return this[index]; | |
| } | |
| /** | |
| * @param {!SDK.CallFunctionResult} result | |
| * @return {!RemoteObject} | |
| */ | |
| function assertCallFunctionResult(result) { | |
| if (result.wasThrown || !result.object) { | |
| throw new Error('Exception in callFunction or result value is empty'); | |
| } | |
| return result.object; | |
| } | |
| } | |
| /** | |
| * @return {number} | |
| */ | |
| length() { | |
| return this._object.arrayLength(); | |
| } | |
| /** | |
| * @param {function(!RemoteObject):!Promise<T>} func | |
| * @return {!Promise<!Array<T>>} | |
| * @template T | |
| */ | |
| map(func) { | |
| const promises = []; | |
| for (let i = 0; i < this.length(); ++i) { | |
| promises.push(this.at(i).then(func)); | |
| } | |
| return Promise.all(promises); | |
| } | |
| /** | |
| * @return {!RemoteObject} | |
| */ | |
| object() { | |
| return this._object; | |
| } | |
| } | |
| export class RemoteFunction { | |
| /** | |
| * @param {!RemoteObject} object | |
| */ | |
| constructor(object) { | |
| this._object = object; | |
| } | |
| /** | |
| * @param {?RemoteObject} object | |
| * @return {!RemoteFunction} | |
| */ | |
| static objectAsFunction(object) { | |
| if (!object || object.type !== 'function') { | |
| throw new Error('Object is empty or not a function'); | |
| } | |
| return new RemoteFunction(object); | |
| } | |
| /** | |
| * @return {!Promise<!RemoteObject>} | |
| */ | |
| targetFunction() { | |
| return this._object.getOwnProperties(false /* generatePreview */).then(targetFunction.bind(this)); | |
| /** | |
| * @param {!SDK.GetPropertiesResult} ownProperties | |
| * @return {!RemoteObject} | |
| * @this {RemoteFunction} | |
| */ | |
| function targetFunction(ownProperties) { | |
| if (!ownProperties.internalProperties) { | |
| return this._object; | |
| } | |
| const internalProperties = ownProperties.internalProperties; | |
| for (const property of internalProperties) { | |
| if (property.name === '[[TargetFunction]]') { | |
| return property.value; | |
| } | |
| } | |
| return this._object; | |
| } | |
| } | |
| /** | |
| * @return {!Promise<?SDK.DebuggerModel.FunctionDetails>} | |
| */ | |
| targetFunctionDetails() { | |
| return this.targetFunction().then(functionDetails.bind(this)); | |
| /** | |
| * @param {!RemoteObject} targetFunction | |
| * @return {!Promise<?SDK.DebuggerModel.FunctionDetails>} | |
| * @this {RemoteFunction} | |
| */ | |
| function functionDetails(targetFunction) { | |
| const boundReleaseFunctionDetails = | |
| releaseTargetFunction.bind(null, this._object !== targetFunction ? targetFunction : null); | |
| return targetFunction.debuggerModel().functionDetailsPromise(targetFunction).then(boundReleaseFunctionDetails); | |
| } | |
| /** | |
| * @param {?RemoteObject} targetFunction | |
| * @param {?SDK.DebuggerModel.FunctionDetails} functionDetails | |
| * @return {?SDK.DebuggerModel.FunctionDetails} | |
| */ | |
| function releaseTargetFunction(targetFunction, functionDetails) { | |
| if (targetFunction) { | |
| targetFunction.release(); | |
| } | |
| return functionDetails; | |
| } | |
| } | |
| /** | |
| * @return {!RemoteObject} | |
| */ | |
| object() { | |
| return this._object; | |
| } | |
| } | |
| /** | |
| * @const | |
| * @type {!RegExp} | |
| */ | |
| const _descriptionLengthParenRegex = /\(([0-9]+)\)/; | |
| /** | |
| * @const | |
| * @type {!RegExp} | |
| */ | |
| const _descriptionLengthSquareRegex = /\[([0-9]+)\]/; | |
| /** | |
| * @const | |
| * @enum {!Protocol.Runtime.UnserializableValue} | |
| */ | |
| const UnserializableNumber = { | |
| Negative0: /** @type {!Protocol.Runtime.UnserializableValue} */ ('-0'), | |
| NaN: /** @type {!Protocol.Runtime.UnserializableValue} */ ('NaN'), | |
| Infinity: /** @type {!Protocol.Runtime.UnserializableValue} */ ('Infinity'), | |
| NegativeInfinity: /** @type {!Protocol.Runtime.UnserializableValue} */ ('-Infinity') | |
| }; | |
| /* Legacy exported object */ | |
| self.SDK = self.SDK || {}; | |
| /* Legacy exported object */ | |
| SDK = SDK || {}; | |
| /** @constructor */ | |
| SDK.RemoteObject = RemoteObject; | |
| /** @constructor */ | |
| SDK.RemoteObjectImpl = RemoteObjectImpl; | |
| /** @constructor */ | |
| SDK.ScopeRemoteObject = ScopeRemoteObject; | |
| /** @constructor */ | |
| SDK.ScopeRef = ScopeRef; | |
| /** @constructor */ | |
| SDK.RemoteObjectProperty = RemoteObjectProperty; | |
| /** @constructor */ | |
| SDK.LocalJSONObject = LocalJSONObject; | |
| /** @constructor */ | |
| SDK.RemoteArray = RemoteArray; | |
| /** @constructor */ | |
| SDK.RemoteFunction = RemoteFunction; | |
| /** | |
| * @typedef {{object: ?RemoteObject, wasThrown: (boolean|undefined)}} | |
| */ | |
| SDK.CallFunctionResult; | |
| /** | |
| * @typedef {{properties: ?Array<!RemoteObjectProperty>, internalProperties: ?Array<!RemoteObjectProperty>}} | |
| */ | |
| SDK.GetPropertiesResult; |