From 277bf5d7f62aafa17b017a168d867d9c10f8d0b3 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 7 Sep 2016 23:07:26 +0200 Subject: [PATCH] Show getter value, fix #77 --- src/chrome/chromeConnection.ts | 9 ++++- src/chrome/chromeDebugAdapter.ts | 57 ++++++++++++++++++++--------- src/chrome/chromeDebugProtocol.d.ts | 17 +++++++++ tslint.json | 1 - 4 files changed, 63 insertions(+), 21 deletions(-) diff --git a/src/chrome/chromeConnection.ts b/src/chrome/chromeConnection.ts index af58cfa07..781338902 100644 --- a/src/chrome/chromeConnection.ts +++ b/src/chrome/chromeConnection.ts @@ -236,8 +236,13 @@ export class ChromeConnection { return this.sendMessage('Runtime.getProperties', { objectId, ownProperties, accessorPropertiesOnly }); } - public runtime_evaluate(expression: string, objectGroup = 'dummyObjectGroup', contextId?: number, returnByValue = false): Promise { - return this.sendMessage('Runtime.evaluate', { expression, objectGroup, contextId, returnByValue }); + public runtime_evaluate(expression: string, objectGroup = 'dummyObjectGroup', contextId = 1, returnByValue = false): Promise { + return this.sendMessage('Runtime.evaluate', { expression, contextId }); + } + + public runtime_callFunctionOn(objectId: string, functionDeclaration: string, args?: Chrome.Runtime.CallArgument[], silent?: boolean, returnByValue?: boolean, + generatePreview?: boolean, userGesture?: boolean, awaitPromise?: boolean): Promise { + return this.sendMessage('Runtime.callFunctionOn', { objectId, functionDeclaration, arguments: args, silent, returnByValue, generatePreview, userGesture, awaitPromise }); } public page_setOverlayMessage(message: string): Promise { diff --git a/src/chrome/chromeDebugAdapter.ts b/src/chrome/chromeDebugAdapter.ts index 7b3b449a3..524abb7e3 100644 --- a/src/chrome/chromeDebugAdapter.ts +++ b/src/chrome/chromeDebugAdapter.ts @@ -543,7 +543,8 @@ export abstract class ChromeDebugAdapter extends BaseDebugAdapter { // If this is the special marker for an exception value, create a fake property descriptor so the usual route can be used if (handle.objectId === ChromeDebugAdapter.EXCEPTION_VALUE_ID) { const excValuePropDescriptor: Chrome.Runtime.PropertyDescriptor = { name: 'exception', value: this._exceptionValueObject }; - return Promise.resolve({ variables: [this.propertyDescriptorToVariable(excValuePropDescriptor)] }); + return this.propertyDescriptorToVariable(excValuePropDescriptor) + .then(variable => ({ variables: [variable]})); } return Promise.all([ @@ -561,19 +562,50 @@ export abstract class ChromeDebugAdapter extends BaseDebugAdapter { }); // Convert Chrome prop descriptors to DebugProtocol vars, sort the result - const variables: DebugProtocol.Variable[] = []; - propsByName.forEach(propDesc => variables.push(this.propertyDescriptorToVariable(propDesc))); + const variables: Promise[] = []; + propsByName.forEach(propDesc => variables.push(this.propertyDescriptorToVariable(propDesc, handle.objectId))); + return Promise.all(variables); + }).then(variables => { variables.sort((var1, var2) => ChromeUtils.compareVariableNames(var1.name, var2.name)); - // If this is a scope that should have the 'this', prop, insert it at the top of the list if (handle.thisObj) { - variables.unshift(this.propertyDescriptorToVariable({ name: 'this', value: handle.thisObj })); + // If this is a scope that should have the 'this', prop, insert it at the top of the list + return this.propertyDescriptorToVariable({ name: 'this', value: handle.thisObj }).then(thisObjVar => { + variables.unshift(thisObjVar); + return { variables }; + }); + } else { + return { variables }; } - - return { variables }; }); } + private propertyDescriptorToVariable(propDesc: Chrome.Runtime.PropertyDescriptor, owningObjectId?: string): Promise { + if (propDesc.get) { + const grabGetterValue = 'function remoteFunction(propName) { return this[propName]; }'; + return this._chromeConnection.runtime_callFunctionOn(owningObjectId, grabGetterValue, [{ value: propDesc.name }]).then(response => { + if (response.error) { + logger.error('Error evaluating getter - ' + response.error.toString()); + return { name: propDesc.name, value: response.error.toString(), variablesReference: 0 }; + } else if (response.result.exceptionDetails) { + // Not an error, getter could be `get foo() { throw new Error('bar'); }` + const exceptionDetails = response.result.exceptionDetails; + logger.log('Exception thrown evaluating getter - ' + JSON.stringify(exceptionDetails.exception)); + return { name: propDesc.name, value: response.result.exceptionDetails.exception.description, variablesReference: 0 }; + } else { + const { value, variablesReference } = this.remoteObjectToValueWithHandle(response.result.result); + return { name: propDesc.name, value, variablesReference }; + } + }); + } else if (propDesc.set) { + // setter without a getter, unlikely + return Promise.resolve({ name: propDesc.name, value: 'setter', variablesReference: 0 }); + } else { + const { value, variablesReference } = this.remoteObjectToValueWithHandle(propDesc.value); + return Promise.resolve({ name: propDesc.name, value, variablesReference }); + } + } + public source(args: DebugProtocol.SourceArguments): Promise { return this._chromeConnection.debugger_getScriptSource(sourceReferenceToScriptId(args.sourceReference)).then(chromeResponse => { return { @@ -620,17 +652,6 @@ export abstract class ChromeDebugAdapter extends BaseDebugAdapter { }); } - private propertyDescriptorToVariable(propDesc: Chrome.Runtime.PropertyDescriptor): DebugProtocol.Variable { - if (propDesc.get || propDesc.set) { - // A property doesn't have a value here, and we shouldn't evaluate the getter because it may have side effects. - // Node adapter shows 'undefined', Chrome can eval the getter on demand. - return { name: propDesc.name, value: 'property', variablesReference: 0 }; - } else { - const { value, variablesReference } = this.remoteObjectToValueWithHandle(propDesc.value); - return { name: propDesc.name, value, variablesReference }; - } - } - /** * Run the object through ChromeUtilities.remoteObjectToValue, and if it returns a variableHandle reference, * use it with this instance's variableHandles to create a variable handle. diff --git a/src/chrome/chromeDebugProtocol.d.ts b/src/chrome/chromeDebugProtocol.d.ts index 9a71b3927..2f4289356 100644 --- a/src/chrome/chromeDebugProtocol.d.ts +++ b/src/chrome/chromeDebugProtocol.d.ts @@ -215,6 +215,23 @@ export namespace Runtime { callFrames: CallFrame[]; parent?: StackTrace; } + + /** + * Represents function call argument. Either remote object id objectId, primitive value, + * unserializable primitive value or neither of (for undefined) them should be specified. + */ + export interface CallArgument { + value?: any; + unserializableValue?: "Infinity" | "NaN" | "-Infinity" | "-0"; + objectId?: string; + } + + export interface CallFunctionOnResponse extends Response { + result: { + result: RemoteObject; + exceptionDetails: any; + }; + } } export namespace Page { diff --git a/tslint.json b/tslint.json index 139454710..628f727f5 100644 --- a/tslint.json +++ b/tslint.json @@ -9,7 +9,6 @@ ], "align": [ true, - "parameters", "statements" ], "ban": false,