Bug 1504756 - [marionette] Use waitForMessage() to wait for an expected message manager message. r=ato
authorHenrik Skupin <mail@hskupin.info>
Wed, 05 Dec 2018 19:57:23 +0000
changeset 508668 f23b667d8bfa4780add93901e66a71603eb2baf9
parent 508667 6068c233f4eff8bb8a4124275032fa7c604f76d8
child 508669 5c2826c58f9ef0bd89b5a5c94100a1c2d5f7e34e
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersato
bugs1504756
milestone65.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);
+  }
+});