Bug 1228359: Allow experiments to register chrome. r=felipe
authorDave Townsend <dtownsend@oxymoronical.com>
Fri, 27 Nov 2015 12:44:22 -0800
changeset 308787 a5ad0e73b4bf19c63eb157f4fd1bbaa98eb051d8
parent 308648 29ce9059dc2c97f2403f4e694eff3bcf611a8fc5
child 308788 258cff34022977ce14c964f90994277dbe50c959
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfelipe
bugs1228359
milestone45.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 1228359: Allow experiments to register chrome. r=felipe 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
@@ -214,16 +214,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
@@ -68,35 +68,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) {
@@ -118,16 +151,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);
@@ -138,16 +176,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)
@@ -159,28 +198,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() {
@@ -582,16 +639,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 = {};