Bug 1463635: Part 1 - Use slightly less dodgy, WebExtension-compatible bootstrap monitor for temporary add-on tests. r?aswan draft
authorKris Maglione <maglione.k@gmail.com>
Tue, 22 May 2018 19:51:48 -0700
changeset 800400 f5a46ff56aac89e74368a83de0a2fa3af5643d0e
parent 800351 5891376150c9754d55ca6bce21da5e58f1c64aed
child 800401 33f0db96230914ac1d52cf3f6e7a8baa0a8019b8
child 806326 ce48b0aaade09cea5c4811134e85b4d23677f972
push id111340
push usermaglione.k@gmail.com
push dateSun, 27 May 2018 23:50:23 +0000
reviewersaswan
bugs1463635
milestone62.0a1
Bug 1463635: Part 1 - Use slightly less dodgy, WebExtension-compatible bootstrap monitor for temporary add-on tests. r?aswan MozReview-Commit-ID: CHq1nV8rEv4
toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/test/xpcshell/head_addons.js
toolkit/mozapps/extensions/test/xpcshell/test_temporary.js
--- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
@@ -744,16 +744,29 @@ var AddonTestUtils = {
 
 
     if (newVersion)
       this.appInfo.version = newVersion;
 
     let XPIScope = ChromeUtils.import("resource://gre/modules/addons/XPIProvider.jsm", null);
     XPIScope.AsyncShutdown = MockAsyncShutdown;
 
+    XPIScope.XPIInternal.BootstrapScope.prototype
+      ._beforeCallBootstrapMethod = (method, params, reason) => {
+        try {
+          this.emit("bootstrap-method", {method, params, reason});
+        } catch (e) {
+          try {
+            this.testScope.do_throw(e);
+          } catch (e) {
+            // Le sigh.
+          }
+        }
+      };
+
     this.addonIntegrationService = Cc["@mozilla.org/addons/integration;1"]
           .getService(Ci.nsIObserver);
 
     this.addonIntegrationService.observe(null, "addons-startup", null);
 
     this.emit("addon-manager-started");
 
     // Load the add-ons list as it was after extension registration
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -1569,16 +1569,18 @@ class BootstrapScope {
       }
 
       let result;
       if (!method) {
         logger.warn(`Add-on ${addon.id} is missing bootstrap method ${aMethod}`);
       } else {
         logger.debug(`Calling bootstrap method ${aMethod} on ${addon.id} version ${addon.version}`);
 
+        this._beforeCallBootstrapMethod(aMethod, params, aReason);
+
         try {
           result = method.call(scope, params, aReason);
         } catch (e) {
           logger.warn(`Exception running bootstrap method ${aMethod} on ${addon.id}`, e);
         }
       }
       return result;
     } finally {
@@ -1592,16 +1594,19 @@ class BootstrapScope {
       if (addon.type == "extension" && aMethod == "shutdown" &&
           aReason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
         logger.debug(`Removing manifest for ${this.file.path}`);
         Components.manager.removeBootstrappedManifestLocation(this.file);
       }
     }
   }
 
+  // No-op method to be overridden by tests.
+  _beforeCallBootstrapMethod() {}
+
   /**
    * Loads a bootstrapped add-on's bootstrap.js into a sandbox and the reason
    * values as constants in the scope.
    *
    * @param {integer?} [aReason]
    *        The reason this bootstrap is being loaded, as passed to a
    *        bootstrap method.
    */
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -98,16 +98,29 @@ const {
 } = AddonTestUtils;
 
 // WebExtension wrapper for ease of testing
 ExtensionTestUtils.init(this);
 
 AddonTestUtils.init(this);
 AddonTestUtils.overrideCertDB();
 
+XPCOMUtils.defineLazyGetter(this, "BOOTSTRAP_REASONS",
+                            () => AddonManagerPrivate.BOOTSTRAP_REASONS);
+
+function getReasonName(reason) {
+  for (let key of Object.keys(BOOTSTRAP_REASONS)) {
+    if (BOOTSTRAP_REASONS[key] == reason) {
+      return key;
+    }
+  }
+  throw new Error("This shouldn't happen.");
+}
+
+
 Object.defineProperty(this, "gAppInfo", {
   get() {
     return AddonTestUtils.appInfo;
   },
 });
 
 Object.defineProperty(this, "gAddonStartup", {
   get() {
@@ -346,16 +359,138 @@ this.BootstrapMonitor = {
         resolve();
       this.startupPromises = [];
     }
   }
 };
 
 AddonTestUtils.on("addon-manager-shutdown", () => BootstrapMonitor.shutdownCheck());
 
+var SlightlyLessDodgyBootstrapMonitor = {
+  started: new Map(),
+  stopped: new Map(),
+  installed: new Map(),
+  uninstalled: new Map(),
+
+  init() {
+    this.onEvent = this.onEvent.bind(this);
+
+    AddonTestUtils.on("addon-manager-shutdown", this.onEvent);
+    AddonTestUtils.on("bootstrap-method", this.onEvent);
+  },
+
+  shutdownCheck() {
+    equal(this.started.size, 0,
+          "Should have no add-ons that were started but not shutdown");
+  },
+
+  onEvent(msg, data) {
+    switch (msg) {
+      case "addon-manager-shutdown":
+        this.shutdownCheck();
+        break;
+      case "bootstrap-method":
+        this.onBootstrapMethod(data.method, data.params, data.reason);
+        break;
+    }
+  },
+
+  onBootstrapMethod(method, params, reason) {
+    let {id} = params;
+
+    info(`Bootstrap method ${method} for ${params.id} version ${params.version}`);
+
+    if (method !== "install") {
+      this.checkInstalled(id);
+    }
+
+    switch (method) {
+      case "install":
+        this.checkNotInstalled(id);
+        this.installed.set(id, {reason, params});
+        this.uninstalled.delete(id);
+        break;
+      case "startup":
+        this.checkNotStarted(id);
+        this.started.set(id, {reason, params});
+        this.stopped.delete(id);
+        break;
+      case "shutdown":
+        this.checkMatches("shutdown", "startup", params, this.started.get(id));
+        this.checkStarted(id);
+        this.stopped.set(id, {reason, params});
+        this.started.delete(id);
+        break;
+      case "uninstall":
+        this.checkMatches("uninstall", "install", params, this.installed.get(id));
+        this.uninstalled.set(id, {reason, params});
+        this.installed.delete(id);
+        break;
+      case "update":
+        // Close enough, for now.
+        this.onBootstrapMethod("uninstall",
+                               Object.assign({}, params, {version: params.oldVersion}),
+                               reason);
+        this.onBootstrapMethod("install", params, reason);
+        break;
+    }
+  },
+
+  clear(id) {
+    this.installed.delete(id);
+    this.started.delete(id);
+    this.stopped.delete(id);
+    this.uninstalled.delete(id);
+  },
+
+  checkMatches(method, lastMethod, params, {params: lastParams} = {}) {
+    ok(lastParams,
+       `Expecting matching ${lastMethod} call for add-on ${params.id} ${method} call`);
+
+    equal(params.version, lastParams.version,
+          "params.version should match last call");
+
+    equal(params.installPath.path, lastParams.installPath.path,
+          `params.installPath should match last call`);
+
+    ok(params.resourceURI.equals(lastParams.resourceURI),
+       `params.resourceURI should match: "${params.resourceURI.spec}" == "${lastParams.resourceURI.spec}"`);
+  },
+
+  checkStarted(id, version = undefined) {
+    let started = this.started.get(id);
+    ok(started, `Should have seen startup method call for ${id}`);
+
+    if (version !== undefined)
+      equal(started.params.version, version,
+            "Expected version number");
+  },
+
+  checkNotStarted(id) {
+    ok(!this.started.has(id),
+       `Should not have seen startup method call for ${id}`);
+  },
+
+  checkInstalled(id, version = undefined) {
+    const installed = this.installed.get(id);
+    ok(installed, `Should have seen install call for ${id}`);
+
+    if (version !== undefined)
+      equal(installed.params.version, version,
+            "Expected version number");
+
+    return installed;
+  },
+
+  checkNotInstalled(id) {
+    ok(!this.installed.has(id),
+       `Should not have seen install method call for ${id}`);
+  },
+};
+
 function isNightlyChannel() {
   var channel = Services.prefs.getCharPref("app.update.channel", "default");
 
   return channel != "aurora" && channel != "beta" && channel != "release" && channel != "esr";
 }
 
 
 async function restartWithLocales(locales) {
--- a/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js
@@ -14,46 +14,45 @@ const sampleRDFManifest = {
     minVersion: "1",
     maxVersion: "1"
   }],
   name: "Test Bootstrap 1 (temporary)",
 };
 
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
 
-BootstrapMonitor.init();
-
-// Partial list of bootstrap reasons from XPIProvider.jsm
-const BOOTSTRAP_REASONS = {
-  APP_STARTUP: 1,
-  ADDON_INSTALL: 5,
-  ADDON_UNINSTALL: 6,
-  ADDON_UPGRADE: 7,
-  ADDON_DOWNGRADE: 8,
-};
-
 function waitForBootstrapEvent(expectedEvent, addonId) {
   return new Promise(resolve => {
-    const observer = {
-      observe: (subject, topic, data) => {
-        const info = JSON.parse(data);
-        const targetAddonId = info.data.id;
-        if (targetAddonId === addonId && info.event === expectedEvent) {
-          resolve(info);
-          Services.obs.removeObserver(observer, "bootstrapmonitor-event");
-        } else {
-          info(
-            `Ignoring bootstrap event: ${info.event} for ${targetAddonId}`);
-        }
-      },
-    };
-    Services.obs.addObserver(observer, "bootstrapmonitor-event");
+    function listener(msg, {method, params, reason}) {
+      if (params.id === addonId && method === expectedEvent) {
+        resolve({params, method, reason});
+        AddonTestUtils.off("bootstrap-method", listener);
+      } else {
+        info(`Ignoring bootstrap event: ${method} for ${params.id}`);
+      }
+    }
+    AddonTestUtils.on("bootstrap-method", listener);
   });
 }
 
+async function checkEvent(promise, {reason, params}) {
+  let event = await promise;
+  info(`Checking bootstrap event ${event.method} for ${event.params.id}`);
+
+  equal(event.reason, reason,
+        `Expected bootstrap reason ${getReasonName(reason)} got ${getReasonName(event.reason)}`);
+
+  for (let [param, value] of Object.entries(params)) {
+    equal(event.params[param], value, `Expected value for params.${param}`);
+  }
+}
+
+let Monitor = SlightlyLessDodgyBootstrapMonitor;
+Monitor.init();
+
 // Install a temporary add-on with no existing add-on present.
 // Restart and make sure it has gone away.
 add_task(async function() {
   await promiseStartupManager();
 
   let extInstallCalled = false;
   AddonManager.addInstallListener({
     onExternalInstall: (aInstall) => {
@@ -82,72 +81,73 @@ add_task(async function() {
   });
 
   await AddonManager.installTemporaryAddon(do_get_addon("test_bootstrap1_1"));
 
   Assert.ok(extInstallCalled);
   Assert.ok(installingCalled);
   Assert.ok(installedCalled);
 
-  const install = BootstrapMonitor.checkAddonInstalled(ID, "1.0");
+  const install = Monitor.checkInstalled(ID, "1.0");
   equal(install.reason, BOOTSTRAP_REASONS.ADDON_INSTALL);
-  BootstrapMonitor.checkAddonStarted(ID, "1.0");
 
-  let info = BootstrapMonitor.started.get(ID);
+  Monitor.checkStarted(ID, "1.0");
+
+  let info = Monitor.started.get(ID);
   Assert.equal(info.reason, BOOTSTRAP_REASONS.ADDON_INSTALL);
 
   let addon = await promiseAddonByID(ID);
 
-  Assert.notEqual(addon, null);
-  Assert.equal(addon.version, "1.0");
-  Assert.equal(addon.name, "Test Bootstrap 1");
-  Assert.ok(addon.isCompatible);
-  Assert.ok(!addon.appDisabled);
-  Assert.ok(addon.isActive);
-  Assert.equal(addon.type, "extension");
-  Assert.equal(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
+  checkAddon(ID, addon, {
+    version: "1.0",
+    name: "Test Bootstrap 1",
+    isCompatible: true,
+    appDisabled: false,
+    isActive: true,
+    type: "extension",
+    signedState: mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED,
+  });
 
   let onShutdown = waitForBootstrapEvent("shutdown", ID);
   let onUninstall = waitForBootstrapEvent("uninstall", ID);
 
   await promiseRestartManager();
 
   let shutdown = await onShutdown;
   equal(shutdown.reason, BOOTSTRAP_REASONS.ADDON_UNINSTALL);
 
   let uninstall = await onUninstall;
   equal(uninstall.reason, BOOTSTRAP_REASONS.ADDON_UNINSTALL);
 
-  BootstrapMonitor.checkAddonNotInstalled(ID);
-  BootstrapMonitor.checkAddonNotStarted(ID);
+  Monitor.checkNotInstalled(ID);
+  Monitor.checkNotStarted(ID);
 
   addon = await promiseAddonByID(ID);
   Assert.equal(addon, null);
 
   await promiseRestartManager();
 });
 
 // Install a temporary add-on over the top of an existing add-on.
 // Restart and make sure the existing add-on comes back.
 add_task(async function() {
-  await promiseInstallAllFiles([do_get_addon("test_bootstrap1_2")], true);
+  let {addon} = await promiseInstallFile(do_get_addon("test_bootstrap1_2"), true);
 
-  BootstrapMonitor.checkAddonInstalled(ID, "2.0");
-  BootstrapMonitor.checkAddonStarted(ID, "2.0");
-
-  let addon = await promiseAddonByID(ID);
+  Monitor.checkInstalled(ID, "2.0");
+  Monitor.checkStarted(ID, "2.0");
 
-  Assert.notEqual(addon, null);
-  Assert.equal(addon.version, "2.0");
-  Assert.equal(addon.name, "Test Bootstrap 1");
-  Assert.ok(addon.isCompatible);
-  Assert.ok(!addon.appDisabled);
-  Assert.ok(addon.isActive);
-  Assert.equal(addon.type, "extension");
-  Assert.equal(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
+  checkAddon(ID, addon, {
+    version: "2.0",
+    name: "Test Bootstrap 1",
+    isCompatible: true,
+    appDisabled: false,
+    isActive: true,
+    type: "extension",
+    signedState: mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED,
+  });
 
   let tempdir = gTmpD.clone();
 
   let bootstrapJS = await OS.File.read("data/test_temporary/bootstrap.js", {encoding: "utf-8"});
 
   for (let newversion of ["1.0", "3.0"]) {
     for (let packed of [false, true]) {
       // ugh, file locking issues with xpis on windows
@@ -197,107 +197,133 @@ add_task(async function() {
       let onStartup = waitForBootstrapEvent("startup", ID);
 
       await AddonManager.installTemporaryAddon(target);
 
       let reason = Services.vc.compare(newversion, "2.0") < 0 ?
                    BOOTSTRAP_REASONS.ADDON_DOWNGRADE :
                    BOOTSTRAP_REASONS.ADDON_UPGRADE;
 
-      let shutdown = await onShutdown;
-      equal(shutdown.data.version, "2.0");
-      equal(shutdown.reason, reason);
+      await checkEvent(onShutdown, {
+        reason,
+        params: {
+          version: "2.0",
+        },
+      });
 
-      let uninstall = await onUninstall;
-      equal(uninstall.data.version, "2.0");
-      equal(uninstall.reason, reason);
+      await checkEvent(onUninstall, {
+        reason,
+        params: {
+          version: "2.0",
+        },
+      });
 
-      let install = await onInstall;
-      equal(install.data.version, newversion);
-      equal(install.reason, reason);
-      equal(install.data.oldVersion, "2.0");
+      await checkEvent(onInstall, {
+        reason,
+        params: {
+          version: newversion,
+          oldVersion: "2.0",
+        },
+      });
 
-      let startup = await onStartup;
-      equal(startup.data.version, newversion);
-      equal(startup.reason, reason);
-      equal(startup.data.oldVersion, "2.0");
+      await checkEvent(onStartup, {
+        reason,
+        params: {
+          version: newversion,
+          oldVersion: "2.0",
+        },
+      });
 
       addon = await promiseAddonByID(ID);
 
       let signedState = packed ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_UNKNOWN;
 
       // temporary add-on is installed and started
-      Assert.notEqual(addon, null);
-      Assert.equal(addon.version, newversion);
-      Assert.equal(addon.name, "Test Bootstrap 1 (temporary)");
-      Assert.ok(addon.isCompatible);
-      Assert.ok(!addon.appDisabled);
-      Assert.ok(addon.isActive);
-      Assert.equal(addon.type, "extension");
-      Assert.equal(addon.signedState, mozinfo.addon_signing ? signedState : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
+      checkAddon(ID, addon, {
+        version: newversion,
+        name: "Test Bootstrap 1 (temporary)",
+        isCompatible: true,
+        appDisabled: false,
+        isActive: true,
+        type: "extension",
+        signedState: mozinfo.addon_signing ? signedState : AddonManager.SIGNEDSTATE_NOT_REQUIRED,
+      });
 
       // Now restart, the temporary addon will go away which should
       // be the opposite action (ie, if the temporary addon was an
       // upgrade, then removing it is a downgrade and vice versa)
       reason = reason == BOOTSTRAP_REASONS.ADDON_UPGRADE ?
                BOOTSTRAP_REASONS.ADDON_DOWNGRADE :
                BOOTSTRAP_REASONS.ADDON_UPGRADE;
 
       onShutdown = waitForBootstrapEvent("shutdown", ID);
       onUninstall = waitForBootstrapEvent("uninstall", ID);
       onInstall = waitForBootstrapEvent("install", ID);
       onStartup = waitForBootstrapEvent("startup", ID);
 
       await promiseRestartManager();
 
-      shutdown = await onShutdown;
-      equal(shutdown.data.version, newversion);
-      equal(shutdown.reason, reason);
+      await checkEvent(onShutdown, {
+        reason,
+        params: {
+          version: newversion,
+        },
+      });
 
-      uninstall = await onUninstall;
-      equal(uninstall.data.version, newversion);
-      equal(uninstall.reason, reason);
+      await checkEvent(onUninstall, {
+        reason,
+        params: {
+          version: newversion,
+        },
+      });
 
-      install = await onInstall;
-      equal(install.data.version, "2.0");
-      equal(install.reason, reason);
-      equal(install.data.oldVersion, newversion);
+      await checkEvent(onInstall, {
+        reason,
+        params: {
+          version: "2.0",
+          oldVersion: newversion,
+        },
+      });
 
-      startup = await onStartup;
-      equal(startup.data.version, "2.0");
-      // We don't actually propagate the upgrade/downgrade reason across
-      // the browser restart when a temporary addon is removed.  See
-      // bug 1359558 for detailed reasoning.
-      equal(startup.reason, BOOTSTRAP_REASONS.APP_STARTUP);
+      await checkEvent(onStartup, {
+        // We don't actually propagate the upgrade/downgrade reason across
+        // the browser restart when a temporary addon is removed.  See
+        // bug 1359558 for detailed reasoning.
+        reason: BOOTSTRAP_REASONS.APP_STARTUP,
+        params: {
+          version: "2.0",
+        },
+      });
 
-      BootstrapMonitor.checkAddonInstalled(ID, "2.0");
-      BootstrapMonitor.checkAddonStarted(ID, "2.0");
+      Monitor.checkInstalled(ID, "2.0");
+      Monitor.checkStarted(ID, "2.0");
 
       addon = await promiseAddonByID(ID);
 
       // existing add-on is back
-      Assert.notEqual(addon, null);
-      Assert.equal(addon.version, "2.0");
-      Assert.equal(addon.name, "Test Bootstrap 1");
-      Assert.ok(addon.isCompatible);
-      Assert.ok(!addon.appDisabled);
-      Assert.ok(addon.isActive);
-      Assert.equal(addon.type, "extension");
-      Assert.equal(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
+      checkAddon(ID, addon, {
+        version: "2.0",
+        name: "Test Bootstrap 1",
+        isCompatible: true,
+        appDisabled: false,
+        isActive: true,
+        type: "extension",
+        signedState: mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED,
+      });
 
       Services.obs.notifyObservers(target, "flush-cache-entry");
       target.remove(true);
     }
   }
 
   // remove original add-on
   await addon.uninstall();
 
-  BootstrapMonitor.checkAddonNotInstalled(ID);
-  BootstrapMonitor.checkAddonNotStarted(ID);
+  Monitor.checkNotInstalled(ID);
+  Monitor.checkNotStarted(ID);
 
   await promiseRestartManager();
 });
 
 // Test that loading from the same path multiple times work
 add_task(async function test_samefile() {
   // File locking issues on Windows, ugh
   if (AppConstants.platform == "win") {
@@ -312,72 +338,72 @@ add_task(async function test_samefile() 
       applications: {
         gecko: {
           id: ID
         }
       }
     }
   });
 
-  await AddonManager.installTemporaryAddon(webext);
-  let addon = await promiseAddonByID(ID);
+  let addon = await AddonManager.installTemporaryAddon(webext);
 
   // temporary add-on is installed and started
-  Assert.notEqual(addon, null);
-  Assert.equal(addon.version, "1.0");
-  Assert.equal(addon.name, "Test WebExtension 1 (temporary)");
-  Assert.ok(addon.isCompatible);
-  Assert.ok(!addon.appDisabled);
-  Assert.ok(addon.isActive);
-  Assert.equal(addon.type, "extension");
-  Assert.equal(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
+  checkAddon(ID, addon, {
+    version: "1.0",
+    name: "Test WebExtension 1 (temporary)",
+    isCompatible: true,
+    appDisabled: false,
+    isActive: true,
+    type: "extension",
+    signedState: mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED,
+  });
 
   Services.obs.notifyObservers(webext, "flush-cache-entry");
   webext.remove(false);
   webext = createTempWebExtensionFile({
     manifest: {
       version: "2.0",
       name: "Test WebExtension 1 (temporary)",
       applications: {
         gecko: {
           id: ID
         }
       }
     }
   });
 
-  await AddonManager.installTemporaryAddon(webext);
-  addon = await promiseAddonByID(ID);
+  addon = await AddonManager.installTemporaryAddon(webext);
 
   // temporary add-on is installed and started
-  Assert.notEqual(addon, null);
-  Assert.equal(addon.version, "2.0");
-  Assert.equal(addon.name, "Test WebExtension 1 (temporary)");
-  Assert.ok(addon.isCompatible);
-  Assert.ok(!addon.appDisabled);
-  Assert.ok(addon.isActive);
-  Assert.equal(addon.type, "extension");
-  Assert.ok(addon.isWebExtension);
-  Assert.equal(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
+  checkAddon(ID, addon, {
+    version: "2.0",
+    name: "Test WebExtension 1 (temporary)",
+    isCompatible: true,
+    appDisabled: false,
+    isActive: true,
+    type: "extension",
+    isWebExtension: true,
+    signedState: mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED,
+  });
 
   await addon.uninstall();
 });
 
 // Install a temporary add-on over the top of an existing add-on.
 // Uninstall it and make sure the existing add-on comes back.
 add_task(async function() {
   // We can't install unpacked add-ons on release builds. See above.
   if (!AppConstants.MOZ_ALLOW_LEGACY_EXTENSIONS) {
     return;
   }
 
   await promiseInstallAllFiles([do_get_addon("test_bootstrap1_1")], true);
 
-  BootstrapMonitor.checkAddonInstalled(ID, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID, "1.0");
+  Monitor.checkInstalled(ID, "1.0");
+  Monitor.checkStarted(ID, "1.0");
 
   let tempdir = gTmpD.clone();
   await promiseWriteInstallRDFToDir({
     id: ID,
     version: "2.0",
     bootstrap: true,
     unpack: true,
     targetApplications: [{
@@ -415,60 +441,59 @@ add_task(async function() {
         Assert.equal(aInstall.version, "2.0");
       installedCalled = true;
     },
     onInstallStarted: (aInstall) => {
       do_throw("onInstallStarted called unexpectedly");
     }
   });
 
-  await AddonManager.installTemporaryAddon(unpacked_addon);
+  let addon = await AddonManager.installTemporaryAddon(unpacked_addon);
 
   Assert.ok(extInstallCalled);
   Assert.ok(installingCalled);
   Assert.ok(installedCalled);
 
-  let addon = await promiseAddonByID(ID);
-
-  BootstrapMonitor.checkAddonNotInstalled(ID);
-  BootstrapMonitor.checkAddonNotStarted(ID);
+  Monitor.checkNotInstalled(ID);
+  Monitor.checkNotStarted(ID);
 
   // temporary add-on is installed and started
-  Assert.notEqual(addon, null);
-  Assert.equal(addon.version, "2.0");
-  Assert.equal(addon.name, "Test Bootstrap 1 (temporary)");
-  Assert.ok(addon.isCompatible);
-  Assert.ok(!addon.appDisabled);
-  Assert.ok(addon.isActive);
-  Assert.equal(addon.type, "extension");
-  Assert.equal(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_UNKNOWN : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
+  checkAddon(ID, addon, {
+    version: "2.0",
+    name: "Test Bootstrap 1 (temporary)",
+    isCompatible: true,
+    appDisabled: false,
+    isActive: true,
+    type: "extension",
+    signedState: mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_UNKNOWN : AddonManager.SIGNEDSTATE_NOT_REQUIRED,
+  });
 
   await addon.uninstall();
 
-  await new Promise(executeSoon);
-  addon = await promiseAddonByID(ID);
+  Monitor.checkInstalled(ID);
+  Monitor.checkStarted(ID);
 
-  BootstrapMonitor.checkAddonInstalled(ID);
-  BootstrapMonitor.checkAddonStarted(ID);
+  addon = await promiseAddonByID(ID);
 
   // existing add-on is back
-  Assert.notEqual(addon, null);
-  Assert.equal(addon.version, "1.0");
-  Assert.equal(addon.name, "Test Bootstrap 1");
-  Assert.ok(addon.isCompatible);
-  Assert.ok(!addon.appDisabled);
-  Assert.ok(addon.isActive);
-  Assert.equal(addon.type, "extension");
-  Assert.equal(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
+  checkAddon(ID, addon, {
+    version: "1.0",
+    name: "Test Bootstrap 1",
+    isCompatible: true,
+    appDisabled: false,
+    isActive: true,
+    type: "extension",
+    signedState: mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED,
+  });
 
   unpacked_addon.remove(true);
   await addon.uninstall();
 
-  BootstrapMonitor.checkAddonNotInstalled(ID);
-  BootstrapMonitor.checkAddonNotStarted(ID);
+  Monitor.checkNotInstalled(ID);
+  Monitor.checkNotStarted(ID);
 
   await promiseRestartManager();
 });
 
 // Install a temporary add-on as a version upgrade over the top of an
 // existing temporary add-on.
 add_task(async function() {
   // We can't install unpacked add-ons on release builds. See above.
@@ -494,33 +519,45 @@ add_task(async function() {
   }), tempdir, "bootstrap1@tests.mozilla.org");
 
   const onShutdown = waitForBootstrapEvent("shutdown", ID);
   const onUninstall = waitForBootstrapEvent("uninstall", ID);
   const onInstall = waitForBootstrapEvent("install", ID);
   const onStartup = waitForBootstrapEvent("startup", ID);
   await AddonManager.installTemporaryAddon(unpackedAddon);
 
-  const shutdown = await onShutdown;
-  equal(shutdown.data.version, "1.0");
-  equal(shutdown.reason, BOOTSTRAP_REASONS.ADDON_UPGRADE);
+  await checkEvent(onShutdown, {
+    reason: BOOTSTRAP_REASONS.ADDON_UPGRADE,
+    params: {
+      version: "1.0",
+    },
+  });
 
-  const uninstall = await onUninstall;
-  equal(uninstall.data.version, "1.0");
-  equal(uninstall.reason, BOOTSTRAP_REASONS.ADDON_UPGRADE);
+  await checkEvent(onUninstall, {
+    reason: BOOTSTRAP_REASONS.ADDON_UPGRADE,
+    params: {
+      version: "1.0",
+    },
+  });
 
-  const install = await onInstall;
-  equal(install.data.version, "2.0");
-  equal(install.reason, BOOTSTRAP_REASONS.ADDON_UPGRADE);
-  equal(install.data.oldVersion, "1.0");
+  await checkEvent(onInstall, {
+    reason: BOOTSTRAP_REASONS.ADDON_UPGRADE,
+    params: {
+      version: "2.0",
+      oldVersion: "1.0",
+    },
+  });
 
-  const startup = await onStartup;
-  equal(startup.data.version, "2.0");
-  equal(startup.reason, BOOTSTRAP_REASONS.ADDON_UPGRADE);
-  equal(startup.data.oldVersion, "1.0");
+  await checkEvent(onStartup, {
+    reason: BOOTSTRAP_REASONS.ADDON_UPGRADE,
+    params: {
+      version: "2.0",
+      oldVersion: "1.0",
+    },
+  });
 
   const addon = await promiseAddonByID(ID);
   await addon.uninstall();
 
   unpackedAddon.remove(true);
   await promiseRestartManager();
 });
 
@@ -550,31 +587,43 @@ add_task(async function() {
   }), tempdir, "bootstrap1@tests.mozilla.org");
 
   const onShutdown = waitForBootstrapEvent("shutdown", ID);
   const onUninstall = waitForBootstrapEvent("uninstall", ID);
   const onInstall = waitForBootstrapEvent("install", ID);
   const onStartup = waitForBootstrapEvent("startup", ID);
   await AddonManager.installTemporaryAddon(unpackedAddon);
 
-  const shutdown = await onShutdown;
-  equal(shutdown.data.version, "1.0");
-  equal(shutdown.reason, BOOTSTRAP_REASONS.ADDON_DOWNGRADE);
+  await checkEvent(onShutdown, {
+    reason: BOOTSTRAP_REASONS.ADDON_DOWNGRADE,
+    params: {
+      version: "1.0",
+    },
+  });
 
-  const uninstall = await onUninstall;
-  equal(uninstall.data.version, "1.0");
-  equal(uninstall.reason, BOOTSTRAP_REASONS.ADDON_DOWNGRADE);
+  await checkEvent(onUninstall, {
+    reason: BOOTSTRAP_REASONS.ADDON_DOWNGRADE,
+    params: {
+      version: "1.0",
+    },
+  });
 
-  const install = await onInstall;
-  equal(install.data.version, "0.8");
-  equal(install.reason, BOOTSTRAP_REASONS.ADDON_DOWNGRADE);
+  await checkEvent(onInstall, {
+    reason: BOOTSTRAP_REASONS.ADDON_DOWNGRADE,
+    params: {
+      version: "0.8",
+    },
+  });
 
-  const startup = await onStartup;
-  equal(startup.data.version, "0.8");
-  equal(startup.reason, BOOTSTRAP_REASONS.ADDON_DOWNGRADE);
+  await checkEvent(onStartup, {
+    reason: BOOTSTRAP_REASONS.ADDON_DOWNGRADE,
+    params: {
+      version: "0.8",
+    },
+  });
 
   const addon = await promiseAddonByID(ID);
   await addon.uninstall();
 
   unpackedAddon.remove(true);
   await promiseRestartManager();
 });
 
@@ -594,76 +643,92 @@ add_task(async function() {
   unpackedAddon.append(ID);
   do_get_file("data/test_temporary/bootstrap.js")
     .copyTo(unpackedAddon, "bootstrap.js");
 
   const onInitialInstall = waitForBootstrapEvent("install", ID);
   const onInitialStartup = waitForBootstrapEvent("startup", ID);
   await AddonManager.installTemporaryAddon(unpackedAddon);
 
-  const initialInstall = await onInitialInstall;
-  equal(initialInstall.data.version, "1.0");
-  equal(initialInstall.reason, BOOTSTRAP_REASONS.ADDON_INSTALL);
+  await checkEvent(onInitialInstall, {
+    reason: BOOTSTRAP_REASONS.ADDON_INSTALL,
+    params: {
+      version: "1.0",
+    },
+  });
 
-  const initialStartup = await onInitialStartup;
-  equal(initialStartup.data.version, "1.0");
-  equal(initialStartup.reason, BOOTSTRAP_REASONS.ADDON_INSTALL);
+  await checkEvent(onInitialStartup, {
+    reason: BOOTSTRAP_REASONS.ADDON_INSTALL,
+    params: {
+      version: "1.0",
+    },
+  });
 
-  let info = BootstrapMonitor.started.get(ID);
+  let info = Monitor.started.get(ID);
   Assert.equal(info.reason, BOOTSTRAP_REASONS.ADDON_INSTALL);
 
   // Install it again.
   const onShutdown = waitForBootstrapEvent("shutdown", ID);
   const onUninstall = waitForBootstrapEvent("uninstall", ID);
   const onInstall = waitForBootstrapEvent("install", ID);
   const onStartup = waitForBootstrapEvent("startup", ID);
   await AddonManager.installTemporaryAddon(unpackedAddon);
 
-  const shutdown = await onShutdown;
-  equal(shutdown.data.version, "1.0");
-  equal(shutdown.reason, BOOTSTRAP_REASONS.ADDON_UPGRADE);
+  await checkEvent(onShutdown, {
+    reason: BOOTSTRAP_REASONS.ADDON_UPGRADE,
+    params: {
+      version: "1.0",
+    },
+  });
 
-  const uninstall = await onUninstall;
-  equal(uninstall.data.version, "1.0");
-  equal(uninstall.reason, BOOTSTRAP_REASONS.ADDON_UPGRADE);
+  await checkEvent(onUninstall, {
+    reason: BOOTSTRAP_REASONS.ADDON_UPGRADE,
+    params: {
+      version: "1.0",
+    },
+  });
 
-  const reInstall = await onInstall;
-  equal(reInstall.data.version, "1.0");
-  equal(reInstall.reason, BOOTSTRAP_REASONS.ADDON_UPGRADE);
+  await checkEvent(onInstall, {
+    reason: BOOTSTRAP_REASONS.ADDON_UPGRADE,
+    params: {
+      version: "1.0",
+    },
+  });
 
-  const startup = await onStartup;
-  equal(startup.data.version, "1.0");
-  equal(startup.reason, BOOTSTRAP_REASONS.ADDON_UPGRADE);
+  await checkEvent(onStartup, {
+    reason: BOOTSTRAP_REASONS.ADDON_UPGRADE,
+    params: {
+      version: "1.0",
+    },
+  });
 
   const addon = await promiseAddonByID(ID);
   await addon.uninstall();
 
   unpackedAddon.remove(true);
   await promiseRestartManager();
 });
 
 // Install a temporary add-on over the top of an existing disabled add-on.
 // After restart, the existing add-on should continue to be installed and disabled.
 add_task(async function() {
   // We can't install unpacked add-ons on release builds. See above.
   if (!AppConstants.MOZ_ALLOW_LEGACY_EXTENSIONS) {
     return;
   }
 
-  await promiseInstallAllFiles([do_get_addon("test_bootstrap1_1")], true);
+  let {addon} = await promiseInstallFile(do_get_addon("test_bootstrap1_1"), true);
 
-  BootstrapMonitor.checkAddonInstalled(ID, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID, "1.0");
-
-  let addon = await promiseAddonByID(ID);
+  Monitor.checkInstalled(ID, "1.0");
+  Monitor.checkStarted(ID, "1.0");
 
   await addon.disable();
 
-  BootstrapMonitor.checkAddonInstalled(ID, "1.0");
-  BootstrapMonitor.checkAddonNotStarted(ID);
+  Monitor.checkInstalled(ID, "1.0");
+  Monitor.checkNotStarted(ID);
 
   let tempdir = gTmpD.clone();
   await promiseWriteInstallRDFToDir({
     id: ID,
     version: "2.0",
     bootstrap: true,
     unpack: true,
     targetApplications: [{
@@ -683,112 +748,114 @@ add_task(async function() {
   AddonManager.addInstallListener({
     onExternalInstall: (aInstall) => {
       Assert.equal(aInstall.id, ID);
       Assert.equal(aInstall.version, "2.0");
       extInstallCalled = true;
     },
   });
 
-  await AddonManager.installTemporaryAddon(unpacked_addon);
+  let tempAddon = await AddonManager.installTemporaryAddon(unpacked_addon);
 
   Assert.ok(extInstallCalled);
 
-  let tempAddon = await promiseAddonByID(ID);
-
-  BootstrapMonitor.checkAddonInstalled(ID, "2.0");
-  BootstrapMonitor.checkAddonStarted(ID);
+  Monitor.checkInstalled(ID, "2.0");
+  Monitor.checkStarted(ID);
 
   // temporary add-on is installed and started
-  Assert.notEqual(tempAddon, null);
-  Assert.equal(tempAddon.version, "2.0");
-  Assert.equal(tempAddon.name, "Test Bootstrap 1 (temporary)");
-  Assert.ok(tempAddon.isCompatible);
-  Assert.ok(!tempAddon.appDisabled);
-  Assert.ok(tempAddon.isActive);
-  Assert.equal(tempAddon.type, "extension");
-  Assert.equal(tempAddon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_UNKNOWN : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
+  checkAddon(ID, tempAddon, {
+    version: "2.0",
+    name: "Test Bootstrap 1 (temporary)",
+    isCompatible: true,
+    appDisabled: false,
+    isActive: true,
+    type: "extension",
+    signedState: mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_UNKNOWN : AddonManager.SIGNEDSTATE_NOT_REQUIRED,
+  });
 
   await tempAddon.uninstall();
   unpacked_addon.remove(true);
 
   await addon.enable();
   await new Promise(executeSoon);
   addon = await promiseAddonByID(ID);
 
-  BootstrapMonitor.checkAddonInstalled(ID, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID);
+  Monitor.checkInstalled(ID, "1.0");
+  Monitor.checkStarted(ID);
 
   // existing add-on is back
-  Assert.notEqual(addon, null);
-  Assert.equal(addon.version, "1.0");
-  Assert.equal(addon.name, "Test Bootstrap 1");
-  Assert.ok(addon.isCompatible);
-  Assert.ok(!addon.appDisabled);
-  Assert.ok(addon.isActive);
-  Assert.equal(addon.type, "extension");
-  Assert.equal(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
+  checkAddon(ID, addon, {
+    version: "1.0",
+    name: "Test Bootstrap 1",
+    isCompatible: true,
+    appDisabled: false,
+    isActive: true,
+    type: "extension",
+    signedState: mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED,
+  });
 
   await addon.uninstall();
 
-  BootstrapMonitor.checkAddonNotInstalled(ID);
-  BootstrapMonitor.checkAddonNotStarted(ID);
+  Monitor.checkNotInstalled(ID);
+  Monitor.checkNotStarted(ID);
 
   await promiseRestartManager();
 });
 
 // Installing a temporary add-on when there is already a temporary
 // add-on should fail.
 add_task(async function() {
   await AddonManager.installTemporaryAddon(do_get_addon("test_bootstrap1_1"));
 
   let addon = await promiseAddonByID(ID);
 
-  BootstrapMonitor.checkAddonInstalled(ID, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID, "1.0");
+  Monitor.checkInstalled(ID, "1.0");
+  Monitor.checkStarted(ID, "1.0");
 
-  Assert.notEqual(addon, null);
-  Assert.equal(addon.version, "1.0");
-  Assert.equal(addon.name, "Test Bootstrap 1");
-  Assert.ok(addon.isCompatible);
-  Assert.ok(!addon.appDisabled);
-  Assert.ok(addon.isActive);
-  Assert.equal(addon.type, "extension");
-  Assert.ok(!addon.isWebExtension);
-  Assert.equal(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
+  checkAddon(ID, addon, {
+    version: "1.0",
+    name: "Test Bootstrap 1",
+    isCompatible: true,
+    appDisabled: false,
+    isActive: true,
+    type: "extension",
+    isWebExtension: false,
+    signedState: mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED,
+  });
 
   await AddonManager.installTemporaryAddon(do_get_addon("test_bootstrap1_1"));
 
-  BootstrapMonitor.checkAddonInstalled(ID, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID, "1.0");
+  Monitor.checkInstalled(ID, "1.0");
+  Monitor.checkStarted(ID, "1.0");
 
   await promiseRestartManager();
 
-  BootstrapMonitor.checkAddonNotInstalled(ID);
-  BootstrapMonitor.checkAddonNotStarted(ID);
+  Monitor.checkNotInstalled(ID);
+  Monitor.checkNotStarted(ID);
 });
 
 // Check that a temporary add-on is marked as such.
 add_task(async function() {
   await AddonManager.installTemporaryAddon(do_get_addon("test_bootstrap1_1"));
   const addon = await promiseAddonByID(ID);
 
-  notEqual(addon, null);
-  equal(addon.temporarilyInstalled, true);
+  checkAddon(ID, addon, {
+    temporarilyInstalled: true,
+  });
 
   await promiseRestartManager();
 });
 
 // Check that a permanent add-on is not marked as temporarily installed.
 add_task(async function() {
-  await promiseInstallAllFiles([do_get_addon("test_bootstrap1_1")], true);
-  const addon = await promiseAddonByID(ID);
+  let {addon} = await promiseInstallFile(do_get_addon("test_bootstrap1_1"), true);
 
-  notEqual(addon, null);
-  equal(addon.temporarilyInstalled, false);
+  checkAddon(ID, addon, {
+    temporarilyInstalled: false,
+  });
 
   await promiseRestartManager();
 });
 
 // Tests that XPIs with a .zip extension work when loaded temporarily.
 add_task(async function test_zip_extension() {
   let xpi = createTempWebExtensionFile({
     background() {