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 307276 cc82d9b8f949a60ba3ad9b88c8fca77513e4fa68
parent 307275 1b5636e3136518de47d25306bb5061e42b99d991
child 307277 782b26254a59b935ef93d3b33a5e24688d2cb5ff
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)
reviewersjmaher
bugs1219442
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 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(" "));