Bug 1219442 - Re-write specialpowers as a restartless addon, r=jmaher
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Thu, 05 Nov 2015 10:00:59 -0500
changeset 309813 cc82d9b8f949a60ba3ad9b88c8fca77513e4fa68
parent 309812 1b5636e3136518de47d25306bb5061e42b99d991
child 309814 782b26254a59b935ef93d3b33a5e24688d2cb5ff
push id7649
push usermartin.thomson@gmail.com
push dateThu, 19 Nov 2015 00:06:17 +0000
reviewersjmaher
bugs1219442
milestone45.0a1
Bug 1219442 - Re-write specialpowers as a restartless addon, r=jmaher In order to meet the addon signing requirement for tests, specialpowers needs to be installed at gecko runtime. This means it must be restartless. This patch packages specialpowers as a restartless addon, but it does not yet install it at runtime.
build/mobile/b2gautomation.py
dom/ipc/tests/test_bug1086684.html
testing/marionette/client/marionette/marionette_test.py
testing/marionette/jar.mn
testing/mochitest/b2g_start_script.js
testing/mochitest/mochitest_options.py
testing/mochitest/runtestsb2g.py
testing/specialpowers/Makefile.in
testing/specialpowers/bootstrap.js
testing/specialpowers/components/SpecialPowersObserver.js
testing/specialpowers/content/SpecialPowersObserver.jsm
testing/specialpowers/content/SpecialPowersObserverAPI.js
testing/specialpowers/content/specialpowers.js
testing/specialpowers/content/specialpowersAPI.js
testing/specialpowers/install.rdf
testing/specialpowers/jar.mn
testing/specialpowers/moz.build
testing/testsuite-targets.mk
toolkit/mozapps/update/tests/chrome/utils.js
--- a/build/mobile/b2gautomation.py
+++ b/build/mobile/b2gautomation.py
@@ -352,21 +352,20 @@ class B2GRemoteAutomation(Automation):
         self.marionette.execute_script("""
             let SECURITY_PREF = "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer";
             Components.utils.import("resource://gre/modules/Services.jsm");
             Services.prefs.setBoolPref(SECURITY_PREF, true);
 
             if (!testUtils.hasOwnProperty("specialPowersObserver")) {
               let loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
                 .getService(Components.interfaces.mozIJSSubScriptLoader);
-              loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.js",
+              loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.jsm",
                 testUtils);
               testUtils.specialPowersObserver = new testUtils.SpecialPowersObserver();
               testUtils.specialPowersObserver.init();
-              testUtils.specialPowersObserver._loadFrameScript();
             }
             """)
 
         if not self.context_chrome:
             self.marionette.set_context(self.marionette.CONTEXT_CONTENT)
 
         # run the script that starts the tests
         if self.test_script:
--- a/dom/ipc/tests/test_bug1086684.html
+++ b/dom/ipc/tests/test_bug1086684.html
@@ -16,17 +16,17 @@
 
     const childFrameURL =
       "http://mochi.test:8888/tests/dom/ipc/tests/file_bug1086684.html";
 
     function childFrameScript() {
       "use strict";
 
       let { MockFilePicker } =
-        Components.utils.import("resource://specialpowers/MockFilePicker.jsm", {});
+        Components.utils.import("chrome://specialpowers/content/MockFilePicker.jsm", {});
 
       function parentReady(message) {
         MockFilePicker.init(content);
         MockFilePicker.returnFiles = [message.data.file];
         MockFilePicker.returnValue = MockFilePicker.returnOK;
 
         let input = content.document.getElementById("f");
         input.addEventListener("change", () => {
--- a/testing/marionette/client/marionette/marionette_test.py
+++ b/testing/marionette/client/marionette/marionette_test.py
@@ -553,21 +553,20 @@ setReq.onerror = function() {
         self.marionette.execute_script("""
             let SECURITY_PREF = "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer";
             Components.utils.import("resource://gre/modules/Services.jsm");
             Services.prefs.setBoolPref(SECURITY_PREF, true);
 
             if (!testUtils.hasOwnProperty("specialPowersObserver")) {
               let loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
                 .getService(Components.interfaces.mozIJSSubScriptLoader);
-              loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.js",
+              loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.jsm",
                 testUtils);
               testUtils.specialPowersObserver = new testUtils.SpecialPowersObserver();
               testUtils.specialPowersObserver.init();
-              testUtils.specialPowersObserver._loadFrameScript();
             }
             """)
 
     def run_js_test(self, filename, marionette=None):
         '''
         Run a JavaScript test file and collect its set of assertions
         into the current test's results.
 
--- a/testing/marionette/jar.mn
+++ b/testing/marionette/jar.mn
@@ -26,20 +26,18 @@ marionette.jar:
   content/test.xul  (client/marionette/chrome/test.xul)
   content/test2.xul  (client/marionette/chrome/test2.xul)
   content/test_nested_iframe.xul  (client/marionette/chrome/test_nested_iframe.xul)
   content/test_anonymous_content.xul  (client/marionette/chrome/test_anonymous_content.xul)
 #endif
 
 % content specialpowers %content/
   content/specialpowers.js (../specialpowers/content/specialpowers.js)
-  content/SpecialPowersObserver.js (../specialpowers/components/SpecialPowersObserver.js)
+  content/SpecialPowersObserver.jsm (../specialpowers/content/SpecialPowersObserver.jsm)
 * content/specialpowersAPI.js (../specialpowers/content/specialpowersAPI.js)
   content/SpecialPowersObserverAPI.js (../specialpowers/content/SpecialPowersObserverAPI.js)
   content/ChromePowers.js (../mochitest/tests/SimpleTest/ChromePowers.js)
   content/MozillaLogger.js (../specialpowers/content/MozillaLogger.js)
-
-% resource specialpowers %modules/
-  modules/MockFilePicker.jsm (../specialpowers/content/MockFilePicker.jsm)
-  modules/MockColorPicker.jsm (../specialpowers/content/MockColorPicker.jsm)
-  modules/MockPermissionPrompt.jsm (../specialpowers/content/MockPermissionPrompt.jsm)
-  modules/MockPaymentsUIGlue.jsm (../specialpowers/content/MockPaymentsUIGlue.jsm)
-  modules/Assert.jsm (../modules/Assert.jsm)
+  content/MockFilePicker.jsm (../specialpowers/content/MockFilePicker.jsm)
+  content/MockColorPicker.jsm (../specialpowers/content/MockColorPicker.jsm)
+  content/MockPermissionPrompt.jsm (../specialpowers/content/MockPermissionPrompt.jsm)
+  content/MockPaymentsUIGlue.jsm (../specialpowers/content/MockPaymentsUIGlue.jsm)
+  content/Assert.jsm (../modules/Assert.jsm)
--- a/testing/mochitest/b2g_start_script.js
+++ b/testing/mochitest/b2g_start_script.js
@@ -80,17 +80,17 @@ container.addEventListener('mozbrowsersh
   else if (e.detail.message == 'setVisible::true') {
     container.setVisible(true);
   }
 });
 
 if (outOfProcess) {
   let specialpowers = {};
   let loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
-  loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.js", specialpowers);
+  loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.jsm", specialpowers);
   let specialPowersObserver = new specialpowers.SpecialPowersObserver();
 
   let mm = container.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
   specialPowersObserver.init(mm);
 
   //Workaround for bug 848411, once that bug is fixed, the following line can be removed
   function contentScript() {
     addEventListener("DOMWindowCreated", function listener(e) {
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -894,16 +894,18 @@ class B2GArguments(ArgumentContainer):
           "help": "Path to a directory containing preference and other data to be installed "
                   "into the profile.",
           "suppress": True,
           }],
     ]
 
     defaults = {
         'logFile': 'mochitest.log',
+        # Specialpowers is integrated with marionette for b2g,
+        # see marionette's jar.mn.
         'extensionsToExclude': ['specialpowers'],
         # See dependencies of bug 1038943.
         'defaultLeakThreshold': 5536,
     }
 
     def validate(self, parser, options, context):
         """Validate b2g options."""
 
--- a/testing/mochitest/runtestsb2g.py
+++ b/testing/mochitest/runtestsb2g.py
@@ -229,21 +229,20 @@ class B2GMochitest(MochitestUtilsMixin):
 
             self.marionette.execute_script("""
                 let SECURITY_PREF = "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer";
                 Services.prefs.setBoolPref(SECURITY_PREF, true);
 
                 if (!testUtils.hasOwnProperty("specialPowersObserver")) {
                   let loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
                     .getService(Components.interfaces.mozIJSSubScriptLoader);
-                  loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.js",
+                  loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.jsm",
                     testUtils);
                   testUtils.specialPowersObserver = new testUtils.SpecialPowersObserver();
                   testUtils.specialPowersObserver.init();
-                  testUtils.specialPowersObserver._loadFrameScript();
                 }
                 """)
 
             if options.chrome:
                 self.app_ctx.dm.removeDir(self.remote_chrome_test_dir)
                 self.app_ctx.dm.mkDir(self.remote_chrome_test_dir)
                 local = super(B2GMochitest, self).getChromeTestDir(options)
                 local = os.path.join(local, "chrome")
--- a/testing/specialpowers/Makefile.in
+++ b/testing/specialpowers/Makefile.in
@@ -1,15 +1,15 @@
 #
 # 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/.
 
-
 TEST_EXTENSIONS_DIR = $(DEPTH)/testing/specialpowers
+XPI_PKGNAME = specialpowers@mozilla.org
 
 include $(topsrcdir)/config/rules.mk
 
 libs-preqs = \
   $(call mkdir_deps,$(TEST_EXTENSIONS_DIR)) \
   $(NULL)
 
 libs:: $(libs-preqs)
new file mode 100644
--- /dev/null
+++ b/testing/specialpowers/bootstrap.js
@@ -0,0 +1,39 @@
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var spObserver;
+
+function startup(data, reason) {
+  let observer = {};
+  Components.utils.import("chrome://specialpowers/content/SpecialPowersObserver.jsm", observer);
+
+  let registrar = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+  registrar.registerFactory(
+    observer.SpecialPowersObserver.prototype.classID,
+    "SpecialPowersObserver",
+    observer.SpecialPowersObserver.prototype.contractID,
+    observer.SpecialPowersObserverFactory
+  );
+
+  spObserver = new observer.SpecialPowersObserver();
+  spObserver.init();
+}
+
+function shutdown(data, reason) {
+  let observer = {};
+  Components.utils.import("chrome://specialpowers/content/SpecialPowersObserver.jsm", observer);
+
+  let registrar = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+  registrar.unregisterFactory(
+    observer.SpecialPowersObserver.prototype.classID,
+    observer.SpecialPowersObserverFactory
+  );
+
+  spObserver.uninit();
+}
+
+function install(data, reason) {}
+function uninstall(data, reason) {}
rename from testing/specialpowers/components/SpecialPowersObserver.js
rename to testing/specialpowers/content/SpecialPowersObserver.jsm
--- a/testing/specialpowers/components/SpecialPowersObserver.js
+++ b/testing/specialpowers/content/SpecialPowersObserver.jsm
@@ -4,16 +4,18 @@
 
 // Based on:
 // https://bugzilla.mozilla.org/show_bug.cgi?id=549539
 // https://bug549539.bugzilla.mozilla.org/attachment.cgi?id=429661
 // https://developer.mozilla.org/en/XPCOM/XPCOM_changes_in_Gecko_1.9.3
 // http://mxr.mozilla.org/mozilla-central/source/toolkit/components/console/hudservice/HUDService.jsm#3240
 // https://developer.mozilla.org/en/how_to_build_an_xpcom_component_in_javascript
 
+var EXPORTED_SYMBOLS = ["SpecialPowersObserver", "SpecialPowersObserverFactory"];
+
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.importGlobalProperties(['File']);
 
 if (typeof(Cc) == "undefined") {
   const Cc = Components.classes;
   const Ci = Components.interfaces;
 }
@@ -34,290 +36,287 @@ this.SpecialPowersObserver = function Sp
   this._mmIsGlobal = true;
   this._messageManager = Cc["@mozilla.org/globalmessagemanager;1"].
                          getService(Ci.nsIMessageBroadcaster);
 }
 
 
 SpecialPowersObserver.prototype = new SpecialPowersObserverAPI();
 
-  SpecialPowersObserver.prototype.classDescription = "Special powers Observer for use in testing.";
-  SpecialPowersObserver.prototype.classID = Components.ID("{59a52458-13e0-4d93-9d85-a637344f29a1}");
-  SpecialPowersObserver.prototype.contractID = "@mozilla.org/special-powers-observer;1";
-  SpecialPowersObserver.prototype.QueryInterface = XPCOMUtils.generateQI([Components.interfaces.nsIObserver]);
-  SpecialPowersObserver.prototype._xpcom_categories = [{category: "profile-after-change", service: true }];
-
-  SpecialPowersObserver.prototype.observe = function(aSubject, aTopic, aData)
-  {
-    switch (aTopic) {
-      case "profile-after-change":
-        this.init();
-        break;
+SpecialPowersObserver.prototype.classDescription = "Special powers Observer for use in testing.";
+SpecialPowersObserver.prototype.classID = Components.ID("{59a52458-13e0-4d93-9d85-a637344f29a1}");
+SpecialPowersObserver.prototype.contractID = "@mozilla.org/special-powers-observer;1";
+SpecialPowersObserver.prototype.QueryInterface = XPCOMUtils.generateQI([Components.interfaces.nsIObserver]);
 
-      case "chrome-document-global-created":
-        this._loadFrameScript();
-        break;
+SpecialPowersObserver.prototype.observe = function(aSubject, aTopic, aData)
+{
+  switch (aTopic) {
+    case "chrome-document-global-created":
+      this._loadFrameScript();
+      break;
 
-      case "http-on-modify-request":
-        if (aSubject instanceof Ci.nsIChannel) {
-          let uri = aSubject.URI.spec;
-          this._sendAsyncMessage("specialpowers-http-notify-request", { uri: uri });
-        }
-        break;
-
-      case "xpcom-shutdown":
-        this.uninit();
-        break;
+    case "http-on-modify-request":
+      if (aSubject instanceof Ci.nsIChannel) {
+        let uri = aSubject.URI.spec;
+        this._sendAsyncMessage("specialpowers-http-notify-request", { uri: uri });
+      }
+      break;
 
-      default:
-        this._observe(aSubject, aTopic, aData);
-        break;
-    }
-  };
+    default:
+      this._observe(aSubject, aTopic, aData);
+      break;
+  }
+};
 
-  SpecialPowersObserver.prototype._loadFrameScript = function()
-  {
-    if (!this._isFrameScriptLoaded) {
-      // Register for any messages our API needs us to handle
-      this._messageManager.addMessageListener("SPPrefService", this);
-      this._messageManager.addMessageListener("SPProcessCrashService", this);
-      this._messageManager.addMessageListener("SPPingService", this);
-      this._messageManager.addMessageListener("SpecialPowers.Quit", this);
-      this._messageManager.addMessageListener("SpecialPowers.Focus", this);
-      this._messageManager.addMessageListener("SpecialPowers.CreateFiles", this);
-      this._messageManager.addMessageListener("SpecialPowers.RemoveFiles", this);
-      this._messageManager.addMessageListener("SPPermissionManager", this);
-      this._messageManager.addMessageListener("SPWebAppService", this);
-      this._messageManager.addMessageListener("SPObserverService", this);
-      this._messageManager.addMessageListener("SPLoadChromeScript", this);
-      this._messageManager.addMessageListener("SPChromeScriptMessage", this);
-      this._messageManager.addMessageListener("SPQuotaManager", this);
-      this._messageManager.addMessageListener("SPSetTestPluginEnabledState", this);
-      this._messageManager.addMessageListener("SPLoadExtension", this);
-      this._messageManager.addMessageListener("SPStartupExtension", this);
-      this._messageManager.addMessageListener("SPUnloadExtension", this);
-      this._messageManager.addMessageListener("SPExtensionMessage", this);
-      this._messageManager.addMessageListener("SPCleanUpSTSData", this);
+SpecialPowersObserver.prototype._loadFrameScript = function()
+{
+  if (!this._isFrameScriptLoaded) {
+    // Register for any messages our API needs us to handle
+    this._messageManager.addMessageListener("SPPrefService", this);
+    this._messageManager.addMessageListener("SPProcessCrashService", this);
+    this._messageManager.addMessageListener("SPPingService", this);
+    this._messageManager.addMessageListener("SpecialPowers.Quit", this);
+    this._messageManager.addMessageListener("SpecialPowers.Focus", this);
+    this._messageManager.addMessageListener("SpecialPowers.CreateFiles", this);
+    this._messageManager.addMessageListener("SpecialPowers.RemoveFiles", this);
+    this._messageManager.addMessageListener("SPPermissionManager", this);
+    this._messageManager.addMessageListener("SPWebAppService", this);
+    this._messageManager.addMessageListener("SPObserverService", this);
+    this._messageManager.addMessageListener("SPLoadChromeScript", this);
+    this._messageManager.addMessageListener("SPChromeScriptMessage", this);
+    this._messageManager.addMessageListener("SPQuotaManager", this);
+    this._messageManager.addMessageListener("SPSetTestPluginEnabledState", this);
+    this._messageManager.addMessageListener("SPLoadExtension", this);
+    this._messageManager.addMessageListener("SPStartupExtension", this);
+    this._messageManager.addMessageListener("SPUnloadExtension", this);
+    this._messageManager.addMessageListener("SPExtensionMessage", this);
+    this._messageManager.addMessageListener("SPCleanUpSTSData", this);
 
-      this._messageManager.loadFrameScript(CHILD_LOGGER_SCRIPT, true);
-      this._messageManager.loadFrameScript(CHILD_SCRIPT_API, true);
-      this._messageManager.loadFrameScript(CHILD_SCRIPT, true);
-      this._isFrameScriptLoaded = true;
-      this._createdFiles = null;
-    }
-  };
+    this._messageManager.loadFrameScript(CHILD_LOGGER_SCRIPT, true);
+    this._messageManager.loadFrameScript(CHILD_SCRIPT_API, true);
+    this._messageManager.loadFrameScript(CHILD_SCRIPT, true);
+    this._isFrameScriptLoaded = true;
+    this._createdFiles = null;
+  }
+};
 
-  SpecialPowersObserver.prototype._sendAsyncMessage = function(msgname, msg)
-  {
-    if (this._mmIsGlobal) {
-      this._messageManager.broadcastAsyncMessage(msgname, msg);
-    }
-    else {
-      this._messageManager.sendAsyncMessage(msgname, msg);
-    }
-  };
+SpecialPowersObserver.prototype._sendAsyncMessage = function(msgname, msg)
+{
+  if (this._mmIsGlobal) {
+    this._messageManager.broadcastAsyncMessage(msgname, msg);
+  }
+  else {
+    this._messageManager.sendAsyncMessage(msgname, msg);
+  }
+};
 
-  SpecialPowersObserver.prototype._receiveMessage = function(aMessage) {
-    return this._receiveMessageAPI(aMessage);
-  };
+SpecialPowersObserver.prototype._receiveMessage = function(aMessage) {
+  return this._receiveMessageAPI(aMessage);
+};
 
-  SpecialPowersObserver.prototype.init = function(messageManager)
-  {
-    var obs = Services.obs;
-    obs.addObserver(this, "xpcom-shutdown", false);
-    obs.addObserver(this, "chrome-document-global-created", false);
+SpecialPowersObserver.prototype.init = function(messageManager)
+{
+  var obs = Services.obs;
+  obs.addObserver(this, "chrome-document-global-created", false);
 
-    // Register special testing modules.
-    var testsURI = Cc["@mozilla.org/file/directory_service;1"].
-                     getService(Ci.nsIProperties).
-                     get("ProfD", Ci.nsILocalFile);
-    testsURI.append("tests.manifest");
-    var ioSvc = Cc["@mozilla.org/network/io-service;1"].
-                  getService(Ci.nsIIOService);
-    var manifestFile = ioSvc.newFileURI(testsURI).
-                         QueryInterface(Ci.nsIFileURL).file;
+  // Register special testing modules.
+  var testsURI = Cc["@mozilla.org/file/directory_service;1"].
+                   getService(Ci.nsIProperties).
+                   get("ProfD", Ci.nsILocalFile);
+  testsURI.append("tests.manifest");
+  var ioSvc = Cc["@mozilla.org/network/io-service;1"].
+                getService(Ci.nsIIOService);
+  var manifestFile = ioSvc.newFileURI(testsURI).
+                       QueryInterface(Ci.nsIFileURL).file;
 
-    Components.manager.QueryInterface(Ci.nsIComponentRegistrar).
-                   autoRegister(manifestFile);
+  Components.manager.QueryInterface(Ci.nsIComponentRegistrar).
+                 autoRegister(manifestFile);
 
-    obs.addObserver(this, "http-on-modify-request", false);
+  obs.addObserver(this, "http-on-modify-request", false);
 
-    if (messageManager) {
-      this._messageManager = messageManager;
-      this._mmIsGlobal = false;
+  if (messageManager) {
+    this._messageManager = messageManager;
+    this._mmIsGlobal = false;
+  }
 
-      this._loadFrameScript();
-    }
-  };
+  this._loadFrameScript();
+};
 
-  SpecialPowersObserver.prototype.uninit = function()
-  {
-    var obs = Services.obs;
-    obs.removeObserver(this, "chrome-document-global-created");
-    obs.removeObserver(this, "http-on-modify-request");
-    obs.removeObserver(this, "xpcom-shutdown");
-    this._registerObservers._topics.forEach(function(element) {
-      obs.removeObserver(this._registerObservers, element);
-    });
-    this._removeProcessCrashObservers();
+SpecialPowersObserver.prototype.uninit = function()
+{
+  var obs = Services.obs;
+  obs.removeObserver(this, "chrome-document-global-created");
+  obs.removeObserver(this, "http-on-modify-request");
+  this._registerObservers._topics.forEach(function(element) {
+    obs.removeObserver(this._registerObservers, element);
+  });
+  this._removeProcessCrashObservers();
 
-    if (this._isFrameScriptLoaded) {
-      this._messageManager.removeMessageListener("SPPrefService", this);
-      this._messageManager.removeMessageListener("SPProcessCrashService", this);
-      this._messageManager.removeMessageListener("SPPingService", this);
-      this._messageManager.removeMessageListener("SpecialPowers.Quit", this);
-      this._messageManager.removeMessageListener("SpecialPowers.Focus", this);
-      this._messageManager.removeMessageListener("SpecialPowers.CreateFiles", this);
-      this._messageManager.removeMessageListener("SpecialPowers.RemoveFiles", this);
-      this._messageManager.removeMessageListener("SPPermissionManager", this);
-      this._messageManager.removeMessageListener("SPWebAppService", this);
-      this._messageManager.removeMessageListener("SPObserverService", this);
-      this._messageManager.removeMessageListener("SPLoadChromeScript", this);
-      this._messageManager.removeMessageListener("SPChromeScriptMessage", this);
-      this._messageManager.removeMessageListener("SPQuotaManager", this);
-      this._messageManager.removeMessageListener("SPSetTestPluginEnabledState", this);
-      this._messageManager.removeMessageListener("SPLoadExtension", this);
-      this._messageManager.removeMessageListener("SPStartupExtension", this);
-      this._messageManager.removeMessageListener("SPUnloadExtension", this);
-      this._messageManager.removeMessageListener("SPExtensionMessage", this);
-      this._messageManager.removeMessageListener("SPCleanUpSTSData", this);
+  if (this._isFrameScriptLoaded) {
+    this._messageManager.removeMessageListener("SPPrefService", this);
+    this._messageManager.removeMessageListener("SPProcessCrashService", this);
+    this._messageManager.removeMessageListener("SPPingService", this);
+    this._messageManager.removeMessageListener("SpecialPowers.Quit", this);
+    this._messageManager.removeMessageListener("SpecialPowers.Focus", this);
+    this._messageManager.removeMessageListener("SpecialPowers.CreateFiles", this);
+    this._messageManager.removeMessageListener("SpecialPowers.RemoveFiles", this);
+    this._messageManager.removeMessageListener("SPPermissionManager", this);
+    this._messageManager.removeMessageListener("SPWebAppService", this);
+    this._messageManager.removeMessageListener("SPObserverService", this);
+    this._messageManager.removeMessageListener("SPLoadChromeScript", this);
+    this._messageManager.removeMessageListener("SPChromeScriptMessage", this);
+    this._messageManager.removeMessageListener("SPQuotaManager", this);
+    this._messageManager.removeMessageListener("SPSetTestPluginEnabledState", this);
+    this._messageManager.removeMessageListener("SPLoadExtension", this);
+    this._messageManager.removeMessageListener("SPStartupExtension", this);
+    this._messageManager.removeMessageListener("SPUnloadExtension", this);
+    this._messageManager.removeMessageListener("SPExtensionMessage", this);
+    this._messageManager.removeMessageListener("SPCleanUpSTSData", this);
 
-      this._messageManager.removeDelayedFrameScript(CHILD_LOGGER_SCRIPT);
-      this._messageManager.removeDelayedFrameScript(CHILD_SCRIPT_API);
-      this._messageManager.removeDelayedFrameScript(CHILD_SCRIPT);
-      this._isFrameScriptLoaded = false;
-    }
+    this._messageManager.removeDelayedFrameScript(CHILD_LOGGER_SCRIPT);
+    this._messageManager.removeDelayedFrameScript(CHILD_SCRIPT_API);
+    this._messageManager.removeDelayedFrameScript(CHILD_SCRIPT);
+    this._isFrameScriptLoaded = false;
+  }
+
+  this._mmIsGlobal = true;
+  this._messageManager = Cc["@mozilla.org/globalmessagemanager;1"].
+    getService(Ci.nsIMessageBroadcaster);
+};
 
-    this._mmIsGlobal = true;
-    this._messageManager = Cc["@mozilla.org/globalmessagemanager;1"].
-      getService(Ci.nsIMessageBroadcaster);
-  };
+SpecialPowersObserver.prototype._addProcessCrashObservers = function() {
+  if (this._processCrashObserversRegistered) {
+    return;
+  }
+
+  var obs = Components.classes["@mozilla.org/observer-service;1"]
+                      .getService(Components.interfaces.nsIObserverService);
+
+  obs.addObserver(this, "plugin-crashed", false);
+  obs.addObserver(this, "ipc:content-shutdown", false);
+  this._processCrashObserversRegistered = true;
+};
+
+SpecialPowersObserver.prototype._removeProcessCrashObservers = function() {
+  if (!this._processCrashObserversRegistered) {
+    return;
+  }
 
-  SpecialPowersObserver.prototype._addProcessCrashObservers = function() {
-    if (this._processCrashObserversRegistered) {
-      return;
-    }
+  var obs = Components.classes["@mozilla.org/observer-service;1"]
+                      .getService(Components.interfaces.nsIObserverService);
 
-    var obs = Components.classes["@mozilla.org/observer-service;1"]
-                        .getService(Components.interfaces.nsIObserverService);
+  obs.removeObserver(this, "plugin-crashed");
+  obs.removeObserver(this, "ipc:content-shutdown");
+  this._processCrashObserversRegistered = false;
+};
 
-    obs.addObserver(this, "plugin-crashed", false);
-    obs.addObserver(this, "ipc:content-shutdown", false);
-    this._processCrashObserversRegistered = true;
-  };
-
-  SpecialPowersObserver.prototype._removeProcessCrashObservers = function() {
-    if (!this._processCrashObserversRegistered) {
-      return;
+SpecialPowersObserver.prototype._registerObservers = {
+  _self: null,
+  _topics: [],
+  _add: function(topic) {
+    if (this._topics.indexOf(topic) < 0) {
+      this._topics.push(topic);
+      Services.obs.addObserver(this, topic, false);
     }
-
-    var obs = Components.classes["@mozilla.org/observer-service;1"]
-                        .getService(Components.interfaces.nsIObserverService);
+  },
+  observe: function (aSubject, aTopic, aData) {
+    var msg = { aData: aData };
+    switch (aTopic) {
+      case "perm-changed":
+        var permission = aSubject.QueryInterface(Ci.nsIPermission);
 
-    obs.removeObserver(this, "plugin-crashed");
-    obs.removeObserver(this, "ipc:content-shutdown");
-    this._processCrashObserversRegistered = false;
-  };
+        // specialPowersAPI will consume this value, and it is used as a
+        // fake permission, but only type and principal.appId will be used.
+        //
+        // We need to ensure that it looks the same as a real permission,
+        // so we fake these properties.
+        msg.permission = {
+          principal: {
+            originAttributes: {appId: permission.principal.appId}
+          },
+          type: permission.type
+        };
+      default:
+        this._self._sendAsyncMessage("specialpowers-" + aTopic, msg);
+    }
+  }
+};
 
-  SpecialPowersObserver.prototype._registerObservers = {
-    _self: null,
-    _topics: [],
-    _add: function(topic) {
-      if (this._topics.indexOf(topic) < 0) {
-        this._topics.push(topic);
-        Services.obs.addObserver(this, topic, false);
+/**
+ * messageManager callback function
+ * This will get requests from our API in the window and process them in chrome for it
+ **/
+SpecialPowersObserver.prototype.receiveMessage = function(aMessage) {
+  switch(aMessage.name) {
+    case "SPPingService":
+      if (aMessage.json.op == "ping") {
+        aMessage.target
+                .QueryInterface(Ci.nsIFrameLoaderOwner)
+                .frameLoader
+                .messageManager
+                .sendAsyncMessage("SPPingService", { op: "pong" });
       }
-    },
-    observe: function (aSubject, aTopic, aData) {
-      var msg = { aData: aData };
-      switch (aTopic) {
-        case "perm-changed":
-          var permission = aSubject.QueryInterface(Ci.nsIPermission);
-
-          // specialPowersAPI will consume this value, and it is used as a
-          // fake permission, but only type and principal.appId will be used.
-          //
-          // We need to ensure that it looks the same as a real permission,
-          // so we fake these properties.
-          msg.permission = {
-            principal: {
-              originAttributes: {appId: permission.principal.appId}
-            },
-            type: permission.type
-          };
-        default:
-          this._self._sendAsyncMessage("specialpowers-" + aTopic, msg);
+      break;
+    case "SpecialPowers.Quit":
+      let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup);
+      appStartup.quit(Ci.nsIAppStartup.eForceQuit);
+      break;
+    case "SpecialPowers.Focus":
+      aMessage.target.focus();
+      break;
+    case "SpecialPowers.CreateFiles":
+      let filePaths = new Array;
+      if (!this.createdFiles) {
+        this._createdFiles = new Array;
       }
-    }
-  };
-
-  /**
-   * messageManager callback function
-   * This will get requests from our API in the window and process them in chrome for it
-   **/
-  SpecialPowersObserver.prototype.receiveMessage = function(aMessage) {
-    switch(aMessage.name) {
-      case "SPPingService":
-        if (aMessage.json.op == "ping") {
-          aMessage.target
-                  .QueryInterface(Ci.nsIFrameLoaderOwner)
-                  .frameLoader
-                  .messageManager
-                  .sendAsyncMessage("SPPingService", { op: "pong" });
-        }
-        break;
-      case "SpecialPowers.Quit":
-        let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup);
-        appStartup.quit(Ci.nsIAppStartup.eForceQuit);
-        break;
-      case "SpecialPowers.Focus":
-        aMessage.target.focus();
-        break;
-      case "SpecialPowers.CreateFiles":
-        let filePaths = new Array;
-        if (!this.createdFiles) {
-          this._createdFiles = new Array;
-        }
-        let createdFiles = this._createdFiles;
-        try {
-          aMessage.data.forEach(function(request) {
-            let testFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
-            testFile.append(request.name);
-            let outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
-            outStream.init(testFile, 0x02 | 0x08 | 0x20, // PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE
-                           0666, 0);
-            if (request.data) {
-              outStream.write(request.data, request.data.length);
-              outStream.close();
-            }
-            filePaths.push(new File(testFile.path));
-            createdFiles.push(testFile);
-          });
-          aMessage.target
-                  .QueryInterface(Ci.nsIFrameLoaderOwner)
-                  .frameLoader
-                  .messageManager
-                  .sendAsyncMessage("SpecialPowers.FilesCreated", filePaths);
-        } catch (e) {
+      let createdFiles = this._createdFiles;
+      try {
+        aMessage.data.forEach(function(request) {
+              let testFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
+              testFile.append(request.name);
+              let outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
+              outStream.init(testFile, 0x02 | 0x08 | 0x20, // PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE
+                             0666, 0);
+              if (request.data) {
+            outStream.write(request.data, request.data.length);
+            outStream.close();
+          }
+          filePaths.push(new File(testFile.path));
+          createdFiles.push(testFile);
+        });
+        aMessage.target
+                .QueryInterface(Ci.nsIFrameLoaderOwner)
+                .frameLoader
+                .messageManager
+                .sendAsyncMessage("SpecialPowers.FilesCreated", filePaths);
+      } catch (e) {
           aMessage.target
                   .QueryInterface(Ci.nsIFrameLoaderOwner)
                   .frameLoader
                   .messageManager
                   .sendAsyncMessage("SpecialPowers.FilesError", e.toString());
-        }
+      }
 
-        break;
-      case "SpecialPowers.RemoveFiles":
-        if (this._createdFiles) {
-          this._createdFiles.forEach(function (testFile) {
-            try {
-              testFile.remove(false);
-            } catch (e) {}
-          });
-          this._createdFiles = null;
-        }
-        break;
-      default:
-        return this._receiveMessage(aMessage);
-    }
-  };
+      break;
+    case "SpecialPowers.RemoveFiles":
+      if (this._createdFiles) {
+        this._createdFiles.forEach(function (testFile) {
+          try {
+            testFile.remove(false);
+          } catch (e) {}
+        });
+        this._createdFiles = null;
+      }
+      break;
+    default:
+      return this._receiveMessage(aMessage);
+  }
+};
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SpecialPowersObserver]);
+this.SpecialPowersObserverFactory = Object.freeze({
+  createInstance: function(outer, id) {
+    if (outer) { throw Components.results.NS_ERROR_NO_AGGREGATION };
+    return new SpecialPowersObserver();
+  },
+  loadFactory: function(lock){},
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
+});
--- a/testing/specialpowers/content/SpecialPowersObserverAPI.js
+++ b/testing/specialpowers/content/SpecialPowersObserverAPI.js
@@ -454,17 +454,17 @@ SpecialPowersObserverAPI.prototype = {
           // Pipe assertions back to parent process
           mm.sendAsyncMessage("SPChromeScriptAssert",
                               { id: id, url: url, err: err, message: message,
                                 stack: stack });
         };
         Object.defineProperty(sb, "assert", {
           get: function () {
             let scope = Components.utils.createObjectIn(sb);
-            Services.scriptloader.loadSubScript("resource://specialpowers/Assert.jsm",
+            Services.scriptloader.loadSubScript("chrome://specialpowers/content/Assert.jsm",
                                                 scope);
 
             let assert = new scope.Assert(reporter);
             delete sb.assert;
             return sb.assert = assert;
           },
           configurable: true
         });
--- a/testing/specialpowers/content/specialpowers.js
+++ b/testing/specialpowers/content/specialpowers.js
@@ -208,20 +208,19 @@ SpecialPowers.prototype.nestedFrameSetup
         });
       });
       mm.addMessageListener("SPPAddNestedMessageListener", function(msg) {
         self._addMessageListener(msg.json.name, function(aMsg) {
           mm.sendAsyncMessage(aMsg.name, aMsg.data);
           });
       });
 
-      let specialPowersBase = "chrome://specialpowers/content/";
-      mm.loadFrameScript(specialPowersBase + "MozillaLogger.js", false);
-      mm.loadFrameScript(specialPowersBase + "specialpowersAPI.js", false);
-      mm.loadFrameScript(specialPowersBase + "specialpowers.js", false);
+      mm.loadFrameScript("chrome://specialpowers/content/MozillaLogger.js", false);
+      mm.loadFrameScript("chrome://specialpowers/content/specialpowersAPI.js", false);
+      mm.loadFrameScript("chrome://specialpowers/content/specialpowers.js", false);
 
       let frameScript = "SpecialPowers.prototype.IsInNestedFrame=true;";
       mm.loadFrameScript("data:," + frameScript, false);
     }
   }, "remote-browser-shown", false);
 };
 
 // Attach our API to the window.
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/specialpowersAPI.js
@@ -6,20 +6,20 @@
  */
 
 "use strict";
 
 var Ci = Components.interfaces;
 var Cc = Components.classes;
 var Cu = Components.utils;
 
-Cu.import("resource://specialpowers/MockFilePicker.jsm");
-Cu.import("resource://specialpowers/MockColorPicker.jsm");
-Cu.import("resource://specialpowers/MockPermissionPrompt.jsm");
-Cu.import("resource://specialpowers/MockPaymentsUIGlue.jsm");
+Cu.import("chrome://specialpowers/content/MockFilePicker.jsm");
+Cu.import("chrome://specialpowers/content/MockColorPicker.jsm");
+Cu.import("chrome://specialpowers/content/MockPermissionPrompt.jsm");
+Cu.import("chrome://specialpowers/content/MockPaymentsUIGlue.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 // We're loaded with "this" not set to the global in some cases, so we
 // have to play some games to get at the global object here.  Normally
 // we'd try "this" from a function called with undefined this value,
 // but this whole file is in strict mode.  So instead fall back on
--- a/testing/specialpowers/install.rdf
+++ b/testing/specialpowers/install.rdf
@@ -1,25 +1,27 @@
 <?xml version="1.0"?>
 
 <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
      xmlns:em="http://www.mozilla.org/2004/em-rdf#">
 
   <Description about="urn:mozilla:install-manifest">
     <em:id>special-powers@mozilla.org</em:id>
-    <em:version>2010.07.23</em:version>
+    <em:version>2015.11.16</em:version>
     <em:type>2</em:type>
+    <em:bootstrap>true</em:bootstrap>
 
     <!-- Target Application this extension can install into, 
          with minimum and maximum supported versions. -->
     <em:targetApplication>
       <Description>
         <em:id>toolkit@mozilla.org</em:id>
 #expand        <em:minVersion>__MOZILLA_VERSION_U__</em:minVersion>
-#expand        <em:maxVersion>__MOZILLA_VERSION_U__</em:maxVersion>
+               <!-- Set to * so toolkit/mozapps/update/chrome tests pass. -->
+               <em:maxVersion>*</em:maxVersion>
       </Description>
     </em:targetApplication>
 
     <!-- Front End MetaData -->
     <em:name>Special Powers</em:name>
     <em:description>Special powers for use in testing.</em:description>
     <em:creator>Mozilla</em:creator>
   </Description>      
--- a/testing/specialpowers/jar.mn
+++ b/testing/specialpowers/jar.mn
@@ -1,17 +1,12 @@
 specialpowers.jar:
-% content specialpowers %content/
+% content specialpowers %content/ contentaccessible=true
   content/specialpowers.js (content/specialpowers.js)
 * content/specialpowersAPI.js (content/specialpowersAPI.js)
   content/SpecialPowersObserverAPI.js (content/SpecialPowersObserverAPI.js)
+  content/SpecialPowersObserver.jsm (content/SpecialPowersObserver.jsm)
   content/MozillaLogger.js (content/MozillaLogger.js)
-
-% resource specialpowers %modules/
-  modules/MockFilePicker.jsm (content/MockFilePicker.jsm)
-  modules/MockColorPicker.jsm (content/MockColorPicker.jsm)
-  modules/MockPermissionPrompt.jsm (content/MockPermissionPrompt.jsm)
-  modules/MockPaymentsUIGlue.jsm (content/MockPaymentsUIGlue.jsm)
-  modules/Assert.jsm (../modules/Assert.jsm)
-
-% component {59a52458-13e0-4d93-9d85-a637344f29a1} components/SpecialPowersObserver.js
-% contract @mozilla.org/special-powers-observer;1 {59a52458-13e0-4d93-9d85-a637344f29a1}
-% category profile-after-change @mozilla.org/special-powers-observer;1 @mozilla.org/special-powers-observer;1
+  content/MockFilePicker.jsm (content/MockFilePicker.jsm)
+  content/MockColorPicker.jsm (content/MockColorPicker.jsm)
+  content/MockPermissionPrompt.jsm (content/MockPermissionPrompt.jsm)
+  content/MockPaymentsUIGlue.jsm (content/MockPaymentsUIGlue.jsm)
+  content/Assert.jsm (../modules/Assert.jsm)
--- a/testing/specialpowers/moz.build
+++ b/testing/specialpowers/moz.build
@@ -1,18 +1,17 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
-EXTRA_COMPONENTS += [
-    'components/SpecialPowersObserver.js',
-]
-
 XPI_NAME = 'specialpowers'
 
 JAR_MANIFESTS += ['jar.mn']
 
 USE_EXTENSION_MANIFEST = True
 NO_JS_MANIFEST = True
 
-DIST_FILES += ['install.rdf']
+DIST_FILES += [
+    'bootstrap.js',
+    'install.rdf',
+]
--- a/testing/testsuite-targets.mk
+++ b/testing/testsuite-targets.mk
@@ -369,16 +369,17 @@ pgo-profile-run:
 # Package up the tests and test harnesses
 include $(topsrcdir)/toolkit/mozapps/installer/package-name.mk
 
 PKG_STAGE = $(DIST)/test-stage
 
 stage-all: \
   stage-config \
   stage-mach \
+  stage-extensions \
   stage-mochitest \
   stage-xpcshell \
   stage-jstests \
   stage-jetpack \
   stage-marionette \
   stage-cppunittests \
   stage-luciddream \
   test-packages-manifest \
@@ -545,16 +546,24 @@ stage-marionette: make-stage-dir
           $(topsrcdir) \
           $(topsrcdir)/testing/marionette/client/marionette/tests/webapi-tests.ini \
           | (cd $(topsrcdir) && xargs tar $(TAR_CREATE_FLAGS) -) \
           | (cd $(MARIONETTE_DIR)/tests && tar -xf -)
 
 stage-instrumentation-tests: make-stage-dir
 	$(MAKE) -C $(DEPTH)/testing/instrumentation stage-package
 
+TEST_EXTENSIONS := \
+    specialpowers@mozilla.org.xpi \
+	$(NULL)
+
+stage-extensions: make-stage-dir
+	$(NSINSTALL) -D $(PKG_STAGE)/extensions/
+	@$(foreach ext,$(TEST_EXTENSIONS), cp -RL $(DIST)/xpi-stage/$(ext) $(PKG_STAGE)/extensions;)
+
 .PHONY: \
   mochitest \
   mochitest-plain \
   mochitest-chrome \
   mochitest-devtools \
   mochitest-a11y \
   reftest \
   crashtest \
--- a/toolkit/mozapps/update/tests/chrome/utils.js
+++ b/toolkit/mozapps/update/tests/chrome/utils.js
@@ -1295,19 +1295,22 @@ function setupAddons(aCallback) {
   // tests.
   AddonManager.getAllAddons(function(aAddons) {
     let disabledAddons = [];
     aAddons.forEach(function(aAddon) {
       // If an addon's type equals plugin it is skipped since
       // checking plugins compatibility information isn't supported at this
       // time (also see bug 566787). Also, SCOPE_APPLICATION add-ons are
       // excluded by app update so there is no reason to disable them.
+      // Specialpowers is excluded as the test harness requires it to run
+      // the tests.
       if (aAddon.type != "plugin" && !aAddon.appDisabled &&
           !aAddon.userDisabled &&
-          aAddon.scope != AddonManager.SCOPE_APPLICATION) {
+          aAddon.scope != AddonManager.SCOPE_APPLICATION &&
+          aAddon.id != "special-powers@mozilla.org") {
         disabledAddons.push(aAddon);
         aAddon.userDisabled = true;
       }
     });
     // If there are no pre-existing add-ons the preference value will be an
     // empty string.
     Services.prefs.setCharPref(PREF_DISABLEDADDONS, disabledAddons.join(" "));