Bug 1383711 - Make addOneTimeListener return a Promise; r=ochameau. draft
authorNicolas Chevobbe <nchevobbe@mozilla.com>
Mon, 06 Nov 2017 08:40:39 +0100
changeset 693549 076522e89004f8a4634b4f7732800a5ec14ce633
parent 693376 179dae92e4d794e7f45ad080ff01908c80691f31
child 739073 06bf01efd544dd8f5c78e941eed717ff14ac4c55
push id87849
push userbmo:nchevobbe@mozilla.com
push dateMon, 06 Nov 2017 13:45:48 +0000
reviewersochameau
bugs1383711
milestone58.0a1
Bug 1383711 - Make addOneTimeListener return a Promise; r=ochameau. Switch some calls to addOneTimeListener from callback-style to Promise. MozReview-Commit-ID: F9AlSvK0MAH
devtools/client/debugger/debugger-controller.js
devtools/client/responsive.html/test/browser/head.js
devtools/server/tests/unit/head_dbg.js
devtools/shared/client/event-source.js
devtools/shared/webconsole/test/test_console_assert.html
devtools/shared/webconsole/test/test_console_group_styling.html
--- a/devtools/client/debugger/debugger-controller.js
+++ b/devtools/client/debugger/debugger-controller.js
@@ -961,50 +961,52 @@ StackFrames.prototype = {
   addMoreFrames: function () {
     this.activeThread.fillFrames(
       this.activeThread.cachedFrames.length + CALL_STACK_PAGE_SIZE);
   },
 
   /**
    * Evaluate an expression in the context of the selected frame.
    *
-   * @param string aExpression
+   * @param string expression
    *        The expression to evaluate.
-   * @param object aOptions [optional]
+   * @param object options [optional]
    *        Additional options for this client evaluation:
    *          - depth: the frame depth used for evaluation, 0 being the topmost.
    *          - meta: some meta-description for what this evaluation represents.
    * @return object
    *         A promise that is resolved when the evaluation finishes,
    *         or rejected if there was no stack frame available or some
    *         other error occurred.
    */
-  evaluate: function (aExpression, aOptions = {}) {
-    let depth = "depth" in aOptions ? aOptions.depth : this.currentFrameDepth;
+  evaluate: async function (expression, options = {}) {
+    let depth = "depth" in options
+      ? options.depth
+      : this.currentFrameDepth;
     let frame = this.activeThread.cachedFrames[depth];
     if (frame == null) {
-      return promise.reject(new Error("No stack frame available."));
+      throw new Error("No stack frame available.");
     }
 
-    let deferred = promise.defer();
+    const onThreadPaused = this.activeThread.addOneTimeListener("paused");
+
+    let meta = "meta" in options
+      ? options.meta
+      : FRAME_TYPE.PUBLIC_CLIENT_EVAL;
+    this._currentFrameDescription = meta;
+    this.activeThread.eval(frame.actor, expression);
 
-    this.activeThread.addOneTimeListener("paused", (aEvent, aPacket) => {
-      let { type, frameFinished } = aPacket.why;
-      if (type == "clientEvaluated") {
-        deferred.resolve(frameFinished);
-      } else {
-        deferred.reject(new Error("Active thread paused unexpectedly."));
-      }
-    });
+    const packet = await onThreadPaused;
 
-    let meta = "meta" in aOptions ? aOptions.meta : FRAME_TYPE.PUBLIC_CLIENT_EVAL;
-    this._currentFrameDescription = meta;
-    this.activeThread.eval(frame.actor, aExpression);
+    let { type, frameFinished } = packet.why;
+    if (type !== "clientEvaluated") {
+      throw new Error("Active thread paused unexpectedly.");
+    }
 
-    return deferred.promise;
+    return frameFinished;
   },
 
   /**
    * Add nodes for special frame references in the innermost scope.
    *
    * @param Scope aScope
    *        The scope where the references will be placed into.
    * @param object aFrame
--- a/devtools/client/responsive.html/test/browser/head.js
+++ b/devtools/client/responsive.html/test/browser/head.js
@@ -332,24 +332,20 @@ function addDeviceForTest(device) {
   addDevice(device);
 
   registerCleanupFunction(() => {
     // Note that assertions in cleanup functions are not displayed unless they failed.
     ok(removeDevice(device), `Removed Test Device "${device.name}" from the list.`);
   });
 }
 
-function waitForClientClose(ui) {
-  return new Promise(resolve => {
-    info("Waiting for RDM debugger client to close");
-    ui.client.addOneTimeListener("closed", () => {
-      info("RDM's debugger client is now closed");
-      resolve();
-    });
-  });
+async function waitForClientClose(ui) {
+  info("Waiting for RDM debugger client to close");
+  await ui.client.addOneTimeListener("closed");
+  info("RDM's debugger client is now closed");
 }
 
 function* testTouchEventsOverride(ui, expected) {
   let { document } = ui.toolWindow;
   let touchButton = document.querySelector("#global-touch-simulation-button");
 
   let flag = yield ui.emulationFront.getTouchEventsOverride();
   is(flag === Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_ENABLED, expected,
--- a/devtools/server/tests/unit/head_dbg.js
+++ b/devtools/server/tests/unit/head_dbg.js
@@ -658,32 +658,29 @@ const assert = do_check_true;
  * Create a promise that is resolved on the next occurence of the given event.
  *
  * @param DebuggerClient client
  * @param String event
  * @param Function predicate
  * @returns Promise
  */
 function waitForEvent(client, type, predicate) {
+  if (!predicate) {
+    return client.addOneTimeListener(type);
+  }
+
   return new Promise(function (resolve) {
     function listener(type, packet) {
       if (!predicate(packet)) {
         return;
       }
       client.removeListener(listener);
       resolve(packet);
     }
-
-    if (predicate) {
-      client.addListener(type, listener);
-    } else {
-      client.addOneTimeListener(type, function (type, packet) {
-        resolve(packet);
-      });
-    }
+    client.addListener(type, listener);
   });
 }
 
 /**
  * Execute the action on the next tick and return a promise that is resolved on
  * the next pause.
  *
  * When using promises and Task.jsm, we often want to do an action that causes a
--- a/devtools/shared/client/event-source.js
+++ b/devtools/shared/client/event-source.js
@@ -43,23 +43,30 @@ function eventSource(proto) {
   /**
    * Add a listener to the event source for a given event. The
    * listener will be removed after it is called for the first time.
    *
    * @param name string
    *        The event to listen for.
    * @param listener function
    *        Called when the event is fired.
+   * @returns Promise
+   *          Resolved with an array of the arguments of the event.
    */
   proto.addOneTimeListener = function (name, listener) {
-    let l = (...args) => {
-      this.removeListener(name, l);
-      listener.apply(null, args);
-    };
-    this.addListener(name, l);
+    return new Promise(resolve => {
+      let l = (eventName, ...rest) => {
+        this.removeListener(name, l);
+        if (listener) {
+          listener(eventName, ...rest);
+        }
+        resolve(rest[0]);
+      };
+      this.addListener(name, l);
+    });
   };
 
   /**
    * Remove a listener from the event source previously added with
    * addListener().
    *
    * @param name string
    *        The event name used during addListener to add the listener.
--- a/devtools/shared/webconsole/test/test_console_assert.html
+++ b/devtools/shared/webconsole/test/test_console_assert.html
@@ -15,22 +15,17 @@ window.onload = async function () {
   let state;
   try {
     state = await new Promise(resolve =>
       attachConsole(["ConsoleAPI"], resolve)
     );
     const {dbgClient} = state;
 
     const consoleAPICall = consoleCall => {
-      const onConsoleAPICall = new Promise((resolve, reject) => {
-        dbgClient.addOneTimeListener(
-          "consoleAPICall",
-          (type, packet) => resolve({type, packet})
-        );
-      });
+      const onConsoleAPICall = dbgClient.addOneTimeListener("consoleAPICall");
       consoleCall();
       return onConsoleAPICall;
     };
 
     await testFalseAssert(consoleAPICall);
     await testFalsyAssert(consoleAPICall);
     await testUndefinedAssert(consoleAPICall);
     await testNullAssert(consoleAPICall);
@@ -40,47 +35,47 @@ window.onload = async function () {
     ok(false, `Error thrown: ${e.message}`);
   }
 
   closeDebugger(state, () => SimpleTest.finish());
 };
 
 async function testFalseAssert(consoleAPICall) {
   info(`Testing console.assert(false)`);
-  let {packet} = await consoleAPICall(() =>
+  const packet = await consoleAPICall(() =>
     top.console.assert(false, "assertion is false"));
 
   checkConsoleAPICall(packet.message, {
     arguments: ["assertion is false"]
   });
 }
 
 async function testFalsyAssert(consoleAPICall) {
   info(`Testing console.assert(0")`);
-  let {packet} = await consoleAPICall(() =>
+  const packet = await consoleAPICall(() =>
     top.console.assert(0, "assertion is false"));
 
   checkConsoleAPICall(packet.message, {
     arguments: ["assertion is false"]
   });
 }
 
 async function testUndefinedAssert(consoleAPICall) {
   info(`Testing console.assert(undefined)`);
-  let {packet} = await consoleAPICall(() =>
+  const packet = await consoleAPICall(() =>
     top.console.assert(undefined, "assertion is false"));
 
   checkConsoleAPICall(packet.message, {
     arguments: ["assertion is false"]
   });
 }
 
 async function testNullAssert(consoleAPICall) {
   info(`Testing console.assert(null)`);
-  let {packet} = await consoleAPICall(() =>
+  const packet = await consoleAPICall(() =>
     top.console.assert(null, "assertion is false"));
 
   checkConsoleAPICall(packet.message, {
     arguments: ["assertion is false"]
   });
 }
 
 async function testTrueAssert(consoleAPICall) {
--- a/devtools/shared/webconsole/test/test_console_group_styling.html
+++ b/devtools/shared/webconsole/test/test_console_group_styling.html
@@ -33,99 +33,94 @@ window.onload = async function () {
     ok(false, `Error thrown: ${e.message}`);
   }
 
   closeDebugger(state, () => SimpleTest.finish());
 };
 
 async function testSingleCustomStyleGroup(debuggerClient) {
   info("Testing console.group with a custom style");
-  let {packet} = await consoleAPICall(
+  let packet = await consoleAPICall(
     debuggerClient,
     () => top.console.group("%cfoobar", "color:red")
   );
 
   checkConsoleAPICall(packet.message, {
     arguments: ["foobar"],
     styles: ["color:red"]
   });
 }
 
 async function testSingleCustomStyleGroupCollapsed(debuggerClient) {
   info("Testing console.groupCollapsed with a custom style");
-  let {packet} = await consoleAPICall(
+  let packet = await consoleAPICall(
     debuggerClient,
     () => top.console.groupCollapsed("%cfoobaz", "color:blue")
   );
 
   checkConsoleAPICall(packet.message, {
     arguments: ["foobaz"],
     styles: ["color:blue"]
   });
 }
 
 async function testMultipleCustomStyleGroup(debuggerClient) {
   info("Testing console.group with multiple custom styles");
-  let {packet} = await consoleAPICall(
+  let packet = await consoleAPICall(
     debuggerClient,
     () => top.console.group("%cfoo%cbar", "color:red", "color:blue")
   );
 
   checkConsoleAPICall(packet.message, {
     arguments: ["foo", "bar"],
     styles: ["color:red", "color:blue"]
   });
 }
 
 async function testMultipleCustomStyleGroupCollapsed(debuggerClient) {
   info("Testing console.groupCollapsed with multiple custom styles");
-  let {packet} = await consoleAPICall(
+  let packet = await consoleAPICall(
     debuggerClient,
     () => top.console.group("%cfoo%cbaz", "color:red", "color:green")
   );
 
   checkConsoleAPICall(packet.message, {
     arguments: ["foo", "baz"],
     styles: ["color:red", "color:green"]
   });
 }
 
 async function testFormatterWithNoStyleGroup(debuggerClient) {
   info("Testing console.group with one formatter but no style");
-  let {packet} = await consoleAPICall(
+  let packet = await consoleAPICall(
     debuggerClient,
     () => top.console.group("%cfoobar")
   );
 
   checkConsoleAPICall(packet.message, {
     arguments: ["%cfoobar"],
     styles: []
   });
 }
 
 async function testFormatterWithNoStyleGroupCollapsed(debuggerClient) {
   info("Testing console.groupCollapsed with one formatter but no style");
-  let {packet} = await consoleAPICall(
+  let packet = await consoleAPICall(
     debuggerClient,
     () => top.console.groupCollapsed("%cfoobaz")
   );
 
   checkConsoleAPICall(packet.message, {
     arguments: ["%cfoobaz"],
     styles: []
   });
 }
 
 function consoleAPICall(debuggerClient, consoleCall) {
-  const onConsoleAPICall = new Promise((resolve, reject) => {
-    debuggerClient.addOneTimeListener(
-      "consoleAPICall",
-      (type, packet) => resolve({type, packet})
-    );
-  });
+  const onConsoleAPICall = debuggerClient.addOneTimeListener("consoleAPICall");
   consoleCall();
   return onConsoleAPICall;
 }
 
   </script>
 </head>
 <body>
   <p id="display"></p>