Bug 1548098 - Implement Runtime.callFunctionOn's awaitPromise argument. r=remote-protocol-reviewers,ato
authorAlexandre Poirot <poirot.alex@gmail.com>
Mon, 13 May 2019 16:10:30 +0000
changeset 532448 087726ba0d15387d6a18b152a1687c2b562b1806
parent 532447 55505348c64ff409345e16a6f18c7ae411b02b1f
child 532449 d1985ca4a9e2cc6d94366eea82150788f185305b
push id11268
push usercsabou@mozilla.com
push dateTue, 14 May 2019 15:24:22 +0000
treeherdermozilla-beta@5fb7fcd568d6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersremote-protocol-reviewers, ato
bugs1548098
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1548098 - Implement Runtime.callFunctionOn's awaitPromise argument. r=remote-protocol-reviewers,ato Differential Revision: https://phabricator.services.mozilla.com/D30266
remote/domains/content/Runtime.jsm
remote/domains/content/runtime/ExecutionContext.jsm
remote/test/browser/browser_runtime_callFunctionOn.js
--- a/remote/domains/content/Runtime.jsm
+++ b/remote/domains/content/Runtime.jsm
@@ -86,17 +86,20 @@ class Runtime extends ContentProcessDoma
       throw new Error("Expect 'functionDeclaration' attribute to be passed and be a string");
     }
     if (request.arguments && !Array.isArray(request.arguments)) {
       throw new Error("Expect 'arguments' to be an array");
     }
     if (request.returnByValue && typeof(request.returnByValue) != "boolean") {
       throw new Error("Expect 'returnByValue' to be a boolean");
     }
-    return context.callFunctionOn(request.functionDeclaration, request.arguments, request.returnByValue);
+    if (request.awaitPromise && typeof(request.awaitPromise) != "boolean") {
+      throw new Error("Expect 'awaitPromise' to be a boolean");
+    }
+    return context.callFunctionOn(request.functionDeclaration, request.arguments, request.returnByValue, request.awaitPromise, request.objectId);
   }
 
   get _debugger() {
     if (this.__debugger) {
       return this.__debugger;
     }
     this.__debugger = new Debugger();
     return this.__debugger;
--- a/remote/domains/content/runtime/ExecutionContext.jsm
+++ b/remote/domains/content/runtime/ExecutionContext.jsm
@@ -85,17 +85,17 @@ class ExecutionContext {
     // If that isn't an Error, consider the exception as a JS value
     return {
       exceptionDetails: {
         exception: this._toRemoteObject(exception),
       },
     };
   }
 
-  async callFunctionOn(functionDeclaration, callArguments = [], returnByValue = false) {
+  async callFunctionOn(functionDeclaration, callArguments = [], returnByValue = false, awaitPromise = false) {
     // First evaluate the function
     const fun = this._debuggee.executeInGlobal("(" + functionDeclaration + ")");
     if (!fun) {
       return {
         exceptionDetails: {
           text: "Evaluation terminated!",
         },
       };
@@ -108,26 +108,45 @@ class ExecutionContext {
     // into JS values
     const args = callArguments.map(arg => this._fromCallArgument(arg));
 
     // Finally, call the function with these arguments
     const rv = fun.return.apply(null, args);
     if (rv.throw) {
       return this._returnError(rv.throw);
     }
+
+    let result = rv.return;
+
+    if (result && result.isPromise && awaitPromise) {
+      if (result.promiseState === "fulfilled") {
+        result = result.promiseValue;
+      } else if (result.promiseState === "rejected") {
+        return this._returnError(result.promiseReason);
+      } else {
+        try {
+          const promiseResult = await result.unsafeDereference();
+          result = this._debuggee.makeDebuggeeValue(promiseResult);
+        } catch (e) {
+          // The promise has been rejected
+          return this._returnError(e);
+        }
+      }
+    }
+
     if (returnByValue) {
       return {
         result: {
-          value: this._serialize(rv.return),
+          value: this._serialize(result),
         },
       };
     }
 
     return {
-      result: this._toRemoteObject(rv.return),
+      result: this._toRemoteObject(result),
     };
   }
 
   /**
    * Convert a given `Debugger.Object` to a JSON string.
    *
    * @param {Debugger.Object} obj
    *  The object to convert
--- a/remote/test/browser/browser_runtime_callFunctionOn.js
+++ b/remote/test/browser/browser_runtime_callFunctionOn.js
@@ -27,16 +27,17 @@ add_task(async function() {
   });
   ok(true, "CDP client has been instantiated");
 
   const firstContext = await testRuntimeEnable(client);
   const contextId = firstContext.id;
   await testObjectReferences(client, contextId);
   await testExceptions(client, contextId);
   await testReturnByValue(client, contextId);
+  await testAwaitPromise(client, contextId);
 
   await client.close();
   ok(true, "The client is closed");
 
   BrowserTestUtils.removeTab(gBrowser.selectedTab);
 
   await RemoteAgent.close();
 });
@@ -141,8 +142,68 @@ async function testReturnByValue({ Runti
   // Test undefined individually as JSON.stringify doesn't return a string
   const { result } = await Runtime.callFunctionOn({
     executionContextId,
     functionDeclaration: "() => {}",
     returnByValue: true,
   });
   is(result.value, undefined, "The returned value is undefined");
 }
+
+async function testAwaitPromise({ Runtime }, executionContextId) {
+  // First assert promise resolution with awaitPromise
+  let { result } = await Runtime.callFunctionOn({
+    executionContextId,
+    functionDeclaration: "() => Promise.resolve(42)",
+    awaitPromise: true,
+  });
+  is(result.type, "number", "The type is correct");
+  is(result.subtype, null, "The subtype is null for numbers");
+  is(result.value, 42, "The result is the promise's resolution");
+
+  // Also test promise rejection with awaitPromise
+  let { exceptionDetails } = await Runtime.callFunctionOn({
+    executionContextId,
+    functionDeclaration: "() => Promise.reject(42)",
+    awaitPromise: true,
+  });
+  is(exceptionDetails.exception.value, 42, "The result is the promise's rejection");
+
+  // Then check delayed promise resolution
+  ({ result } = await Runtime.callFunctionOn({
+    executionContextId,
+    functionDeclaration: "() => new Promise(r => setTimeout(() => r(42), 0))",
+    awaitPromise: true,
+  }));
+  is(result.type, "number", "The type is correct");
+  is(result.subtype, null, "The subtype is null for numbers");
+  is(result.value, 42, "The result is the promise's resolution");
+
+  // And delayed promise rejection
+  ({ exceptionDetails } = await Runtime.callFunctionOn({
+    executionContextId,
+    functionDeclaration: "() => new Promise((_,r) => setTimeout(() => r(42), 0))",
+    awaitPromise: true,
+  }));
+  is(exceptionDetails.exception.value, 42, "The result is the promise's rejection");
+
+  // Finally assert promise resolution without awaitPromise
+  ({ result } = await Runtime.callFunctionOn({
+    executionContextId,
+    functionDeclaration: "() => Promise.resolve(42)",
+    awaitPromise: false,
+  }));
+  is(result.type, "object", "The type is correct");
+  is(result.subtype, "promise", "The subtype is promise");
+  ok(!!result.objectId, "We got the object id for the promise");
+  ok(!result.value, "We do not receive any value");
+
+  // As well as promise rejection without awaitPromise
+  ({ result } = await Runtime.callFunctionOn({
+    executionContextId,
+    functionDeclaration: "() => Promise.reject(42)",
+    awaitPromise: false,
+  }));
+  is(result.type, "object", "The type is correct");
+  is(result.subtype, "promise", "The subtype is promise");
+  ok(!!result.objectId, "We got the object id for the promise");
+  ok(!result.exceptionDetails, "We do not receive any exception");
+}