Bug 1228359 - Allow experiments to register chrome. r=felipe, a=lizzard
authorDave Townsend <dtownsend@oxymoronical.com>
Fri, 27 Nov 2015 12:44:22 -0800
changeset 305697 e576dd92d0f27df387320ed59fce50803549828c
parent 305696 943f61d3178335be894792ee2ea12a829c04b2d2
child 305698 bb474924f23bfa27e850fd895448ccf4d85ed5c8
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfelipe, lizzard
bugs1228359
milestone44.0a2
Bug 1228359 - Allow experiments to register chrome. r=felipe, a=lizzard Simple obvious fix. Adds tests by making BootstrapMonitor (which test_experiments.js and others use for verifying bootstrap startup and shutdown) verify the list of registered chrome manifests at various points. Without the fix this makes test_experiment fail as expected.
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/test/xpcshell/head_addons.js
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -211,16 +211,17 @@ const TYPES = {
 // externally
 const TYPE_ALIASES = {
   "webextension": "extension",
 };
 
 const CHROME_TYPES = new Set([
   "extension",
   "locale",
+  "experiment",
 ]);
 
 const RESTARTLESS_TYPES = new Set([
   "webextension",
   "dictionary",
   "experiment",
   "locale",
 ]);
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -63,35 +63,68 @@ var TEST_UNPACKED = false;
 
 // Map resource://xpcshell-data/ to the data directory
 var resHandler = Services.io.getProtocolHandler("resource")
                          .QueryInterface(AM_Ci.nsISubstitutingProtocolHandler);
 // Allow non-existent files because of bug 1207735
 var dataURI = NetUtil.newURI(do_get_file("data", true));
 resHandler.setSubstitution("xpcshell-data", dataURI);
 
+function isManifestRegistered(file) {
+  let manifests = Components.manager.getManifestLocations();
+  for (let i = 0; i < manifests.length; i++) {
+    let manifest = manifests.queryElementAt(i, AM_Ci.nsIURI);
+
+    // manifest is the url to the manifest file either in an XPI or a directory.
+    // We want the location of the XPI or directory itself.
+    if (manifest instanceof AM_Ci.nsIJARURI) {
+      manifest = manifest.JARFile.QueryInterface(AM_Ci.nsIFileURL).file;
+    }
+    else if (manifest instanceof AM_Ci.nsIFileURL) {
+      manifest = manifest.file.parent;
+    }
+    else {
+      continue;
+    }
+
+    if (manifest.equals(file))
+      return true;
+  }
+  return false;
+}
+
 // Listens to messages from bootstrap.js telling us what add-ons were started
 // and stopped etc. and performs some sanity checks that only installed add-ons
 // are started etc.
 this.BootstrapMonitor = {
+  inited: false,
+
   // Contain the current state of add-ons in the system
   installed: new Map(),
   started: new Map(),
 
   // Contain the last state of shutdown and uninstall calls for an add-on
   stopped: new Map(),
   uninstalled: new Map(),
 
   startupPromises: [],
   installPromises: [],
 
   init() {
+    this.inited = true;
     Services.obs.addObserver(this, "bootstrapmonitor-event", false);
   },
 
+  shutdownCheck() {
+    if (!this.inited)
+      return;
+
+    do_check_eq(this.started.size, 0);
+  },
+
   clear(id) {
     this.installed.delete(id);
     this.started.delete(id);
     this.stopped.delete(id);
     this.uninstalled.delete(id);
   },
 
   promiseAddonStartup(id) {
@@ -113,16 +146,21 @@ this.BootstrapMonitor = {
     do_check_eq(current.data.resourceURI, cached.data.resourceURI);
   },
 
   checkAddonStarted(id, version = undefined) {
     let started = this.started.get(id);
     do_check_neq(started, undefined);
     if (version != undefined)
       do_check_eq(started.data.version, version);
+
+    // Chrome should be registered by now
+    let installPath = new FileUtils.File(started.data.installPath);
+    let isRegistered = isManifestRegistered(installPath);
+    do_check_true(isRegistered);
   },
 
   checkAddonNotStarted(id) {
     do_check_false(this.started.has(id));
   },
 
   checkAddonInstalled(id, version = undefined) {
     let installed = this.installed.get(id);
@@ -133,16 +171,17 @@ this.BootstrapMonitor = {
 
   checkAddonNotInstalled(id) {
     do_check_false(this.installed.has(id));
   },
 
   observe(subject, topic, data) {
     let info = JSON.parse(data);
     let id = info.data.id;
+    let installPath = new FileUtils.File(info.data.installPath);
 
     // If this is the install event the add-ons shouldn't already be installed
     if (info.event == "install") {
       this.checkAddonNotInstalled(id);
 
       this.installed.set(id, info);
 
       for (let resolve of this.installPromises)
@@ -154,28 +193,46 @@ this.BootstrapMonitor = {
     }
 
     // If this is the shutdown event than the add-on should already be started
     if (info.event == "shutdown") {
       this.checkMatches(this.started.get(id), info);
 
       this.started.delete(id);
       this.stopped.set(id, info);
+
+      // Chrome should still be registered at this point
+      let isRegistered = isManifestRegistered(installPath);
+      do_check_true(isRegistered);
+
+      // XPIProvider doesn't bother unregistering chrome on app shutdown but
+      // since we simulate restarts we must do so manually to keep the registry
+      // consistent.
+      if (info.reason == 2 /* APP_SHUTDOWN */)
+        Components.manager.removeBootstrappedManifestLocation(installPath);
     }
     else {
       this.checkAddonNotStarted(id);
     }
 
     if (info.event == "uninstall") {
+      // Chrome should be unregistered at this point
+      let isRegistered = isManifestRegistered(installPath);
+      do_check_false(isRegistered);
+
       this.installed.delete(id);
       this.uninstalled.set(id, info)
     }
     else if (info.event == "startup") {
       this.started.set(id, info);
 
+      // Chrome should be registered at this point
+      let isRegistered = isManifestRegistered(installPath);
+      do_check_true(isRegistered);
+
       for (let resolve of this.startupPromises)
         resolve();
       this.startupPromises = [];
     }
   }
 }
 
 function isNightlyChannel() {
@@ -577,16 +634,17 @@ function promiseShutdownManager() {
     return Promise.resolve(false);
   }
 
   let hookErr = null;
   Services.obs.notifyObservers(null, "quit-application-granted", null);
   return MockAsyncShutdown.hook()
     .then(null, err => hookErr = err)
     .then( () => {
+      BootstrapMonitor.shutdownCheck();
       gInternalManager = null;
 
       // Load the add-ons list as it was after application shutdown
       loadAddonsList();
 
       // Clear any crash report annotations
       gAppInfo.annotations = {};