From 2d184d685dfff6c05ff3f56aa4eeb50ba6ceb93e Mon Sep 17 00:00:00 2001 From: Henrik Skupin Date: Tue, 24 Mar 2020 20:32:19 +0000 Subject: [PATCH] Bug 1623581 - [remote] Add "returnByValue" support to Runtime.evaluate. r=remote-protocol-reviewers,maja_zf Differential Revision: https://phabricator.services.mozilla.com/D67841 --HG-- extra : moz-landing-system : lando --- remote/domains/content/Runtime.jsm | 7 +- .../content/runtime/ExecutionContext.jsm | 29 +++-- .../test/browser/runtime/browser_evaluate.js | 105 ++++++++++++++++++ 3 files changed, 131 insertions(+), 10 deletions(-) diff --git a/remote/domains/content/Runtime.jsm b/remote/domains/content/Runtime.jsm index 37b52bd98448..71f6c5189f53 100644 --- a/remote/domains/content/Runtime.jsm +++ b/remote/domains/content/Runtime.jsm @@ -223,11 +223,14 @@ class Runtime extends ContentProcessDomain { * The evaluation result, and optionally exception details. */ evaluate(options = {}) { - const { expression, contextId } = options; + const { expression, contextId, returnByValue = false } = options; if (typeof expression != "string") { throw new Error("expression: string value expected"); } + if (typeof returnByValue != "boolean") { + throw new Error("returnByValue: boolean value expected"); + } let context; if (typeof contextId != "undefined") { @@ -239,7 +242,7 @@ class Runtime extends ContentProcessDomain { context = this._getDefaultContextForWindow(); } - return context.evaluate(expression); + return context.evaluate(expression, returnByValue); } getProperties({ objectId, ownProperties }) { diff --git a/remote/domains/content/runtime/ExecutionContext.jsm b/remote/domains/content/runtime/ExecutionContext.jsm index 360866e65f5c..ff34b47d4114 100644 --- a/remote/domains/content/runtime/ExecutionContext.jsm +++ b/remote/domains/content/runtime/ExecutionContext.jsm @@ -76,13 +76,18 @@ class ExecutionContext { * * @param {String} expression * The JS expression to evaluate against the JS context. - * @return {Object} A multi-form object depending if the execution succeed or failed. - * If the expression failed to evaluate, it will return an object with an - * `exceptionDetails` attribute matching the `ExceptionDetails` CDP type. - * Otherwise it will return an object with `result` attribute whose type is + * @param {boolean} returnByValue + * Whether the result is expected to be a JSON object + * that should be sent by value. + * + * @return {Object} A multi-form object depending if the execution + * succeed or failed. If the expression failed to evaluate, + * it will return an object with an `exceptionDetails` attribute + * matching the `ExceptionDetails` CDP type. Otherwise it will + * return an object with `result` attribute whose type is * `RemoteObject` CDP type. */ - evaluate(expression) { + evaluate(expression, returnByValue) { let rv = this._debuggee.executeInGlobal(expression); if (!rv) { return { @@ -91,12 +96,19 @@ class ExecutionContext { }, }; } + if (rv.throw) { return this._returnError(rv.throw); } - return { - result: this._toRemoteObject(rv.return), - }; + + let result; + if (returnByValue) { + result = this._toRemoteObjectByValue(result); + } else { + result = this._toRemoteObject(rv.return); + } + + return { result }; } /** @@ -437,6 +449,7 @@ class ExecutionContext { * * @param {Debugger.Object} obj * The object to convert + * * @return {Object} * The converted object */ diff --git a/remote/test/browser/runtime/browser_evaluate.js b/remote/test/browser/runtime/browser_evaluate.js index aef6e69e5639..7411304856ac 100644 --- a/remote/test/browser/runtime/browser_evaluate.js +++ b/remote/test/browser/runtime/browser_evaluate.js @@ -152,6 +152,111 @@ add_task(async function returnAsObjectUndefined({ client }) { ); }); +add_task(async function returnByValueInvalidTypes({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + for (const returnByValue of [null, 1, "foo", [], {}]) { + let errorThrown = ""; + try { + await Runtime.evaluate({ + expression: "", + returnByValue, + }); + } catch (e) { + errorThrown = e.message; + } + ok(errorThrown.includes("returnByValue: boolean value expected")); + } +}); + +add_task(async function returnByValue({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const values = [ + null, + 42, + 42.0, + "42", + true, + false, + { foo: true }, + { foo: { bar: 42, str: "str", array: [1, 2, 3] } }, + [42, "42", true], + [{ foo: true }], + ]; + + for (const value of values) { + const { result } = await Runtime.evaluate({ + expression: `(${JSON.stringify(value)})`, + returnByValue: true, + }); + + Assert.deepEqual( + result, + { + type: typeof value, + value, + description: value != null ? value.toString() : value, + }, + `Returned expected value for ${JSON.stringify(value)}` + ); + } +}); + +add_task(async function returnByValueNotSerializable({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const notSerializableNumbers = { + number: ["-0", "NaN", "Infinity", "-Infinity"], + bigint: ["42n"], + }; + + for (const type in notSerializableNumbers) { + for (const unserializableValue of notSerializableNumbers[type]) { + const { result } = await Runtime.evaluate({ + expression: `(${unserializableValue})`, + returnByValue: true, + }); + + Assert.deepEqual( + result, + { + type, + unserializableValue, + description: unserializableValue, + }, + `Returned expected value for ${JSON.stringify(unserializableValue)}` + ); + } + } +}); + +// Test undefined individually as JSON.stringify doesn't return a string +add_task(async function returnByValueUndefined({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const { result } = await Runtime.evaluate({ + expression: "undefined", + returnByValue: true, + }); + + Assert.deepEqual( + result, + { + type: "undefined", + }, + "Undefined type is correct" + ); +}); + add_task(async function exceptionDetailsJavascriptError({ client }) { const { Runtime } = client;