Bug 1504756 - [marionette] Use waitForMessage() to wait for an expected message manager message. r=ato
☠☠ backed out by 4ff41b70cbd8 ☠ ☠
authorHenrik Skupin <mail@hskupin.info>
Wed, 09 Jan 2019 18:22:57 +0000
changeset 510233 58ab81d373b9ac3c504cc7af753204e6cf980a8a
parent 510232 21658a2d0174439bfe8f72337d03bc4265e0105e
child 510234 c25c93d2dc4d81827e58be861fc6e6f5cb36d9e4
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersato
bugs1504756
milestone66.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 1504756 - [marionette] Use waitForMessage() to wait for an expected message manager message. r=ato Depends on D13660 Differential Revision: https://phabricator.services.mozilla.com/D13661
testing/marionette/doc/internals/sync.rst
testing/marionette/sync.js
testing/marionette/test/unit/test_sync.js
--- a/testing/marionette/doc/internals/sync.rst
+++ b/testing/marionette/doc/internals/sync.rst
@@ -13,8 +13,10 @@ Provides an assortment of synchronisatio
 
 .. js:autoclass:: Sleep
   :members:
 
 .. js:autoclass:: TimedPromise
   :members:
 
 .. js:autofunction:: waitForEvent
+
+.. js:autofunction:: waitForMessage
--- a/testing/marionette/sync.js
+++ b/testing/marionette/sync.js
@@ -8,29 +8,31 @@ ChromeUtils.import("resource://gre/modul
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const {
   error,
   stack,
   TimeoutError,
 } = ChromeUtils.import("chrome://marionette/content/error.js", {});
+const {truncate} = ChromeUtils.import("chrome://marionette/content/format.js", {});
 const {Log} = ChromeUtils.import("chrome://marionette/content/log.js", {});
 
 XPCOMUtils.defineLazyGetter(this, "log", Log.get);
 
 this.EXPORTED_SYMBOLS = [
   "executeSoon",
   "DebounceCallback",
   "IdlePromise",
   "MessageManagerDestroyedPromise",
   "PollPromise",
   "Sleep",
   "TimedPromise",
   "waitForEvent",
+  "waitForMessage",
 ];
 
 const {TYPE_ONE_SHOT, TYPE_REPEATING_SLACK} = Ci.nsITimer;
 
 const PROMISE_TIMEOUT = AppConstants.DEBUG ? 4500 : 1500;
 
 
 /**
@@ -459,8 +461,53 @@ function waitForEvent(subject, eventName
         } catch (ex2) {
           // Maybe the provided object does not support removeEventListener.
         }
         executeSoon(() => reject(ex));
       }
     }, capture, wantsUntrusted);
   });
 }
+
+/**
+ * Wait for a message to be fired from a particular message manager.
+ *
+ * This method has been duplicated from BrowserTestUtils.jsm.
+ *
+ * @param {nsIMessageManager} messageManager
+ *     The message manager that should be used.
+ * @param {string} messageName
+ *     The message to wait for.
+ * @param {Object=} options
+ *     Extra options.
+ * @param {function(Message)=} options.checkFn
+ *     Called with the ``Message`` object as argument, should return ``true``
+ *     if the message is the expected one, or ``false`` if it should be
+ *     ignored and listening should continue. If not specified, the first
+ *     message with the specified name resolves the returned promise.
+ *
+ * @return {Promise.<Object>}
+ *     Promise which resolves to the data property of the received
+ *     ``Message``.
+ */
+function waitForMessage(messageManager, messageName,
+    {checkFn = undefined} = {}) {
+  if (messageManager == null || !("addMessageListener" in messageManager)) {
+    throw new TypeError();
+  }
+  if (typeof messageName != "string") {
+    throw new TypeError();
+  }
+  if (checkFn && typeof checkFn != "function") {
+    throw new TypeError();
+  }
+
+  return new Promise(resolve => {
+    messageManager.addMessageListener(messageName, function onMessage(msg) {
+      log.trace(`Received ${messageName} for ${msg.target}`);
+      if (checkFn && !checkFn(msg)) {
+        return;
+      }
+      messageManager.removeMessageListener(messageName, onMessage);
+      resolve(msg.data);
+    });
+  });
+}
--- a/testing/marionette/test/unit/test_sync.js
+++ b/testing/marionette/test/unit/test_sync.js
@@ -4,16 +4,17 @@
 
 const {
   DebounceCallback,
   IdlePromise,
   PollPromise,
   Sleep,
   TimedPromise,
   waitForEvent,
+  waitForMessage,
 } = ChromeUtils.import("chrome://marionette/content/sync.js", {});
 
 const DEFAULT_TIMEOUT = 2000;
 
 /**
  * Mimic a DOM node for listening for events.
  */
 class MockElement {
@@ -51,16 +52,46 @@ class MockElement {
     this.capture = false;
     this.func = null;
     this.eventName = null;
     this.untrusted = false;
   }
 }
 
 /**
+ * Mimic a message manager for sending messages.
+ */
+class MessageManager {
+  constructor() {
+    this.func = null;
+    this.message = null;
+  }
+
+  addMessageListener(message, func) {
+    this.func = func;
+    this.message = message;
+  }
+
+  removeMessageListener(message) {
+    this.func = null;
+    this.message = null;
+  }
+
+  send(message, data) {
+    if (this.func) {
+      this.func({
+        data,
+        message,
+        target: this,
+      });
+    }
+  }
+}
+
+/**
  * Mimics nsITimer, but instead of using a system clock you can
  * preprogram it to invoke the callback after a given number of ticks.
  */
 class MockTimer {
   constructor(ticksBeforeFiring) {
     this.goal = ticksBeforeFiring;
     this.ticks = 0;
     this.cancelled = false;
@@ -348,8 +379,47 @@ add_task(async function test_waitForEven
     element = new MockElement();
     let clicked = waitForEvent(element, "click", {wantsUntrusted});
     element.click();
     let event = await clicked;
     equal(element, event.target);
     equal(expected_untrusted, event.untrusted);
   }
 });
+
+add_task(async function test_waitForMessage_messageManagerAndMessageTypes() {
+  let messageManager = new MessageManager();
+
+  for (let manager of ["foo", 42, null, undefined, true, [], {}]) {
+    Assert.throws(() => waitForMessage(manager, "message"), /TypeError/);
+  }
+
+  for (let message of [42, null, undefined, true, [], {}]) {
+    Assert.throws(() => waitForEvent(messageManager, message), /TypeError/);
+  }
+
+  let data = {"foo": "bar"};
+  let sent = waitForMessage(messageManager, "message");
+  messageManager.send("message", data);
+  equal(data, await sent);
+});
+
+add_task(async function test_waitForMessage_checkFnTypes() {
+  let messageManager = new MessageManager();
+
+  for (let checkFn of ["foo", 42, true, [], {}]) {
+    Assert.throws(() => waitForMessage(
+        messageManager, "message", {checkFn}), /TypeError/);
+  }
+
+  let data1 = {"fo": "bar"};
+  let data2 = {"foo": "bar"};
+
+  for (let checkFn of [null, undefined, msg => "foo" in msg.data]) {
+    let expected_data = (checkFn == null) ? data1 : data2;
+
+    messageManager = new MessageManager();
+    let sent = waitForMessage(messageManager, "message", {checkFn});
+    messageManager.send("message", data1);
+    messageManager.send("message", data2);
+    equal(expected_data, await sent);
+  }
+});