Bug 1504756 - [marionette] Use waitForObserverTopic when waiting for observer notifications. r=ato
☠☠ backed out by 4ff41b70cbd8 ☠ ☠
authorHenrik Skupin <mail@hskupin.info>
Wed, 09 Jan 2019 18:27:25 +0000
changeset 510234 c25c93d2dc4d81827e58be861fc6e6f5cb36d9e4
parent 510233 58ab81d373b9ac3c504cc7af753204e6cf980a8a
child 510235 9715660f8c07d7e31aaf2dbb02c37103942c2206
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 waitForObserverTopic when waiting for observer notifications. r=ato Depends on D13661 Differential Revision: https://phabricator.services.mozilla.com/D13662
testing/marionette/doc/internals/sync.rst
testing/marionette/driver.js
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
@@ -15,8 +15,10 @@ Provides an assortment of synchronisatio
   :members:
 
 .. js:autoclass:: TimedPromise
   :members:
 
 .. js:autofunction:: waitForEvent
 
 .. js:autofunction:: waitForMessage
+
+.. js:autofunction:: waitForObserverTopic
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -58,16 +58,17 @@ const {MarionettePrefs} = ChromeUtils.im
 ChromeUtils.import("chrome://marionette/content/proxy.js");
 ChromeUtils.import("chrome://marionette/content/reftest.js");
 const {
   DebounceCallback,
   IdlePromise,
   PollPromise,
   TimedPromise,
   waitForEvent,
+  waitForObserverTopic,
 } = ChromeUtils.import("chrome://marionette/content/sync.js", {});
 
 XPCOMUtils.defineLazyGetter(this, "logger", Log.get);
 XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
 
 this.EXPORTED_SYMBOLS = ["GeckoDriver"];
 
 const APP_ID_FIREFOX = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
@@ -3278,25 +3279,20 @@ GeckoDriver.prototype.quit = async funct
   } else {
     mode = Ci.nsIAppStartup.eAttemptQuit;
   }
 
   this._server.acceptConnections = false;
   this.deleteSession();
 
   // delay response until the application is about to quit
-  let quitApplication = new Promise(resolve => {
-    Services.obs.addObserver(
-        (subject, topic, data) => resolve(data),
-        "quit-application");
-  });
-
+  let quitApplication = waitForObserverTopic("quit-application");
   Services.startup.quit(mode);
 
-  return {cause: await quitApplication};
+  return {cause: (await quitApplication).data};
 };
 
 GeckoDriver.prototype.installAddon = function(cmd) {
   assert.desktop();
 
   let path = cmd.parameters.path;
   let temp = cmd.parameters.temporary || false;
   if (typeof path == "undefined" || typeof path != "string" ||
--- a/testing/marionette/sync.js
+++ b/testing/marionette/sync.js
@@ -23,16 +23,17 @@ this.EXPORTED_SYMBOLS = [
   "DebounceCallback",
   "IdlePromise",
   "MessageManagerDestroyedPromise",
   "PollPromise",
   "Sleep",
   "TimedPromise",
   "waitForEvent",
   "waitForMessage",
+  "waitForObserverTopic",
 ];
 
 const {TYPE_ONE_SHOT, TYPE_REPEATING_SLACK} = Ci.nsITimer;
 
 const PROMISE_TIMEOUT = AppConstants.DEBUG ? 4500 : 1500;
 
 
 /**
@@ -506,8 +507,56 @@ function waitForMessage(messageManager, 
       if (checkFn && !checkFn(msg)) {
         return;
       }
       messageManager.removeMessageListener(messageName, onMessage);
       resolve(msg.data);
     });
   });
 }
+
+/**
+ * Wait for the specified observer topic to be observed.
+ *
+ * This method has been duplicated from TestUtils.jsm.
+ *
+ * Because this function is intended for testing, any error in checkFn
+ * will cause the returned promise to be rejected instead of waiting for
+ * the next notification, since this is probably a bug in the test.
+ *
+ * @param {string} topic
+ *     The topic to observe.
+ * @param {Object=} options
+ *     Extra options.
+ * @param {function(String,Object)=} options.checkFn
+ *     Called with ``subject``, and ``data`` as arguments, should return true
+ *     if the notification is the expected one, or false if it should be
+ *     ignored and listening should continue. If not specified, the first
+ *     notification for the specified topic resolves the returned promise.
+ *
+ * @return {Promise.<Array<String, Object>>}
+ *     Promise which resolves to an array of ``subject``, and ``data`` from
+ *     the observed notification.
+ */
+function waitForObserverTopic(topic, {checkFn = null} = {}) {
+  if (typeof topic != "string") {
+    throw new TypeError();
+  }
+  if (checkFn != null && typeof checkFn != "function") {
+    throw new TypeError();
+  }
+
+  return new Promise((resolve, reject) => {
+    Services.obs.addObserver(function observer(subject, topic, data) {
+      log.trace(`Received observer notification ${topic}`);
+      try {
+        if (checkFn && !checkFn(subject, data)) {
+          return;
+        }
+        Services.obs.removeObserver(observer, topic);
+        resolve({subject, data});
+      } catch (ex) {
+        Services.obs.removeObserver(observer, topic);
+        reject(ex);
+      }
+    }, topic);
+  });
+}
--- a/testing/marionette/test/unit/test_sync.js
+++ b/testing/marionette/test/unit/test_sync.js
@@ -1,20 +1,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
 const {
   DebounceCallback,
   IdlePromise,
   PollPromise,
   Sleep,
   TimedPromise,
   waitForEvent,
   waitForMessage,
+  waitForObserverTopic,
 } = ChromeUtils.import("chrome://marionette/content/sync.js", {});
 
 const DEFAULT_TIMEOUT = 2000;
 
 /**
  * Mimic a DOM node for listening for events.
  */
 class MockElement {
@@ -418,8 +421,41 @@ add_task(async function test_waitForMess
 
     messageManager = new MessageManager();
     let sent = waitForMessage(messageManager, "message", {checkFn});
     messageManager.send("message", data1);
     messageManager.send("message", data2);
     equal(expected_data, await sent);
   }
 });
+
+add_task(async function test_waitForObserverTopic_topicTypes() {
+  for (let topic of [42, null, undefined, true, [], {}]) {
+    Assert.throws(() => waitForObserverTopic(topic), /TypeError/);
+  }
+
+  let data = {"foo": "bar"};
+  let sent = waitForObserverTopic("message");
+  Services.obs.notifyObservers(this, "message", data);
+  let result = await sent;
+  equal(this, result.subject);
+  equal(data, result.data);
+});
+
+add_task(async function test_waitForObserverTopic_checkFnTypes() {
+  for (let checkFn of ["foo", 42, true, [], {}]) {
+    Assert.throws(() => waitForObserverTopic(
+        "message", {checkFn}), /TypeError/);
+  }
+
+  let data1 = {"fo": "bar"};
+  let data2 = {"foo": "bar"};
+
+  for (let checkFn of [null, undefined, (subject, data) => data == data2]) {
+    let expected_data = (checkFn == null) ? data1 : data2;
+
+    let sent = waitForObserverTopic("message");
+    Services.obs.notifyObservers(this, "message", data1);
+    Services.obs.notifyObservers(this, "message", data2);
+    let result = await sent;
+    equal(expected_data, result.data);
+  }
+});