[Pkg-javascript-commits] [pdf.js] 01/56: Fix special powers add-on for firefox.

David Prévot taffit at moszumanska.debian.org
Thu May 15 15:17:42 UTC 2014


This is an automated email from the git hooks/post-receive script.

taffit pushed a commit to branch master
in repository pdf.js.

commit da522d42d4f09e5914fa16f1c0cbb079bcd5f4cf
Author: Brendan Dahl <brendan.dahl at gmail.com>
Date:   Fri Apr 11 10:40:15 2014 -0700

    Fix special powers add-on for firefox.
---
 .../special-powers at mozilla.org/chrome.manifest     |    5 +-
 .../chrome/specialpowers/content/MozillaLogger.js  |  121 ++
 .../content/SpecialPowersObserverAPI.js            |  414 +++++
 .../chrome/specialpowers/content/specialpowers.js  |  376 +----
 .../specialpowers/content/specialpowersAPI.js      | 1744 ++++++++++++++++++++
 .../chrome/specialpowers/modules/Assert.jsm        |  442 +++++
 .../specialpowers/modules/MockColorPicker.jsm      |  125 ++
 .../specialpowers/modules/MockFilePicker.jsm       |  236 +++
 .../specialpowers/modules/MockPermissionPrompt.jsm |   97 ++
 .../components/SpecialPowersObserver.js            |  303 ++--
 test/resources/firefox/prefs.js                    |    1 +
 11 files changed, 3346 insertions(+), 518 deletions(-)

diff --git a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome.manifest b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome.manifest
index 614f31c..cac9fd6 100644
--- a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome.manifest
+++ b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome.manifest
@@ -1,4 +1,5 @@
-content specialpowers chrome/specialpowers/content/
+category profile-after-change @mozilla.org/special-powers-observer;1 @mozilla.org/special-powers-observer;1
 component {59a52458-13e0-4d93-9d85-a637344f29a1} components/SpecialPowersObserver.js
+content specialpowers chrome/specialpowers/content/
 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
+resource specialpowers chrome/specialpowers/modules/
diff --git a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/MozillaLogger.js b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/MozillaLogger.js
new file mode 100644
index 0000000..96ad8a2
--- /dev/null
+++ b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/MozillaLogger.js
@@ -0,0 +1,121 @@
+/**
+ * MozillaLogger, a base class logger that just logs to stdout.
+ */
+
+function MozillaLogger(aPath) {
+}
+
+MozillaLogger.prototype = {
+
+  init : function(path) {},
+  
+  getLogCallback : function() {
+    return function (msg) {
+      var data = msg.num + " " + msg.level + " " + msg.info.join(' ') + "\n";
+      dump(data);
+    }
+  },
+
+  log : function(msg) {
+    dump(msg);
+  },
+
+  close : function() {}
+};
+
+
+/**
+ * SpecialPowersLogger, inherits from MozillaLogger and utilizes SpecialPowers.
+ * intented to be used in content scripts to write to a file
+ */
+function SpecialPowersLogger(aPath) {
+  // Call the base constructor
+  MozillaLogger.call(this);
+  this.prototype = new MozillaLogger(aPath);
+  this.init(aPath);
+}
+
+SpecialPowersLogger.prototype = {
+  init : function (path) {
+    SpecialPowers.setLogFile(path);
+  },
+
+  getLogCallback : function () {
+    return function (msg) {
+      var data = msg.num + " " + msg.level + " " + msg.info.join(' ') + "\n";
+      SpecialPowers.log(data);
+
+      if (data.indexOf("SimpleTest FINISH") >= 0) {
+        SpecialPowers.closeLogFile();
+      }
+    }
+  },
+
+  log : function (msg) {
+    SpecialPowers.log(msg);
+  },
+
+  close : function () {
+    SpecialPowers.closeLogFile();
+  }
+};
+
+
+/**
+ * MozillaFileLogger, a log listener that can write to a local file.
+ * intended to be run from chrome space
+ */
+
+/** Init the file logger with the absolute path to the file.
+    It will create and append if the file already exists **/
+function MozillaFileLogger(aPath) {
+  // Call the base constructor
+  MozillaLogger.call(this);
+  this.prototype = new MozillaLogger(aPath);
+  this.init(aPath);
+}
+
+MozillaFileLogger.prototype = {
+  
+  init : function (path) {
+    var PR_WRITE_ONLY   = 0x02; // Open for writing only.
+    var PR_CREATE_FILE  = 0x08;
+    var PR_APPEND       = 0x10;
+    this._file = Components.classes["@mozilla.org/file/local;1"].
+                            createInstance(Components.interfaces.nsILocalFile);
+    this._file.initWithPath(path);
+    this._foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
+                                     createInstance(Components.interfaces.nsIFileOutputStream);
+    this._foStream.init(this._file, PR_WRITE_ONLY | PR_CREATE_FILE | PR_APPEND,
+                                     0664, 0);
+  },
+
+  getLogCallback : function() {
+    return function (msg) {
+      var data = msg.num + " " + msg.level + " " + msg.info.join(' ') + "\n";
+      if (MozillaFileLogger._foStream)
+        this._foStream.write(data, data.length);
+
+      if (data.indexOf("SimpleTest FINISH") >= 0) {
+        MozillaFileLogger.close();
+      }
+    }
+  },
+
+  log : function(msg) {
+    if (this._foStream)
+      this._foStream.write(msg, msg.length);
+  },
+
+  close : function() {
+    if(this._foStream)
+      this._foStream.close();
+  
+    this._foStream = null;
+    this._file = null;
+  }
+};
+
+this.MozillaLogger = MozillaLogger;
+this.SpecialPowersLogger = SpecialPowersLogger;
+this.MozillaFileLogger = MozillaFileLogger;
diff --git a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/SpecialPowersObserverAPI.js b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/SpecialPowersObserverAPI.js
new file mode 100644
index 0000000..cb87113
--- /dev/null
+++ b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/SpecialPowersObserverAPI.js
@@ -0,0 +1,414 @@
+/* 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/. */
+"use strict";
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+if (typeof(Ci) == 'undefined') {
+  var Ci = Components.interfaces;
+}
+
+if (typeof(Cc) == 'undefined') {
+  var Cc = Components.classes;
+}
+
+/**
+ * Special Powers Exception - used to throw exceptions nicely
+ **/
+function SpecialPowersException(aMsg) {
+  this.message = aMsg;
+  this.name = "SpecialPowersException";
+}
+
+SpecialPowersException.prototype.toString = function() {
+  return this.name + ': "' + this.message + '"';
+};
+
+this.SpecialPowersObserverAPI = function SpecialPowersObserverAPI() {
+  this._crashDumpDir = null;
+  this._processCrashObserversRegistered = false;
+  this._chromeScriptListeners = [];
+}
+
+function parseKeyValuePairs(text) {
+  var lines = text.split('\n');
+  var data = {};
+  for (let i = 0; i < lines.length; i++) {
+    if (lines[i] == '')
+      continue;
+
+    // can't just .split() because the value might contain = characters
+    let eq = lines[i].indexOf('=');
+    if (eq != -1) {
+      let [key, value] = [lines[i].substring(0, eq),
+                          lines[i].substring(eq + 1)];
+      if (key && value)
+        data[key] = value.replace(/\\n/g, "\n").replace(/\\\\/g, "\\");
+    }
+  }
+  return data;
+}
+
+function parseKeyValuePairsFromFile(file) {
+  var fstream = Cc["@mozilla.org/network/file-input-stream;1"].
+                createInstance(Ci.nsIFileInputStream);
+  fstream.init(file, -1, 0, 0);
+  var is = Cc["@mozilla.org/intl/converter-input-stream;1"].
+           createInstance(Ci.nsIConverterInputStream);
+  is.init(fstream, "UTF-8", 1024, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
+  var str = {};
+  var contents = '';
+  while (is.readString(4096, str) != 0) {
+    contents += str.value;
+  }
+  is.close();
+  fstream.close();
+  return parseKeyValuePairs(contents);
+}
+
+SpecialPowersObserverAPI.prototype = {
+
+  _observe: function(aSubject, aTopic, aData) {
+    function addDumpIDToMessage(propertyName) {
+      var id = aSubject.getPropertyAsAString(propertyName);
+      if (id) {
+        message.dumpIDs.push({id: id, extension: "dmp"});
+        message.dumpIDs.push({id: id, extension: "extra"});
+      }
+    }
+
+    switch(aTopic) {
+      case "plugin-crashed":
+      case "ipc:content-shutdown":
+        var message = { type: "crash-observed", dumpIDs: [] };
+        aSubject = aSubject.QueryInterface(Ci.nsIPropertyBag2);
+        if (aTopic == "plugin-crashed") {
+          addDumpIDToMessage("pluginDumpID");
+          addDumpIDToMessage("browserDumpID");
+
+          let pluginID = aSubject.getPropertyAsAString("pluginDumpID");
+          let extra = this._getExtraData(pluginID);
+          if (extra && ("additional_minidumps" in extra)) {
+            let dumpNames = extra.additional_minidumps.split(',');
+            for (let name of dumpNames) {
+              message.dumpIDs.push({id: pluginID + "-" + name, extension: "dmp"});
+            }
+          }
+        } else { // ipc:content-shutdown
+          addDumpIDToMessage("dumpID");
+        }
+        this._sendAsyncMessage("SPProcessCrashService", message);
+        break;
+    }
+  },
+
+  _getCrashDumpDir: function() {
+    if (!this._crashDumpDir) {
+      this._crashDumpDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
+      this._crashDumpDir.append("minidumps");
+    }
+    return this._crashDumpDir;
+  },
+
+  _getExtraData: function(dumpId) {
+    let extraFile = this._getCrashDumpDir().clone();
+    extraFile.append(dumpId + ".extra");
+    if (!extraFile.exists()) {
+      return null;
+    }
+    return parseKeyValuePairsFromFile(extraFile);
+  },
+
+  _deleteCrashDumpFiles: function(aFilenames) {
+    var crashDumpDir = this._getCrashDumpDir();
+    if (!crashDumpDir.exists()) {
+      return false;
+    }
+
+    var success = aFilenames.length != 0;
+    aFilenames.forEach(function(crashFilename) {
+      var file = crashDumpDir.clone();
+      file.append(crashFilename);
+      if (file.exists()) {
+        file.remove(false);
+      } else {
+        success = false;
+      }
+    });
+    return success;
+  },
+
+  _findCrashDumpFiles: function(aToIgnore) {
+    var crashDumpDir = this._getCrashDumpDir();
+    var entries = crashDumpDir.exists() && crashDumpDir.directoryEntries;
+    if (!entries) {
+      return [];
+    }
+
+    var crashDumpFiles = [];
+    while (entries.hasMoreElements()) {
+      var file = entries.getNext().QueryInterface(Ci.nsIFile);
+      var path = String(file.path);
+      if (path.match(/\.(dmp|extra)$/) && !aToIgnore[path]) {
+        crashDumpFiles.push(path);
+      }
+    }
+    return crashDumpFiles.concat();
+  },
+
+  _getURI: function (url) {
+    return Services.io.newURI(url, null, null);
+  },
+
+  _readUrlAsString: function(aUrl) {
+    // Fetch script content as we can't use scriptloader's loadSubScript
+    // to evaluate http:// urls...
+    var scriptableStream = Cc["@mozilla.org/scriptableinputstream;1"]
+                             .getService(Ci.nsIScriptableInputStream);
+    var channel = Services.io.newChannel(aUrl, null, null);
+    var input = channel.open();
+    scriptableStream.init(input);
+
+    var str;
+    var buffer = [];
+
+    while ((str = scriptableStream.read(4096))) {
+      buffer.push(str);
+    }
+
+    var output = buffer.join("");
+
+    scriptableStream.close();
+    input.close();
+
+    var status;
+    try {
+      channel.QueryInterface(Ci.nsIHttpChannel);
+      status = channel.responseStatus;
+    } catch(e) {
+      /* The channel is not a nsIHttpCHannel, but that's fine */
+      dump("-*- _readUrlAsString: Got an error while fetching " +
+           "chrome script '" + aUrl + "': (" + e.name + ") " + e.message + ". " +
+           "Ignoring.\n");
+    }
+
+    if (status == 404) {
+      throw new SpecialPowersException(
+        "Error while executing chrome script '" + aUrl + "':\n" +
+        "The script doesn't exists. Ensure you have registered it in " +
+        "'support-files' in your mochitest.ini.");
+    }
+
+    return output;
+  },
+
+  /**
+   * messageManager callback function
+   * This will get requests from our API in the window and process them in chrome for it
+   **/
+  _receiveMessageAPI: function(aMessage) {
+    // We explicitly return values in the below code so that this function
+    // doesn't trigger a flurry of warnings about "does not always return
+    // a value".
+    switch(aMessage.name) {
+      case "SPPrefService":
+        var prefs = Services.prefs;
+        var prefType = aMessage.json.prefType.toUpperCase();
+        var prefName = aMessage.json.prefName;
+        var prefValue = "prefValue" in aMessage.json ? aMessage.json.prefValue : null;
+
+        if (aMessage.json.op == "get") {
+          if (!prefName || !prefType)
+            throw new SpecialPowersException("Invalid parameters for get in SPPrefService");
+
+          // return null if the pref doesn't exist
+          if (prefs.getPrefType(prefName) == prefs.PREF_INVALID)
+            return null;
+        } else if (aMessage.json.op == "set") {
+          if (!prefName || !prefType  || prefValue === null)
+            throw new SpecialPowersException("Invalid parameters for set in SPPrefService");
+        } else if (aMessage.json.op == "clear") {
+          if (!prefName)
+            throw new SpecialPowersException("Invalid parameters for clear in SPPrefService");
+        } else {
+          throw new SpecialPowersException("Invalid operation for SPPrefService");
+        }
+
+        // Now we make the call
+        switch(prefType) {
+          case "BOOL":
+            if (aMessage.json.op == "get")
+              return(prefs.getBoolPref(prefName));
+            else 
+              return(prefs.setBoolPref(prefName, prefValue));
+          case "INT":
+            if (aMessage.json.op == "get") 
+              return(prefs.getIntPref(prefName));
+            else
+              return(prefs.setIntPref(prefName, prefValue));
+          case "CHAR":
+            if (aMessage.json.op == "get")
+              return(prefs.getCharPref(prefName));
+            else
+              return(prefs.setCharPref(prefName, prefValue));
+          case "COMPLEX":
+            if (aMessage.json.op == "get")
+              return(prefs.getComplexValue(prefName, prefValue[0]));
+            else
+              return(prefs.setComplexValue(prefName, prefValue[0], prefValue[1]));
+          case "":
+            if (aMessage.json.op == "clear") {
+              prefs.clearUserPref(prefName);
+              return undefined;
+            }
+        }
+        return undefined;	// See comment at the beginning of this function.
+
+      case "SPProcessCrashService":
+        switch (aMessage.json.op) {
+          case "register-observer":
+            this._addProcessCrashObservers();
+            break;
+          case "unregister-observer":
+            this._removeProcessCrashObservers();
+            break;
+          case "delete-crash-dump-files":
+            return this._deleteCrashDumpFiles(aMessage.json.filenames);
+          case "find-crash-dump-files":
+            return this._findCrashDumpFiles(aMessage.json.crashDumpFilesToIgnore);
+          default:
+            throw new SpecialPowersException("Invalid operation for SPProcessCrashService");
+        }
+        return undefined;	// See comment at the beginning of this function.
+
+      case "SPPermissionManager":
+        let msg = aMessage.json;
+
+        let secMan = Services.scriptSecurityManager;
+        let principal = secMan.getAppCodebasePrincipal(this._getURI(msg.url), msg.appId, msg.isInBrowserElement);
+
+        switch (msg.op) {
+          case "add":
+            Services.perms.addFromPrincipal(principal, msg.type, msg.permission);
+            break;
+          case "remove":
+            Services.perms.removeFromPrincipal(principal, msg.type);
+            break;
+          case "has":
+            let hasPerm = Services.perms.testPermissionFromPrincipal(principal, msg.type);
+            if (hasPerm == Ci.nsIPermissionManager.ALLOW_ACTION) 
+              return true;
+            return false;
+            break;
+          case "test":
+            let testPerm = Services.perms.testPermissionFromPrincipal(principal, msg.type, msg.value);
+            if (testPerm == msg.value)  {
+              return true;
+            }
+            return false;
+            break;
+          default:
+            throw new SpecialPowersException("Invalid operation for " +
+                                             "SPPermissionManager");
+        }
+        return undefined;	// See comment at the beginning of this function.
+
+      case "SPWebAppService":
+        let Webapps = {};
+        Components.utils.import("resource://gre/modules/Webapps.jsm", Webapps);
+        switch (aMessage.json.op) {
+          case "set-launchable":
+            let val = Webapps.DOMApplicationRegistry.allAppsLaunchable;
+            Webapps.DOMApplicationRegistry.allAppsLaunchable = aMessage.json.launchable;
+            return val;
+          default:
+            throw new SpecialPowersException("Invalid operation for SPWebAppsService");
+        }
+        return undefined;	// See comment at the beginning of this function.
+
+      case "SPObserverService":
+        switch (aMessage.json.op) {
+          case "notify":
+            let topic = aMessage.json.observerTopic;
+            let data = aMessage.json.observerData
+            Services.obs.notifyObservers(null, topic, data);
+            break;
+          default:
+            throw new SpecialPowersException("Invalid operation for SPObserverervice");
+        }
+        return undefined;	// See comment at the beginning of this function.
+
+      case "SPLoadChromeScript":
+        var url = aMessage.json.url;
+        var id = aMessage.json.id;
+
+        var jsScript = this._readUrlAsString(url);
+
+        // Setup a chrome sandbox that has access to sendAsyncMessage
+        // and addMessageListener in order to communicate with
+        // the mochitest.
+        var systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+        var sb = Components.utils.Sandbox(systemPrincipal);
+        var mm = aMessage.target
+                         .QueryInterface(Ci.nsIFrameLoaderOwner)
+                         .frameLoader
+                         .messageManager;
+        sb.sendAsyncMessage = (name, message) => {
+          mm.sendAsyncMessage("SPChromeScriptMessage",
+                              { id: id, name: name, message: message });
+        };
+        sb.addMessageListener = (name, listener) => {
+          this._chromeScriptListeners.push({ id: id, name: name, listener: listener });
+        };
+
+        // Also expose assertion functions
+        let reporter = function (err, message, stack) {
+          // 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",
+                                                scope);
+
+            let assert = new scope.Assert(reporter);
+            delete sb.assert;
+            return sb.assert = assert;
+          },
+          configurable: true
+        });
+
+        // Evaluate the chrome script
+        try {
+          Components.utils.evalInSandbox(jsScript, sb, "1.8", url, 1);
+        } catch(e) {
+          throw new SpecialPowersException("Error while executing chrome " +
+                                           "script '" + url + "':\n" + e + "\n" +
+                                           e.fileName + ":" + e.lineNumber);
+        }
+        return undefined;	// See comment at the beginning of this function.
+
+      case "SPChromeScriptMessage":
+        var id = aMessage.json.id;
+        var name = aMessage.json.name;
+        var message = aMessage.json.message;
+        this._chromeScriptListeners
+            .filter(o => (o.name == name && o.id == id))
+            .forEach(o => o.listener(message));
+        return undefined;	// See comment at the beginning of this function.
+
+      default:
+        throw new SpecialPowersException("Unrecognized Special Powers API");
+    }
+
+    // We throw an exception before reaching this explicit return because
+    // we should never be arriving here anyway.
+    throw new SpecialPowersException("Unreached code");
+    return undefined;
+  }
+};
+
diff --git a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/specialpowers.js b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/specialpowers.js
index 538b104..31ff093 100644
--- a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/specialpowers.js
+++ b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/specialpowers.js
@@ -1,332 +1,101 @@
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Special Powers code
- *
- * The Initial Developer of the Original Code is
- * Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2010
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Clint Talbert cmtalbert at gmail.com
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK *****/
+/* 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/. */
 /* This code is loaded in every child process that is started by mochitest in
  * order to be used as a replacement for UniversalXPConnect
  */
 
-var Ci = Components.interfaces;
-var Cc = Components.classes;
-
 function SpecialPowers(window) {
-  this.window = window;
-  bindDOMWindowUtils(this, window);
+  this.window = Components.utils.getWeakReference(window);
   this._encounteredCrashDumpFiles = [];
   this._unexpectedCrashDumpFiles = { };
   this._crashDumpDir = null;
+  this.DOMWindowUtils = bindDOMWindowUtils(window);
+  Object.defineProperty(this, 'Components', {
+      configurable: true, enumerable: true, get: function() {
+          var win = this.window.get();
+          if (!win)
+              return null;
+          return getRawComponents(win);
+      }});
   this._pongHandlers = [];
   this._messageListener = this._messageReceived.bind(this);
   addMessageListener("SPPingService", this._messageListener);
 }
 
-function bindDOMWindowUtils(sp, window) {
-  var util = window.QueryInterface(Ci.nsIInterfaceRequestor)
-                   .getInterface(Ci.nsIDOMWindowUtils);
-  // This bit of magic brought to you by the letters
-  // B Z, and E, S and the number 5.
-  //
-  // Take all of the properties on the nsIDOMWindowUtils-implementing
-  // object, and rebind them onto a new object with a stub that uses
-  // apply to call them from this privileged scope. This way we don't
-  // have to explicitly stub out new methods that appear on
-  // nsIDOMWindowUtils.
-  var proto = Object.getPrototypeOf(util);
-  var target = {};
-  function rebind(desc, prop) {
-    if (prop in desc && typeof(desc[prop]) == "function") {
-      var oldval = desc[prop];
-      desc[prop] = function() { return oldval.apply(util, arguments); };
-    }
-  }
-  for (var i in proto) {
-    var desc = Object.getOwnPropertyDescriptor(proto, i);
-    rebind(desc, "get");
-    rebind(desc, "set");
-    rebind(desc, "value");
-    Object.defineProperty(target, i, desc);
-  }
-  sp.DOMWindowUtils = target;
-}
-
-SpecialPowers.prototype = {
-  toString: function() { return "[SpecialPowers]"; },
-  sanityCheck: function() { return "foo"; },
-
-  // This gets filled in in the constructor.
-  DOMWindowUtils: undefined,
-
-  // Mimic the get*Pref API
-  getBoolPref: function(aPrefName) {
-    return (this._getPref(aPrefName, 'BOOL'));
-  },
-  getIntPref: function(aPrefName) {
-    return (this._getPref(aPrefName, 'INT'));
-  },
-  getCharPref: function(aPrefName) {
-    return (this._getPref(aPrefName, 'CHAR'));
-  },
-  getComplexValue: function(aPrefName, aIid) {
-    return (this._getPref(aPrefName, 'COMPLEX', aIid));
-  },
-
-  // Mimic the set*Pref API
-  setBoolPref: function(aPrefName, aValue) {
-    return (this._setPref(aPrefName, 'BOOL', aValue));
-  },
-  setIntPref: function(aPrefName, aValue) {
-    return (this._setPref(aPrefName, 'INT', aValue));
-  },
-  setCharPref: function(aPrefName, aValue) {
-    return (this._setPref(aPrefName, 'CHAR', aValue));
-  },
-  setComplexValue: function(aPrefName, aIid, aValue) {
-    return (this._setPref(aPrefName, 'COMPLEX', aValue, aIid));
-  },
+SpecialPowers.prototype = new SpecialPowersAPI();
 
-  // Mimic the clearUserPref API
-  clearUserPref: function(aPrefName) {
-    var msg = {'op':'clear', 'prefName': aPrefName, 'prefType': ""};
-    sendSyncMessage('SPPrefService', msg);
-  },
+SpecialPowers.prototype.toString = function() { return "[SpecialPowers]"; };
+SpecialPowers.prototype.sanityCheck = function() { return "foo"; };
 
-  // Private pref functions to communicate to chrome
-  _getPref: function(aPrefName, aPrefType, aIid) {
-    var msg = {};
-    if (aIid) {
-      // Overloading prefValue to handle complex prefs
-      msg = {'op':'get', 'prefName': aPrefName, 'prefType':aPrefType, 'prefValue':[aIid]};
-    } else {
-      msg = {'op':'get', 'prefName': aPrefName,'prefType': aPrefType};
-    }
-    return(sendSyncMessage('SPPrefService', msg)[0]);
-  },
-  _setPref: function(aPrefName, aPrefType, aValue, aIid) {
-    var msg = {};
-    if (aIid) {
-      msg = {'op':'set','prefName':aPrefName, 'prefType': aPrefType, 'prefValue': [aIid,aValue]};
-    } else {
-      msg = {'op':'set', 'prefName': aPrefName, 'prefType': aPrefType, 'prefValue': aValue};
-    }
-    return(sendSyncMessage('SPPrefService', msg)[0]);
-  },
-
-  //XXX: these APIs really ought to be removed, they're not e10s-safe.
-  // (also they're pretty Firefox-specific)
-  _getTopChromeWindow: function(window) {
-    return window.QueryInterface(Ci.nsIInterfaceRequestor)
-                 .getInterface(Ci.nsIWebNavigation)
-                 .QueryInterface(Ci.nsIDocShellTreeItem)
-                 .rootTreeItem
-                 .QueryInterface(Ci.nsIInterfaceRequestor)
-                 .getInterface(Ci.nsIDOMWindow)
-                 .QueryInterface(Ci.nsIDOMChromeWindow);
-  },
-  _getDocShell: function(window) {
-    return window.QueryInterface(Ci.nsIInterfaceRequestor)
-                 .getInterface(Ci.nsIWebNavigation)
-                 .QueryInterface(Ci.nsIDocShell);
-  },
-  _getMUDV: function(window) {
-    return this._getDocShell(window).contentViewer
-               .QueryInterface(Ci.nsIMarkupDocumentViewer);
-  },
-  _getAutoCompletePopup: function(window) {
-    return this._getTopChromeWindow(window).document
-                                           .getElementById("PopupAutoComplete");
-  },
-  addAutoCompletePopupEventListener: function(window, listener) {
-    this._getAutoCompletePopup(window).addEventListener("popupshowing",
-                                                        listener,
-                                                        false);
-  },
-  removeAutoCompletePopupEventListener: function(window, listener) {
-    this._getAutoCompletePopup(window).removeEventListener("popupshowing",
-                                                           listener,
-                                                           false);
-  },
-  isBackButtonEnabled: function(window) {
-    return !this._getTopChromeWindow(window).document
-                                      .getElementById("Browser:Back")
-                                      .hasAttribute("disabled");
-  },
+// This gets filled in in the constructor.
+SpecialPowers.prototype.DOMWindowUtils = undefined;
+SpecialPowers.prototype.Components = undefined;
 
-  addChromeEventListener: function(type, listener, capture, allowUntrusted) {
-    addEventListener(type, listener, capture, allowUntrusted);
-  },
-  removeChromeEventListener: function(type, listener, capture) {
-    removeEventListener(type, listener, capture);
-  },
-
-  getFullZoom: function(window) {
-    return this._getMUDV(window).fullZoom;
-  },
-  setFullZoom: function(window, zoom) {
-    this._getMUDV(window).fullZoom = zoom;
-  },
-  getTextZoom: function(window) {
-    return this._getMUDV(window).textZoom;
-  },
-  setTextZoom: function(window, zoom) {
-    this._getMUDV(window).textZoom = zoom;
-  },
+SpecialPowers.prototype._sendSyncMessage = function(msgname, msg) {
+  return sendSyncMessage(msgname, msg);
+};
 
-  createSystemXHR: function() {
-    return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
-             .createInstance(Ci.nsIXMLHttpRequest);
-  },
+SpecialPowers.prototype._sendAsyncMessage = function(msgname, msg) {
+  sendAsyncMessage(msgname, msg);
+};
 
-  gc: function() {
-    this.DOMWindowUtils.garbageCollect();
-  },
+SpecialPowers.prototype._addMessageListener = function(msgname, listener) {
+  addMessageListener(msgname, listener);
+};
 
-  hasContentProcesses: function() {
-    try {
-      var rt = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
-      return rt.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
-    } catch (e) {
-      return true;
-    }
-  },
+SpecialPowers.prototype._removeMessageListener = function(msgname, listener) {
+  removeMessageListener(msgname, listener);
+};
 
-  registerProcessCrashObservers: function() {
-    addMessageListener("SPProcessCrashService", this._messageListener);
-    sendSyncMessage("SPProcessCrashService", { op: "register-observer" });
-  },
+SpecialPowers.prototype.registerProcessCrashObservers = function() {
+  addMessageListener("SPProcessCrashService", this._messageListener);
+  sendSyncMessage("SPProcessCrashService", { op: "register-observer" });
+};
 
-  _messageReceived: function(aMessage) {
-    switch (aMessage.name) {
-      case "SPProcessCrashService":
-        if (aMessage.json.type == "crash-observed") {
-          var self = this;
-          aMessage.json.dumpIDs.forEach(function(id) {
-            self._encounteredCrashDumpFiles.push(id + ".dmp");
-            self._encounteredCrashDumpFiles.push(id + ".extra");
-          });
-        }
-        break;
+SpecialPowers.prototype.unregisterProcessCrashObservers = function() {
+  addMessageListener("SPProcessCrashService", this._messageListener);
+  sendSyncMessage("SPProcessCrashService", { op: "unregister-observer" });
+};
 
-      case "SPPingService":
-        if (aMessage.json.op == "pong") {
-          var handler = this._pongHandlers.shift();
-          if (handler) {
-            handler();
-          }
+SpecialPowers.prototype._messageReceived = function(aMessage) {
+  switch (aMessage.name) {
+    case "SPProcessCrashService":
+      if (aMessage.json.type == "crash-observed") {
+        for (let e of aMessage.json.dumpIDs) {
+          this._encounteredCrashDumpFiles.push(e.id + "." + e.extension);
         }
-        break;
-    }
-    return true;
-  },
-
-  removeExpectedCrashDumpFiles: function(aExpectingProcessCrash) {
-    var success = true;
-    if (aExpectingProcessCrash) {
-      var message = {
-        op: "delete-crash-dump-files",
-        filenames: this._encounteredCrashDumpFiles 
-      };
-      if (!sendSyncMessage("SPProcessCrashService", message)[0]) {
-        success = false;
       }
-    }
-    this._encounteredCrashDumpFiles.length = 0;
-    return success;
-  },
-
-  findUnexpectedCrashDumpFiles: function() {
-    var self = this;
-    var message = {
-      op: "find-crash-dump-files",
-      crashDumpFilesToIgnore: this._unexpectedCrashDumpFiles
-    };
-    var crashDumpFiles = sendSyncMessage("SPProcessCrashService", message)[0];
-    crashDumpFiles.forEach(function(aFilename) {
-      self._unexpectedCrashDumpFiles[aFilename] = true;
-    });
-    return crashDumpFiles;
-  },
+      break;
 
-  executeAfterFlushingMessageQueue: function(aCallback) {
-    this._pongHandlers.push(aCallback);
-    sendAsyncMessage("SPPingService", { op: "ping" });
-  },
-
-  executeSoon: function(aFunc) {
-    var tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
-    tm.mainThread.dispatch({
-      run: function() {
-        aFunc();
+    case "SPPingService":
+      if (aMessage.json.op == "pong") {
+        var handler = this._pongHandlers.shift();
+        if (handler) {
+          handler();
+        }
       }
-    }, Ci.nsIThread.DISPATCH_NORMAL);
-  },
+      break;
+  }
+  return true;
+};
 
-  /* from http://mxr.mozilla.org/mozilla-central/source/testing/mochitest/tests/SimpleTest/quit.js
-   * by Bob Clary, Jeff Walden, and Robert Sayre.
-   */
-  quitApplication: function() {
-      function canQuitApplication()
-      {
-	  var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
-	  if (!os)
-	      return true;
-  
-	  try {
-	      var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
-	      os.notifyObservers(cancelQuit, "quit-application-requested", null);
-    
-	      // Something aborted the quit process. 
-	      if (cancelQuit.data)
-		  return false;
-	  } catch (ex) {}
-	  return true;
-      }
+SpecialPowers.prototype.quit = function() {
+  sendAsyncMessage("SpecialPowers.Quit", {});
+};
 
-      if (!canQuitApplication())
-	  return false;
-	  
-      var appService = Cc['@mozilla.org/toolkit/app-startup;1'].getService(Ci.nsIAppStartup);
-      appService.quit(Ci.nsIAppStartup.eForceQuit);
-      return true;
-  }
+SpecialPowers.prototype.executeAfterFlushingMessageQueue = function(aCallback) {
+  this._pongHandlers.push(aCallback);
+  sendAsyncMessage("SPPingService", { op: "ping" });
 };
 
 // Expose everything but internal APIs (starting with underscores) to
-// web content.
+// web content.  We cannot use Object.keys to view SpecialPowers.prototype since
+// we are using the functions from SpecialPowersAPI.prototype
 SpecialPowers.prototype.__exposedProps__ = {};
-for each (i in Object.keys(SpecialPowers.prototype).filter(function(v) {return v.charAt(0) != "_";})) {
-  SpecialPowers.prototype.__exposedProps__[i] = "r";
+for (var i in SpecialPowers.prototype) {
+  if (i.charAt(0) != "_")
+    SpecialPowers.prototype.__exposedProps__[i] = "r";
 }
 
 // Attach our API to the window.
@@ -355,18 +124,11 @@ function SpecialPowersManager() {
 SpecialPowersManager.prototype = {
   handleEvent: function handleEvent(aEvent) {
     var window = aEvent.target.defaultView;
-
-    // Need to make sure we are called on what we care about -
-    // content windows. DOMWindowCreated is called on *all* HTMLDocuments,
-    // some of which belong to chrome windows or other special content.
-    //
-    var uri = window.document.documentURIObject;
-    if (uri.scheme === "chrome" || uri.spec.split(":")[0] == "about") {
-      return;
-    }
-
     attachSpecialPowersToWindow(window);
   }
 };
 
 var specialpowersmanager = new SpecialPowersManager();
+
+this.SpecialPowers = SpecialPowers;
+this.attachSpecialPowersToWindow = attachSpecialPowersToWindow;
diff --git a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/specialpowersAPI.js b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/specialpowersAPI.js
new file mode 100644
index 0000000..940bb01
--- /dev/null
+++ b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/content/specialpowersAPI.js
@@ -0,0 +1,1744 @@
+/* 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/. */
+/* This code is loaded in every child process that is started by mochitest in
+ * order to be used as a replacement for UniversalXPConnect
+ */
+
+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://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function SpecialPowersAPI() {
+  this._consoleListeners = [];
+  this._encounteredCrashDumpFiles = [];
+  this._unexpectedCrashDumpFiles = { };
+  this._crashDumpDir = null;
+  this._mfl = null;
+  this._prefEnvUndoStack = [];
+  this._pendingPrefs = [];
+  this._applyingPrefs = false;
+  this._permissionsUndoStack = [];
+  this._pendingPermissions = [];
+  this._applyingPermissions = false;
+  this._fm = null;
+  this._cb = null;
+}
+
+function bindDOMWindowUtils(aWindow) {
+  if (!aWindow)
+    return
+
+   var util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                     .getInterface(Ci.nsIDOMWindowUtils);
+   return wrapPrivileged(util);
+}
+
+function getRawComponents(aWindow) {
+  // If we're running in automation that supports enablePrivilege, then we also
+  // provided access to the privileged Components.
+  try {
+    let win = Cu.waiveXrays(aWindow);
+    if (typeof win.netscape.security.PrivilegeManager == 'object')
+      Cu.forcePrivilegedComponentsForScope(aWindow);
+  } catch (e) {}
+  return Cu.getComponentsForScope(aWindow);
+}
+
+function isWrappable(x) {
+  if (typeof x === "object")
+    return x !== null;
+  return typeof x === "function";
+};
+
+function isWrapper(x) {
+  return isWrappable(x) && (typeof x.SpecialPowers_wrappedObject !== "undefined");
+};
+
+function unwrapIfWrapped(x) {
+  return isWrapper(x) ? unwrapPrivileged(x) : x;
+};
+
+function wrapIfUnwrapped(x) {
+  return isWrapper(x) ? x : wrapPrivileged(x);
+}
+
+function isXrayWrapper(x) {
+  return Cu.isXrayWrapper(x);
+}
+
+function callGetOwnPropertyDescriptor(obj, name) {
+  // Quickstubbed getters and setters are propertyOps, and don't get reified
+  // until someone calls __lookupGetter__ or __lookupSetter__ on them (note
+  // that there are special version of those functions for quickstubs, so
+  // apply()ing Object.prototype.__lookupGetter__ isn't good enough). Try to
+  // trigger reification before calling Object.getOwnPropertyDescriptor.
+  //
+  // See bug 764315.
+  try {
+    obj.__lookupGetter__(name);
+    obj.__lookupSetter__(name);
+  } catch(e) { }
+  return Object.getOwnPropertyDescriptor(obj, name);
+}
+
+// We can't call apply() directy on Xray-wrapped functions, so we have to be
+// clever.
+function doApply(fun, invocant, args) {
+  return Function.prototype.apply.call(fun, invocant, args);
+}
+
+function wrapPrivileged(obj) {
+
+  // Primitives pass straight through.
+  if (!isWrappable(obj))
+    return obj;
+
+  // No double wrapping.
+  if (isWrapper(obj))
+    throw "Trying to double-wrap object!";
+
+  // Make our core wrapper object.
+  var handler = new SpecialPowersHandler(obj);
+
+  // If the object is callable, make a function proxy.
+  if (typeof obj === "function") {
+    var callTrap = function() {
+      // The invocant and arguments may or may not be wrappers. Unwrap them if necessary.
+      var invocant = unwrapIfWrapped(this);
+      var unwrappedArgs = Array.prototype.slice.call(arguments).map(unwrapIfWrapped);
+
+      try {
+        return wrapPrivileged(doApply(obj, invocant, unwrappedArgs));
+      } catch (e) {
+        // Wrap exceptions and re-throw them.
+        throw wrapIfUnwrapped(e);
+      }
+    };
+    var constructTrap = function() {
+      // The arguments may or may not be wrappers. Unwrap them if necessary.
+      var unwrappedArgs = Array.prototype.slice.call(arguments).map(unwrapIfWrapped);
+
+      // We want to invoke "obj" as a constructor, but using unwrappedArgs as
+      // the arguments.  Make sure to wrap and re-throw exceptions!
+      try {
+        return wrapPrivileged(new obj(...unwrappedArgs));
+      } catch (e) {
+        throw wrapIfUnwrapped(e);
+      }
+    };
+
+    return Proxy.createFunction(handler, callTrap, constructTrap);
+  }
+
+  // Otherwise, just make a regular object proxy.
+  return Proxy.create(handler);
+};
+
+function unwrapPrivileged(x) {
+
+  // We don't wrap primitives, so sometimes we have a primitive where we'd
+  // expect to have a wrapper. The proxy pretends to be the type that it's
+  // emulating, so we can just as easily check isWrappable() on a proxy as
+  // we can on an unwrapped object.
+  if (!isWrappable(x))
+    return x;
+
+  // If we have a wrappable type, make sure it's wrapped.
+  if (!isWrapper(x))
+    throw "Trying to unwrap a non-wrapped object!";
+
+  // Unwrap.
+  return x.SpecialPowers_wrappedObject;
+};
+
+function crawlProtoChain(obj, fn) {
+  var rv = fn(obj);
+  if (rv !== undefined)
+    return rv;
+  if (Object.getPrototypeOf(obj))
+    return crawlProtoChain(Object.getPrototypeOf(obj), fn);
+};
+
+/*
+ * We want to waive the __exposedProps__ security check for SpecialPowers-wrapped
+ * objects. We do this by creating a proxy singleton that just always returns 'rw'
+ * for any property name.
+ */
+function ExposedPropsWaiverHandler() {
+  // NB: XPConnect denies access if the relevant member of __exposedProps__ is not
+  // enumerable.
+  var _permit = { value: 'rw', writable: false, configurable: false, enumerable: true };
+  return {
+    getOwnPropertyDescriptor: function(name) { return _permit; },
+    getPropertyDescriptor: function(name) { return _permit; },
+    getOwnPropertyNames: function() { throw Error("Can't enumerate ExposedPropsWaiver"); },
+    getPropertyNames: function() { throw Error("Can't enumerate ExposedPropsWaiver"); },
+    enumerate: function() { throw Error("Can't enumerate ExposedPropsWaiver"); },
+    defineProperty: function(name) { throw Error("Can't define props on ExposedPropsWaiver"); },
+    delete: function(name) { throw Error("Can't delete props from ExposedPropsWaiver"); }
+  };
+};
+ExposedPropsWaiver = Proxy.create(ExposedPropsWaiverHandler());
+
+function SpecialPowersHandler(obj) {
+  this.wrappedObject = obj;
+};
+
+// Allow us to transitively maintain the membrane by wrapping descriptors
+// we return.
+SpecialPowersHandler.prototype.doGetPropertyDescriptor = function(name, own) {
+
+  // Handle our special API.
+  if (name == "SpecialPowers_wrappedObject")
+    return { value: this.wrappedObject, writeable: false, configurable: false, enumerable: false };
+
+  // Handle __exposedProps__.
+  if (name == "__exposedProps__")
+    return { value: ExposedPropsWaiver, writable: false, configurable: false, enumerable: false };
+
+  // In general, we want Xray wrappers for content DOM objects, because waiving
+  // Xray gives us Xray waiver wrappers that clamp the principal when we cross
+  // compartment boundaries. However, Xray adds some gunk to toString(), which
+  // has the potential to confuse consumers that aren't expecting Xray wrappers.
+  // Since toString() is a non-privileged method that returns only strings, we
+  // can just waive Xray for that case.
+  var obj = name == 'toString' ? XPCNativeWrapper.unwrap(this.wrappedObject)
+                               : this.wrappedObject;
+
+  //
+  // Call through to the wrapped object.
+  //
+  // Note that we have several cases here, each of which requires special handling.
+  //
+  var desc;
+
+  // Case 1: Own Properties.
+  //
+  // This one is easy, thanks to Object.getOwnPropertyDescriptor().
+  if (own)
+    desc = callGetOwnPropertyDescriptor(obj, name);
+
+  // Case 2: Not own, not Xray-wrapped.
+  //
+  // Here, we can just crawl the prototype chain, calling
+  // Object.getOwnPropertyDescriptor until we find what we want.
+  //
+  // NB: Make sure to check this.wrappedObject here, rather than obj, because
+  // we may have waived Xray on obj above.
+  else if (!isXrayWrapper(this.wrappedObject))
+    desc = crawlProtoChain(obj, function(o) {return callGetOwnPropertyDescriptor(o, name);});
+
+  // Case 3: Not own, Xray-wrapped.
+  //
+  // This one is harder, because we Xray wrappers are flattened and don't have
+  // a prototype. Xray wrappers are proxies themselves, so we'd love to just call
+  // through to XrayWrapper<Base>::getPropertyDescriptor(). Unfortunately though,
+  // we don't have any way to do that. :-(
+  //
+  // So we first try with a call to getOwnPropertyDescriptor(). If that fails,
+  // we make up a descriptor, using some assumptions about what kinds of things
+  // tend to live on the prototypes of Xray-wrapped objects.
+  else {
+    desc = Object.getOwnPropertyDescriptor(obj, name);
+    if (!desc) {
+      var getter = Object.prototype.__lookupGetter__.call(obj, name);
+      var setter = Object.prototype.__lookupSetter__.call(obj, name);
+      if (getter || setter)
+        desc = {get: getter, set: setter, configurable: true, enumerable: true};
+      else if (name in obj)
+        desc = {value: obj[name], writable: false, configurable: true, enumerable: true};
+    }
+  }
+
+  // Bail if we've got nothing.
+  if (typeof desc === 'undefined')
+    return undefined;
+
+  // When accessors are implemented as JSPropertyOps rather than JSNatives (ie,
+  // QuickStubs), the js engine does the wrong thing and treats it as a value
+  // descriptor rather than an accessor descriptor. Jorendorff suggested this
+  // little hack to work around it. See bug 520882.
+  if (desc && 'value' in desc && desc.value === undefined)
+    desc.value = obj[name];
+
+  // A trapping proxy's properties must always be configurable, but sometimes
+  // this we get non-configurable properties from Object.getOwnPropertyDescriptor().
+  // Tell a white lie.
+  desc.configurable = true;
+
+  // Transitively maintain the wrapper membrane.
+  function wrapIfExists(key) { if (key in desc) desc[key] = wrapPrivileged(desc[key]); };
+  wrapIfExists('value');
+  wrapIfExists('get');
+  wrapIfExists('set');
+
+  return desc;
+};
+
+SpecialPowersHandler.prototype.getOwnPropertyDescriptor = function(name) {
+  return this.doGetPropertyDescriptor(name, true);
+};
+
+SpecialPowersHandler.prototype.getPropertyDescriptor = function(name) {
+  return this.doGetPropertyDescriptor(name, false);
+};
+
+function doGetOwnPropertyNames(obj, props) {
+
+  // Insert our special API. It's not enumerable, but getPropertyNames()
+  // includes non-enumerable properties.
+  var specialAPI = 'SpecialPowers_wrappedObject';
+  if (props.indexOf(specialAPI) == -1)
+    props.push(specialAPI);
+
+  // Do the normal thing.
+  var flt = function(a) { return props.indexOf(a) == -1; };
+  props = props.concat(Object.getOwnPropertyNames(obj).filter(flt));
+
+  // If we've got an Xray wrapper, include the expandos as well.
+  if ('wrappedJSObject' in obj)
+    props = props.concat(Object.getOwnPropertyNames(obj.wrappedJSObject)
+                         .filter(flt));
+
+  return props;
+}
+
+SpecialPowersHandler.prototype.getOwnPropertyNames = function() {
+  return doGetOwnPropertyNames(this.wrappedObject, []);
+};
+
+SpecialPowersHandler.prototype.getPropertyNames = function() {
+
+  // Manually walk the prototype chain, making sure to add only property names
+  // that haven't been overridden.
+  //
+  // There's some trickiness here with Xray wrappers. Xray wrappers don't have
+  // a prototype, so we need to unwrap them if we want to get all of the names
+  // with Object.getOwnPropertyNames(). But we don't really want to unwrap the
+  // base object, because that will include expandos that are inaccessible via
+  // our implementation of get{,Own}PropertyDescriptor(). So we unwrap just
+  // before accessing the prototype. This ensures that we get Xray vision on
+  // the base object, and no Xray vision for the rest of the way up.
+  var obj = this.wrappedObject;
+  var props = [];
+  while (obj) {
+    props = doGetOwnPropertyNames(obj, props);
+    obj = Object.getPrototypeOf(XPCNativeWrapper.unwrap(obj));
+  }
+  return props;
+};
+
+SpecialPowersHandler.prototype.defineProperty = function(name, desc) {
+  return Object.defineProperty(this.wrappedObject, name, desc);
+};
+
+SpecialPowersHandler.prototype.delete = function(name) {
+  return delete this.wrappedObject[name];
+};
+
+SpecialPowersHandler.prototype.fix = function() { return undefined; /* Throws a TypeError. */ };
+
+// Per the ES5 spec this is a derived trap, but it's fundamental in spidermonkey
+// for some reason. See bug 665198.
+SpecialPowersHandler.prototype.enumerate = function() {
+  var t = this;
+  var filt = function(name) { return t.getPropertyDescriptor(name).enumerable; };
+  return this.getPropertyNames().filter(filt);
+};
+
+// SPConsoleListener reflects nsIConsoleMessage objects into JS in a
+// tidy, XPCOM-hiding way.  Messages that are nsIScriptError objects
+// have their properties exposed in detail.  It also auto-unregisters
+// itself when it receives a "sentinel" message.
+function SPConsoleListener(callback) {
+  this.callback = callback;
+}
+
+SPConsoleListener.prototype = {
+  observe: function(msg) {
+    let m = { message: msg.message,
+              errorMessage: null,
+              sourceName: null,
+              sourceLine: null,
+              lineNumber: null,
+              columnNumber: null,
+              category: null,
+              windowID: null,
+              isScriptError: false,
+              isWarning: false,
+              isException: false,
+              isStrict: false };
+    if (msg instanceof Ci.nsIScriptError) {
+      m.errorMessage  = msg.errorMessage;
+      m.sourceName    = msg.sourceName;
+      m.sourceLine    = msg.sourceLine;
+      m.lineNumber    = msg.lineNumber;
+      m.columnNumber  = msg.columnNumber;
+      m.category      = msg.category;
+      m.windowID      = msg.outerWindowID;
+      m.isScriptError = true;
+      m.isWarning     = ((msg.flags & Ci.nsIScriptError.warningFlag) === 1);
+      m.isException   = ((msg.flags & Ci.nsIScriptError.exceptionFlag) === 1);
+      m.isStrict      = ((msg.flags & Ci.nsIScriptError.strictFlag) === 1);
+    }
+
+    // expose all props of 'm' as read-only
+    let expose = {};
+    for (let prop in m)
+      expose[prop] = 'r';
+    m.__exposedProps__ = expose;
+    Object.freeze(m);
+
+    this.callback.call(undefined, m);
+
+    if (!m.isScriptError && m.message === "SENTINEL")
+      Services.console.unregisterListener(this);
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener])
+};
+
+function wrapCallback(cb) {
+  return function SpecialPowersCallbackWrapper() {
+    args = Array.prototype.map.call(arguments, wrapIfUnwrapped);
+    return cb.apply(this, args);
+  }
+}
+
+function wrapCallbackObject(obj) {
+  wrapper = { __exposedProps__: ExposedPropsWaiver };
+  for (var i in obj) {
+    if (typeof obj[i] == 'function')
+      wrapper[i] = wrapCallback(obj[i]);
+    else
+      wrapper[i] = obj[i];
+  }
+  return wrapper;
+}
+
+SpecialPowersAPI.prototype = {
+
+  /*
+   * Privileged object wrapping API
+   *
+   * Usage:
+   *   var wrapper = SpecialPowers.wrap(obj);
+   *   wrapper.privilegedMethod(); wrapper.privilegedProperty;
+   *   obj === SpecialPowers.unwrap(wrapper);
+   *
+   * These functions provide transparent access to privileged objects using
+   * various pieces of deep SpiderMagic. Conceptually, a wrapper is just an
+   * object containing a reference to the underlying object, where all method
+   * calls and property accesses are transparently performed with the System
+   * Principal. Moreover, objects obtained from the wrapper (including properties
+   * and method return values) are wrapped automatically. Thus, after a single
+   * call to SpecialPowers.wrap(), the wrapper layer is transitively maintained.
+   *
+   * Known Issues:
+   *
+   *  - The wrapping function does not preserve identity, so
+   *    SpecialPowers.wrap(foo) !== SpecialPowers.wrap(foo). See bug 718543.
+   *
+   *  - The wrapper cannot see expando properties on unprivileged DOM objects.
+   *    That is to say, the wrapper uses Xray delegation.
+   *
+   *  - The wrapper sometimes guesses certain ES5 attributes for returned
+   *    properties. This is explained in a comment in the wrapper code above,
+   *    and shouldn't be a problem.
+   */
+  wrap: wrapIfUnwrapped,
+  unwrap: unwrapIfWrapped,
+  isWrapper: isWrapper,
+
+  /*
+   * When content needs to pass a callback or a callback object to an API
+   * accessed over SpecialPowers, that API may sometimes receive arguments for
+   * whom it is forbidden to create a wrapper in content scopes. As such, we
+   * need a layer to wrap the values in SpecialPowers wrappers before they ever
+   * reach content.
+   */
+  wrapCallback: wrapCallback,
+  wrapCallbackObject: wrapCallbackObject,
+
+  /*
+   * Create blank privileged objects to use as out-params for privileged functions.
+   */
+  createBlankObject: function () {
+    var obj = new Object;
+    obj.__exposedProps__ = ExposedPropsWaiver;
+    return obj;
+  },
+
+  /*
+   * Because SpecialPowers wrappers don't preserve identity, comparing with ==
+   * can be hazardous. Sometimes we can just unwrap to compare, but sometimes
+   * wrapping the underlying object into a content scope is forbidden. This
+   * function strips any wrappers if they exist and compare the underlying
+   * values.
+   */
+  compare: function(a, b) {
+    return unwrapIfWrapped(a) === unwrapIfWrapped(b);
+  },
+
+  get MockFilePicker() {
+    return MockFilePicker
+  },
+
+  get MockColorPicker() {
+    return MockColorPicker
+  },
+
+  get MockPermissionPrompt() {
+    return MockPermissionPrompt
+  },
+
+  loadChromeScript: function (url) {
+    // Create a unique id for this chrome script
+    let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
+                          .getService(Ci.nsIUUIDGenerator);
+    let id = uuidGenerator.generateUUID().toString();
+
+    // Tells chrome code to evaluate this chrome script
+    this._sendSyncMessage("SPLoadChromeScript",
+                          { url: url, id: id });
+
+    // Returns a MessageManager like API in order to be
+    // able to communicate with this chrome script
+    let listeners = [];
+    let chromeScript = {
+      addMessageListener: (name, listener) => {
+        listeners.push({ name: name, listener: listener });
+      },
+
+      removeMessageListener: (name, listener) => {
+        listeners = listeners.filter(
+          o => (o.name != name || o.listener != listener)
+        );
+      },
+
+      sendAsyncMessage: (name, message) => {
+        this._sendSyncMessage("SPChromeScriptMessage",
+                              { id: id, name: name, message: message });
+      },
+
+      destroy: () => {
+        listeners = [];
+        this._removeMessageListener("SPChromeScriptMessage", chromeScript);
+        this._removeMessageListener("SPChromeScriptAssert", chromeScript);
+      },
+
+      receiveMessage: (aMessage) => {
+        let messageId = aMessage.json.id;
+        let name = aMessage.json.name;
+        let message = aMessage.json.message;
+        // Ignore message from other chrome script
+        if (messageId != id)
+          return;
+
+        if (aMessage.name == "SPChromeScriptMessage") {
+          listeners.filter(o => (o.name == name))
+                   .forEach(o => o.listener(this.wrap(message)));
+        } else if (aMessage.name == "SPChromeScriptAssert") {
+          assert(aMessage.json);
+        }
+      }
+    };
+    this._addMessageListener("SPChromeScriptMessage", chromeScript);
+    this._addMessageListener("SPChromeScriptAssert", chromeScript);
+
+    let assert = json => {
+      // An assertion has been done in a mochitest chrome script
+      let {url, err, message, stack} = json;
+
+      // Try to fetch a test runner from the mochitest
+      // in order to properly log these assertions and notify
+      // all usefull log observers
+      let window = this.window.get();
+      let parentRunner, repr = function (o) o;
+      if (window) {
+        window = window.wrappedJSObject;
+        parentRunner = window.TestRunner;
+        if (window.repr) {
+          repr = window.repr;
+        }
+      }
+
+      // Craft a mochitest-like report string
+      var resultString = err ? "TEST-UNEXPECTED-FAIL" : "TEST-PASS";
+      var diagnostic =
+        message ? message :
+                  ("assertion @ " + stack.filename + ":" + stack.lineNumber);
+      if (err) {
+        diagnostic +=
+          " - got " + repr(err.actual) +
+          ", expected " + repr(err.expected) +
+          " (operator " + err.operator + ")";
+      }
+      var msg = [resultString, url, diagnostic].join(" | ");
+      if (parentRunner) {
+        if (err) {
+          parentRunner.addFailedTest(url);
+          parentRunner.error(msg);
+        } else {
+          parentRunner.log(msg);
+        }
+      } else {
+        // When we are running only a single mochitest, there is no test runner
+        dump(msg + "\n");
+      }
+    };
+
+    return this.wrap(chromeScript);
+  },
+
+  get Services() {
+    return wrapPrivileged(Services);
+  },
+
+  /*
+   * In general, any Components object created for unprivileged scopes is
+   * neutered (it implements nsIXPCComponentsBase, but not nsIXPCComponents).
+   * We override this in certain legacy automation configurations (see the
+   * implementation of getRawComponents() above), but don't want to support
+   * it in cases where it isn't already required.
+   *
+   * In scopes with neutered Components, we don't have a natural referent for
+   * things like SpecialPowers.Cc. So in those cases, we fall back to the
+   * Components object from the SpecialPowers scope. This doesn't quite behave
+   * the same way (in particular, SpecialPowers.Cc[foo].createInstance() will
+   * create an instance in the SpecialPowers scope), but SpecialPowers wrapping
+   * is already a YMMV / Whatever-It-Takes-To-Get-TBPL-Green sort of thing.
+   *
+   * It probably wouldn't be too much work to just make SpecialPowers.Components
+   * unconditionally point to the Components object in the SpecialPowers scope.
+   * Try will tell what needs to be fixed up.
+   */
+  getFullComponents: function() {
+    return typeof this.Components.classes == 'object' ? this.Components
+                                                      : Components;
+  },
+
+  /*
+   * Convenient shortcuts to the standard Components abbreviations. Note that
+   * we don't SpecialPowers-wrap Components.interfaces, because it's available
+   * to untrusted content, and wrapping it confuses QI and identity checks.
+   */
+  get Cc() { return wrapPrivileged(this.getFullComponents()).classes; },
+  get Ci() { return this.Components.interfaces; },
+  get Cu() { return wrapPrivileged(this.getFullComponents()).utils; },
+  get Cr() { return wrapPrivileged(this.Components).results; },
+
+  /*
+   * SpecialPowers.getRawComponents() allows content to get a reference to a
+   * naked (and, in certain automation configurations, privileged) Components
+   * object for its scope.
+   *
+   * SpecialPowers.getRawComponents(window) is defined as the global property
+   * window.SpecialPowers.Components for convenience.
+   */
+  getRawComponents: getRawComponents,
+
+  getDOMWindowUtils: function(aWindow) {
+    if (aWindow == this.window.get() && this.DOMWindowUtils != null)
+      return this.DOMWindowUtils;
+
+    return bindDOMWindowUtils(aWindow);
+  },
+
+  removeExpectedCrashDumpFiles: function(aExpectingProcessCrash) {
+    var success = true;
+    if (aExpectingProcessCrash) {
+      var message = {
+        op: "delete-crash-dump-files",
+        filenames: this._encounteredCrashDumpFiles
+      };
+      if (!this._sendSyncMessage("SPProcessCrashService", message)[0]) {
+        success = false;
+      }
+    }
+    this._encounteredCrashDumpFiles.length = 0;
+    return success;
+  },
+
+  findUnexpectedCrashDumpFiles: function() {
+    var self = this;
+    var message = {
+      op: "find-crash-dump-files",
+      crashDumpFilesToIgnore: this._unexpectedCrashDumpFiles
+    };
+    var crashDumpFiles = this._sendSyncMessage("SPProcessCrashService", message)[0];
+    crashDumpFiles.forEach(function(aFilename) {
+      self._unexpectedCrashDumpFiles[aFilename] = true;
+    });
+    return crashDumpFiles;
+  },
+
+  _delayCallbackTwice: function(callback) {
+    function delayedCallback() {
+      function delayAgain() {
+	content.window.setTimeout(callback, 0);
+      }
+      content.window.setTimeout(delayAgain, 0);
+    }
+    return delayedCallback;
+  },
+
+  /* apply permissions to the system and when the test case is finished (SimpleTest.finish())
+     we will revert the permission back to the original.
+
+     inPermissions is an array of objects where each object has a type, action, context, ex:
+     [{'type': 'SystemXHR', 'allow': 1, 'context': document}, 
+      {'type': 'SystemXHR', 'allow': Ci.nsIPermissionManager.PROMPT_ACTION, 'context': document}]
+
+     Allow can be a boolean value of true/false or ALLOW_ACTION/DENY_ACTION/PROMPT_ACTION/UNKNOWN_ACTION
+  */
+  pushPermissions: function(inPermissions, callback) {
+    var pendingPermissions = [];
+    var cleanupPermissions = [];
+
+    for (var p in inPermissions) {
+        var permission = inPermissions[p];
+        var originalValue = Ci.nsIPermissionManager.UNKNOWN_ACTION;
+        if (this.testPermission(permission.type, Ci.nsIPermissionManager.ALLOW_ACTION, permission.context)) {
+          originalValue = Ci.nsIPermissionManager.ALLOW_ACTION;
+        } else if (this.testPermission(permission.type, Ci.nsIPermissionManager.DENY_ACTION, permission.context)) {
+          originalValue = Ci.nsIPermissionManager.DENY_ACTION;
+        } else if (this.testPermission(permission.type, Ci.nsIPermissionManager.PROMPT_ACTION, permission.context)) {
+          originalValue = Ci.nsIPermissionManager.PROMPT_ACTION;
+        } else if (this.testPermission(permission.type, Ci.nsICookiePermission.ACCESS_SESSION, permission.context)) {
+          originalValue = Ci.nsICookiePermission.ACCESS_SESSION;
+        } else if (this.testPermission(permission.type, Ci.nsICookiePermission.ACCESS_ALLOW_FIRST_PARTY_ONLY, permission.context)) {
+          originalValue = Ci.nsICookiePermission.ACCESS_ALLOW_FIRST_PARTY_ONLY;
+        } else if (this.testPermission(permission.type, Ci.nsICookiePermission.ACCESS_LIMIT_THIRD_PARTY, permission.context)) {
+          originalValue = Ci.nsICookiePermission.ACCESS_LIMIT_THIRD_PARTY;
+        }
+
+        let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(permission.context);
+
+        let perm;
+        if (typeof permission.allow !== 'boolean') {
+          perm = permission.allow;
+        } else {
+          perm = permission.allow ? Ci.nsIPermissionManager.ALLOW_ACTION
+                             : Ci.nsIPermissionManager.DENY_ACTION;
+        }
+
+        if (permission.remove == true)
+          perm = Ci.nsIPermissionManager.UNKNOWN_ACTION;
+
+        if (originalValue == perm) {
+          continue;
+        }
+
+        var todo = {'op': 'add', 'type': permission.type, 'permission': perm, 'value': perm, 'url': url, 'appId': appId, 'isInBrowserElement': isInBrowserElement};
+        if (permission.remove == true)
+          todo.op = 'remove';
+
+        pendingPermissions.push(todo);
+
+        /* Push original permissions value or clear into cleanup array */
+        var cleanupTodo = {'op': 'add', 'type': permission.type, 'permission': perm, 'value': perm, 'url': url, 'appId': appId, 'isInBrowserElement': isInBrowserElement};
+        if (originalValue == Ci.nsIPermissionManager.UNKNOWN_ACTION) {
+          cleanupTodo.op = 'remove';
+        } else {
+          cleanupTodo.value = originalValue;
+          cleanupTodo.permission = originalValue;
+        }
+        cleanupPermissions.push(cleanupTodo);
+    }
+
+    if (pendingPermissions.length > 0) {
+      // The callback needs to be delayed twice. One delay is because the pref
+      // service doesn't guarantee the order it calls its observers in, so it
+      // may notify the observer holding the callback before the other
+      // observers have been notified and given a chance to make the changes
+      // that the callback checks for. The second delay is because pref
+      // observers often defer making their changes by posting an event to the
+      // event loop.
+      this._permissionsUndoStack.push(cleanupPermissions);
+      this._pendingPermissions.push([pendingPermissions,
+				     this._delayCallbackTwice(callback)]);
+      this._applyPermissions();
+    } else {
+      content.window.setTimeout(callback, 0);
+    }
+  },
+
+  popPermissions: function(callback) {
+    if (this._permissionsUndoStack.length > 0) {
+      // See pushPermissions comment regarding delay.
+      let cb = callback ? this._delayCallbackTwice(callback) : null;
+      /* Each pop from the stack will yield an object {op/type/permission/value/url/appid/isInBrowserElement} or null */
+      this._pendingPermissions.push([this._permissionsUndoStack.pop(), cb]);
+      this._applyPermissions();
+    } else {
+      content.window.setTimeout(callback, 0);
+    }
+  },
+
+  flushPermissions: function(callback) {
+    while (this._permissionsUndoStack.length > 1)
+      this.popPermissions(null);
+
+    this.popPermissions(callback);
+  },
+
+
+  _permissionObserver: {
+    _lastPermission: {},
+    _callBack: null,
+    _nextCallback: null,
+
+    observe: function (aSubject, aTopic, aData)
+    {
+      if (aTopic == "perm-changed") {
+        var permission = aSubject.QueryInterface(Ci.nsIPermission);
+        if (permission.type == this._lastPermission.type) {
+          var os = Components.classes["@mozilla.org/observer-service;1"]
+                             .getService(Components.interfaces.nsIObserverService);
+          os.removeObserver(this, "perm-changed");
+          content.window.setTimeout(this._callback, 0);
+          content.window.setTimeout(this._nextCallback, 0);
+        }
+      }
+    }
+  },
+
+  /*
+    Iterate through one atomic set of permissions actions and perform allow/deny as appropriate.
+    All actions performed must modify the relevant permission.
+  */
+  _applyPermissions: function() {
+    if (this._applyingPermissions || this._pendingPermissions.length <= 0) {
+      return;
+    }
+
+    /* Set lock and get prefs from the _pendingPrefs queue */
+    this._applyingPermissions = true;
+    var transaction = this._pendingPermissions.shift();
+    var pendingActions = transaction[0];
+    var callback = transaction[1];
+    var lastPermission = pendingActions[pendingActions.length-1];
+
+    var self = this;
+    var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+    this._permissionObserver._lastPermission = lastPermission;
+    this._permissionObserver._callback = callback;
+    this._permissionObserver._nextCallback = function () {
+        self._applyingPermissions = false;
+        // Now apply any permissions that may have been queued while we were applying
+        self._applyPermissions();
+    }
+
+    os.addObserver(this._permissionObserver, "perm-changed", false);
+
+    for (var idx in pendingActions) {
+      var perm = pendingActions[idx];
+      this._sendSyncMessage('SPPermissionManager', perm)[0];
+    }
+  },
+
+  /*
+   * Take in a list of pref changes to make, and invoke |callback| once those
+   * changes have taken effect.  When the test finishes, these changes are
+   * reverted.
+   *
+   * |inPrefs| must be an object with up to two properties: "set" and "clear".
+   * pushPrefEnv will set prefs as indicated in |inPrefs.set| and will unset
+   * the prefs indicated in |inPrefs.clear|.
+   *
+   * For example, you might pass |inPrefs| as:
+   *
+   *  inPrefs = {'set': [['foo.bar', 2], ['magic.pref', 'baz']],
+   *             'clear': [['clear.this'], ['also.this']] };
+   *
+   * Notice that |set| and |clear| are both an array of arrays.  In |set|, each
+   * of the inner arrays must have the form [pref_name, value] or [pref_name,
+   * value, iid].  (The latter form is used for prefs with "complex" values.)
+   *
+   * In |clear|, each inner array should have the form [pref_name].
+   *
+   * If you set the same pref more than once (or both set and clear a pref),
+   * the behavior of this method is undefined.
+   *
+   * (Implementation note: _prefEnvUndoStack is a stack of values to revert to,
+   * not values which have been set!)
+   *
+   * TODO: complex values for original cleanup?
+   *
+   */
+  pushPrefEnv: function(inPrefs, callback) {
+    var prefs = Components.classes["@mozilla.org/preferences-service;1"].
+                           getService(Components.interfaces.nsIPrefBranch);
+
+    var pref_string = [];
+    pref_string[prefs.PREF_INT] = "INT";
+    pref_string[prefs.PREF_BOOL] = "BOOL";
+    pref_string[prefs.PREF_STRING] = "CHAR";
+
+    var pendingActions = [];
+    var cleanupActions = [];
+
+    for (var action in inPrefs) { /* set|clear */
+      for (var idx in inPrefs[action]) {
+        var aPref = inPrefs[action][idx];
+        var prefName = aPref[0];
+        var prefValue = null;
+        var prefIid = null;
+        var prefType = prefs.PREF_INVALID;
+        var originalValue = null;
+
+        if (aPref.length == 3) {
+          prefValue = aPref[1];
+          prefIid = aPref[2];
+        } else if (aPref.length == 2) {
+          prefValue = aPref[1];
+        }
+
+        /* If pref is not found or invalid it doesn't exist. */
+        if (prefs.getPrefType(prefName) != prefs.PREF_INVALID) {
+          prefType = pref_string[prefs.getPrefType(prefName)];
+          if ((prefs.prefHasUserValue(prefName) && action == 'clear') ||
+              (action == 'set'))
+            originalValue = this._getPref(prefName, prefType);
+        } else if (action == 'set') {
+          /* prefName doesn't exist, so 'clear' is pointless */
+          if (aPref.length == 3) {
+            prefType = "COMPLEX";
+          } else if (aPref.length == 2) {
+            if (typeof(prefValue) == "boolean")
+              prefType = "BOOL";
+            else if (typeof(prefValue) == "number")
+              prefType = "INT";
+            else if (typeof(prefValue) == "string")
+              prefType = "CHAR";
+          }
+        }
+
+        /* PREF_INVALID: A non existing pref which we are clearing or invalid values for a set */
+        if (prefType == prefs.PREF_INVALID)
+          continue;
+
+        /* We are not going to set a pref if the value is the same */
+        if (originalValue == prefValue)
+          continue;
+
+        pendingActions.push({'action': action, 'type': prefType, 'name': prefName, 'value': prefValue, 'Iid': prefIid});
+
+        /* Push original preference value or clear into cleanup array */
+        var cleanupTodo = {'action': action, 'type': prefType, 'name': prefName, 'value': originalValue, 'Iid': prefIid};
+        if (originalValue == null) {
+          cleanupTodo.action = 'clear';
+        } else {
+          cleanupTodo.action = 'set';
+        }
+        cleanupActions.push(cleanupTodo);
+      }
+    }
+
+    if (pendingActions.length > 0) {
+      // The callback needs to be delayed twice. One delay is because the pref
+      // service doesn't guarantee the order it calls its observers in, so it
+      // may notify the observer holding the callback before the other
+      // observers have been notified and given a chance to make the changes
+      // that the callback checks for. The second delay is because pref
+      // observers often defer making their changes by posting an event to the
+      // event loop.
+      this._prefEnvUndoStack.push(cleanupActions);
+      this._pendingPrefs.push([pendingActions,
+			       this._delayCallbackTwice(callback)]);
+      this._applyPrefs();
+    } else {
+      content.window.setTimeout(callback, 0);
+    }
+  },
+
+  popPrefEnv: function(callback) {
+    if (this._prefEnvUndoStack.length > 0) {
+      // See pushPrefEnv comment regarding delay.
+      let cb = callback ? this._delayCallbackTwice(callback) : null;
+      /* Each pop will have a valid block of preferences */
+      this._pendingPrefs.push([this._prefEnvUndoStack.pop(), cb]);
+      this._applyPrefs();
+    } else {
+      content.window.setTimeout(callback, 0);
+    }
+  },
+
+  flushPrefEnv: function(callback) {
+    while (this._prefEnvUndoStack.length > 1)
+      this.popPrefEnv(null);
+
+    this.popPrefEnv(callback);
+  },
+
+  /*
+    Iterate through one atomic set of pref actions and perform sets/clears as appropriate.
+    All actions performed must modify the relevant pref.
+  */
+  _applyPrefs: function() {
+    if (this._applyingPrefs || this._pendingPrefs.length <= 0) {
+      return;
+    }
+
+    /* Set lock and get prefs from the _pendingPrefs queue */
+    this._applyingPrefs = true;
+    var transaction = this._pendingPrefs.shift();
+    var pendingActions = transaction[0];
+    var callback = transaction[1];
+
+    var lastPref = pendingActions[pendingActions.length-1];
+
+    var pb = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+    var self = this;
+    pb.addObserver(lastPref.name, function prefObs(subject, topic, data) {
+      pb.removeObserver(lastPref.name, prefObs);
+
+      content.window.setTimeout(callback, 0);
+      content.window.setTimeout(function () {
+        self._applyingPrefs = false;
+        // Now apply any prefs that may have been queued while we were applying
+        self._applyPrefs();
+      }, 0);
+    }, false);
+
+    for (var idx in pendingActions) {
+      var pref = pendingActions[idx];
+      if (pref.action == 'set') {
+        this._setPref(pref.name, pref.type, pref.value, pref.Iid);
+      } else if (pref.action == 'clear') {
+        this.clearUserPref(pref.name);
+      }
+    }
+  },
+
+  // Disables the app install prompt for the duration of this test. There is
+  // no need to re-enable the prompt at the end of the test.
+  //
+  // The provided callback is invoked once the prompt is disabled.
+  autoConfirmAppInstall: function(cb) {
+    this.pushPrefEnv({set: [['dom.mozApps.auto_confirm_install', true]]}, cb);
+  },
+
+  // Allow tests to disable the per platform app validity checks so we can
+  // test higher level WebApp functionality without full platform support.
+  setAllAppsLaunchable: function(launchable) {
+    var message = {
+      op: "set-launchable",
+      launchable: launchable
+    };
+    return this._sendSyncMessage("SPWebAppService", message);
+  },
+
+  _proxiedObservers: {
+    "specialpowers-http-notify-request": function(aMessage) {
+      let uri = aMessage.json.uri;
+      Services.obs.notifyObservers(null, "specialpowers-http-notify-request", uri);
+    },
+  },
+
+  _addObserverProxy: function(notification) {
+    if (notification in this._proxiedObservers) {
+      this._addMessageListener(notification, this._proxiedObservers[notification]);
+    }
+  },
+
+  _removeObserverProxy: function(notification) {
+    if (notification in this._proxiedObservers) {
+      this._removeMessageListener(notification, this._proxiedObservers[notification]);
+    }
+  },
+
+  addObserver: function(obs, notification, weak) {
+    this._addObserverProxy(notification);
+    if (typeof obs == 'object' && obs.observe.name != 'SpecialPowersCallbackWrapper')
+      obs.observe = wrapCallback(obs.observe);
+    var obsvc = Cc['@mozilla.org/observer-service;1']
+                   .getService(Ci.nsIObserverService);
+    obsvc.addObserver(obs, notification, weak);
+  },
+  removeObserver: function(obs, notification) {
+    this._removeObserverProxy(notification);
+    var obsvc = Cc['@mozilla.org/observer-service;1']
+                   .getService(Ci.nsIObserverService);
+    obsvc.removeObserver(obs, notification);
+  },
+  notifyObservers: function(subject, topic, data) {
+    var obsvc = Cc['@mozilla.org/observer-service;1']
+                   .getService(Ci.nsIObserverService);
+    obsvc.notifyObservers(subject, topic, data);
+  },
+
+  can_QI: function(obj) {
+    return obj.QueryInterface !== undefined;
+  },
+  do_QueryInterface: function(obj, iface) {
+    return obj.QueryInterface(Ci[iface]);
+  },
+
+  call_Instanceof: function (obj1, obj2) {
+     obj1=unwrapIfWrapped(obj1);
+     obj2=unwrapIfWrapped(obj2);
+     return obj1 instanceof obj2;
+  },
+
+  // Returns a privileged getter from an object. GetOwnPropertyDescriptor does
+  // not work here because xray wrappers don't properly implement it.
+  //
+  // This terribleness is used by content/base/test/test_object.html because
+  // <object> and <embed> tags will spawn plugins if their prototype is touched,
+  // so we need to get and cache the getter of |hasRunningPlugin| if we want to
+  // call it without paradoxically spawning the plugin.
+  do_lookupGetter: function(obj, name) {
+    return Object.prototype.__lookupGetter__.call(obj, name);
+  },
+
+  // Mimic the get*Pref API
+  getBoolPref: function(aPrefName) {
+    return (this._getPref(aPrefName, 'BOOL'));
+  },
+  getIntPref: function(aPrefName) {
+    return (this._getPref(aPrefName, 'INT'));
+  },
+  getCharPref: function(aPrefName) {
+    return (this._getPref(aPrefName, 'CHAR'));
+  },
+  getComplexValue: function(aPrefName, aIid) {
+    return (this._getPref(aPrefName, 'COMPLEX', aIid));
+  },
+
+  // Mimic the set*Pref API
+  setBoolPref: function(aPrefName, aValue) {
+    return (this._setPref(aPrefName, 'BOOL', aValue));
+  },
+  setIntPref: function(aPrefName, aValue) {
+    return (this._setPref(aPrefName, 'INT', aValue));
+  },
+  setCharPref: function(aPrefName, aValue) {
+    return (this._setPref(aPrefName, 'CHAR', aValue));
+  },
+  setComplexValue: function(aPrefName, aIid, aValue) {
+    return (this._setPref(aPrefName, 'COMPLEX', aValue, aIid));
+  },
+
+  // Mimic the clearUserPref API
+  clearUserPref: function(aPrefName) {
+    var msg = {'op':'clear', 'prefName': aPrefName, 'prefType': ""};
+    this._sendSyncMessage('SPPrefService', msg);
+  },
+
+  // Private pref functions to communicate to chrome
+  _getPref: function(aPrefName, aPrefType, aIid) {
+    var msg = {};
+    if (aIid) {
+      // Overloading prefValue to handle complex prefs
+      msg = {'op':'get', 'prefName': aPrefName, 'prefType':aPrefType, 'prefValue':[aIid]};
+    } else {
+      msg = {'op':'get', 'prefName': aPrefName,'prefType': aPrefType};
+    }
+    var val = this._sendSyncMessage('SPPrefService', msg);
+
+    if (val == null || val[0] == null)
+      throw "Error getting pref";
+    return val[0];
+  },
+  _setPref: function(aPrefName, aPrefType, aValue, aIid) {
+    var msg = {};
+    if (aIid) {
+      msg = {'op':'set','prefName':aPrefName, 'prefType': aPrefType, 'prefValue': [aIid,aValue]};
+    } else {
+      msg = {'op':'set', 'prefName': aPrefName, 'prefType': aPrefType, 'prefValue': aValue};
+    }
+    return(this._sendSyncMessage('SPPrefService', msg)[0]);
+  },
+
+  _getDocShell: function(window) {
+    return window.QueryInterface(Ci.nsIInterfaceRequestor)
+                 .getInterface(Ci.nsIWebNavigation)
+                 .QueryInterface(Ci.nsIDocShell);
+  },
+  _getMUDV: function(window) {
+    return this._getDocShell(window).contentViewer
+               .QueryInterface(Ci.nsIMarkupDocumentViewer);
+  },
+  //XXX: these APIs really ought to be removed, they're not e10s-safe.
+  // (also they're pretty Firefox-specific)
+  _getTopChromeWindow: function(window) {
+    return window.QueryInterface(Ci.nsIInterfaceRequestor)
+                 .getInterface(Ci.nsIWebNavigation)
+                 .QueryInterface(Ci.nsIDocShellTreeItem)
+                 .rootTreeItem
+                 .QueryInterface(Ci.nsIInterfaceRequestor)
+                 .getInterface(Ci.nsIDOMWindow)
+                 .QueryInterface(Ci.nsIDOMChromeWindow);
+  },
+  _getAutoCompletePopup: function(window) {
+    return this._getTopChromeWindow(window).document
+                                           .getElementById("PopupAutoComplete");
+  },
+  addAutoCompletePopupEventListener: function(window, eventname, listener) {
+    this._getAutoCompletePopup(window).addEventListener(eventname,
+                                                        listener,
+                                                        false);
+  },
+  removeAutoCompletePopupEventListener: function(window, eventname, listener) {
+    this._getAutoCompletePopup(window).removeEventListener(eventname,
+                                                           listener,
+                                                           false);
+  },
+  get formHistory() {
+    let tmp = {};
+    Cu.import("resource://gre/modules/FormHistory.jsm", tmp);
+    return wrapPrivileged(tmp.FormHistory);
+  },
+  getFormFillController: function(window) {
+    return Components.classes["@mozilla.org/satchel/form-fill-controller;1"]
+                     .getService(Components.interfaces.nsIFormFillController);
+  },
+  attachFormFillControllerTo: function(window) {
+    this.getFormFillController()
+        .attachToBrowser(this._getDocShell(window),
+                         this._getAutoCompletePopup(window));
+  },
+  detachFormFillControllerFrom: function(window) {
+    this.getFormFillController().detachFromBrowser(this._getDocShell(window));
+  },
+  isBackButtonEnabled: function(window) {
+    return !this._getTopChromeWindow(window).document
+                                      .getElementById("Browser:Back")
+                                      .hasAttribute("disabled");
+  },
+  //XXX end of problematic APIs
+
+  addChromeEventListener: function(type, listener, capture, allowUntrusted) {
+    addEventListener(type, listener, capture, allowUntrusted);
+  },
+  removeChromeEventListener: function(type, listener, capture) {
+    removeEventListener(type, listener, capture);
+  },
+
+  // Note: each call to registerConsoleListener MUST be paired with a
+  // call to postConsoleSentinel; when the callback receives the
+  // sentinel it will unregister itself (_after_ calling the
+  // callback).  SimpleTest.expectConsoleMessages does this for you.
+  // If you register more than one console listener, a call to
+  // postConsoleSentinel will zap all of them.
+  registerConsoleListener: function(callback) {
+    let listener = new SPConsoleListener(callback);
+    Services.console.registerListener(listener);
+  },
+  postConsoleSentinel: function() {
+    Services.console.logStringMessage("SENTINEL");
+  },
+  resetConsole: function() {
+    Services.console.reset();
+  },
+
+  getMaxLineBoxWidth: function(window) {
+    return this._getMUDV(window).maxLineBoxWidth;
+  },
+
+  setMaxLineBoxWidth: function(window, width) {
+    this._getMUDV(window).changeMaxLineBoxWidth(width);
+  },
+
+  getFullZoom: function(window) {
+    return this._getMUDV(window).fullZoom;
+  },
+  setFullZoom: function(window, zoom) {
+    this._getMUDV(window).fullZoom = zoom;
+  },
+  getTextZoom: function(window) {
+    return this._getMUDV(window).textZoom;
+  },
+  setTextZoom: function(window, zoom) {
+    this._getMUDV(window).textZoom = zoom;
+  },
+
+  emulateMedium: function(window, mediaType) {
+    this._getMUDV(window).emulateMedium(mediaType);
+  },
+  stopEmulatingMedium: function(window) {
+    this._getMUDV(window).stopEmulatingMedium();
+  },
+
+  snapshotWindowWithOptions: function (win, rect, bgcolor, options) {
+    var el = this.window.get().document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+    if (rect === undefined) {
+      rect = { top: win.scrollY, left: win.scrollX,
+               width: win.innerWidth, height: win.innerHeight };
+    }
+    if (bgcolor === undefined) {
+      bgcolor = "rgb(255,255,255)";
+    }
+    if (options === undefined) {
+      options = { };
+    }
+
+    el.width = rect.width;
+    el.height = rect.height;
+    var ctx = el.getContext("2d");
+    var flags = 0;
+
+    for (var option in options) {
+      flags |= options[option] && ctx[option];
+    }
+
+    ctx.drawWindow(win,
+                   rect.left, rect.top, rect.width, rect.height,
+                   bgcolor,
+                   flags);
+    return el;
+  },
+
+  snapshotWindow: function (win, withCaret, rect, bgcolor) {
+    return this.snapshotWindowWithOptions(win, rect, bgcolor,
+                                          { DRAWWINDOW_DRAW_CARET: withCaret });
+  },
+
+  snapshotRect: function (win, rect, bgcolor) {
+    return this.snapshotWindowWithOptions(win, rect, bgcolor);
+  },
+
+  gc: function() {
+    this.DOMWindowUtils.garbageCollect();
+  },
+
+  forceGC: function() {
+    Cu.forceGC();
+  },
+
+  forceCC: function() {
+    Cu.forceCC();
+  },
+
+  // Due to various dependencies between JS objects and C++ objects, an ordinary
+  // forceGC doesn't necessarily clear all unused objects, thus the GC and CC
+  // needs to run several times and when no other JS is running.
+  // The current number of iterations has been determined according to massive
+  // cross platform testing.
+  exactGC: function(win, callback) {
+    var self = this;
+    let count = 0;
+
+    function doPreciseGCandCC() {
+      function scheduledGCCallback() {
+        self.getDOMWindowUtils(win).cycleCollect();
+
+        if (++count < 2) {
+          doPreciseGCandCC();
+        } else {
+          callback();
+        }
+      }
+
+      Cu.schedulePreciseGC(scheduledGCCallback);
+    }
+
+    doPreciseGCandCC();
+  },
+
+  setGCZeal: function(zeal) {
+    Cu.setGCZeal(zeal);
+  },
+
+  isMainProcess: function() {
+    try {
+      return Cc["@mozilla.org/xre/app-info;1"].
+               getService(Ci.nsIXULRuntime).
+               processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+    } catch (e) { }
+    return true;
+  },
+
+  _xpcomabi: null,
+
+  get XPCOMABI() {
+    if (this._xpcomabi != null)
+      return this._xpcomabi;
+
+    var xulRuntime = Cc["@mozilla.org/xre/app-info;1"]
+                        .getService(Components.interfaces.nsIXULAppInfo)
+                        .QueryInterface(Components.interfaces.nsIXULRuntime);
+
+    this._xpcomabi = xulRuntime.XPCOMABI;
+    return this._xpcomabi;
+  },
+
+  // The optional aWin parameter allows the caller to specify a given window in
+  // whose scope the runnable should be dispatched. If aFun throws, the
+  // exception will be reported to aWin.
+  executeSoon: function(aFun, aWin) {
+    // Create the runnable in the scope of aWin to avoid running into COWs.
+    var runnable = {};
+    if (aWin)
+        runnable = Cu.createObjectIn(aWin);
+    runnable.run = aFun;
+    Cu.dispatch(runnable, aWin);
+  },
+
+  _os: null,
+
+  get OS() {
+    if (this._os != null)
+      return this._os;
+
+    var xulRuntime = Cc["@mozilla.org/xre/app-info;1"]
+                        .getService(Components.interfaces.nsIXULAppInfo)
+                        .QueryInterface(Components.interfaces.nsIXULRuntime);
+
+    this._os = xulRuntime.OS;
+    return this._os;
+  },
+
+  addSystemEventListener: function(target, type, listener, useCapture) {
+    Cc["@mozilla.org/eventlistenerservice;1"].
+      getService(Ci.nsIEventListenerService).
+      addSystemEventListener(target, type, listener, useCapture);
+  },
+  removeSystemEventListener: function(target, type, listener, useCapture) {
+    Cc["@mozilla.org/eventlistenerservice;1"].
+      getService(Ci.nsIEventListenerService).
+      removeSystemEventListener(target, type, listener, useCapture);
+  },
+
+  getDOMRequestService: function() {
+    var serv = Cc["@mozilla.org/dom/dom-request-service;1"].
+      getService(Ci.nsIDOMRequestService);
+    var res = { __exposedProps__: {} };
+    var props = ["createRequest", "createCursor", "fireError", "fireSuccess",
+                 "fireDone", "fireDetailedError"];
+    for (i in props) {
+      let prop = props[i];
+      res[prop] = function() { return serv[prop].apply(serv, arguments) };
+      res.__exposedProps__[prop] = "r";
+    }
+    return res;
+  },
+
+  setLogFile: function(path) {
+    this._mfl = new MozillaFileLogger(path);
+  },
+
+  log: function(data) {
+    this._mfl.log(data);
+  },
+
+  closeLogFile: function() {
+    this._mfl.close();
+  },
+
+  addCategoryEntry: function(category, entry, value, persists, replace) {
+    Components.classes["@mozilla.org/categorymanager;1"].
+      getService(Components.interfaces.nsICategoryManager).
+      addCategoryEntry(category, entry, value, persists, replace);
+  },
+
+  deleteCategoryEntry: function(category, entry, persists) {
+    Components.classes["@mozilla.org/categorymanager;1"].
+      getService(Components.interfaces.nsICategoryManager).
+      deleteCategoryEntry(category, entry, persists);
+  },
+
+  copyString: function(str, doc) {
+    Components.classes["@mozilla.org/widget/clipboardhelper;1"].
+      getService(Components.interfaces.nsIClipboardHelper).
+      copyString(str, doc);
+  },
+
+  openDialog: function(win, args) {
+    return win.openDialog.apply(win, args);
+  },
+
+  // :jdm gets credit for this.  ex: getPrivilegedProps(window, 'location.href');
+  getPrivilegedProps: function(obj, props) {
+    var parts = props.split('.');
+
+    for (var i = 0; i < parts.length; i++) {
+      var p = parts[i];
+      if (obj[p]) {
+        obj = obj[p];
+      } else {
+        return null;
+      }
+    }
+    return obj;
+  },
+
+  get focusManager() {
+    if (this._fm != null)
+      return this._fm;
+
+    this._fm = Components.classes["@mozilla.org/focus-manager;1"].
+                        getService(Components.interfaces.nsIFocusManager);
+
+    return this._fm;
+  },
+
+  getFocusedElementForWindow: function(targetWindow, aDeep, childTargetWindow) {
+    return this.focusManager.getFocusedElementForWindow(targetWindow, aDeep, childTargetWindow);
+  },
+
+  activeWindow: function() {
+    return this.focusManager.activeWindow;
+  },
+
+  focusedWindow: function() {
+    return this.focusManager.focusedWindow;
+  },
+
+  focus: function(aWindow) {
+    // This is called inside TestRunner._makeIframe without aWindow, because of assertions in oop mochitests
+    // With aWindow, it is called in SimpleTest.waitForFocus to allow popup window opener focus switching
+    if (aWindow)
+      aWindow.focus();
+    sendAsyncMessage("SpecialPowers.Focus", {});
+  },
+
+  getClipboardData: function(flavor, whichClipboard) {
+    if (this._cb == null)
+      this._cb = Components.classes["@mozilla.org/widget/clipboard;1"].
+                            getService(Components.interfaces.nsIClipboard);
+    if (whichClipboard === undefined)
+      whichClipboard = this._cb.kGlobalClipboard;
+
+    var xferable = Components.classes["@mozilla.org/widget/transferable;1"].
+                   createInstance(Components.interfaces.nsITransferable);
+    xferable.init(this._getDocShell(content.window)
+                      .QueryInterface(Components.interfaces.nsILoadContext));
+    xferable.addDataFlavor(flavor);
+    this._cb.getData(xferable, whichClipboard);
+    var data = {};
+    try {
+      xferable.getTransferData(flavor, data, {});
+    } catch (e) {}
+    data = data.value || null;
+    if (data == null)
+      return "";
+
+    return data.QueryInterface(Components.interfaces.nsISupportsString).data;
+  },
+
+  clipboardCopyString: function(preExpectedVal, doc) {
+    var cbHelperSvc = Components.classes["@mozilla.org/widget/clipboardhelper;1"].
+                      getService(Components.interfaces.nsIClipboardHelper);
+    cbHelperSvc.copyString(preExpectedVal, doc);
+  },
+
+  supportsSelectionClipboard: function() {
+    if (this._cb == null) {
+      this._cb = Components.classes["@mozilla.org/widget/clipboard;1"].
+                            getService(Components.interfaces.nsIClipboard);
+    }
+    return this._cb.supportsSelectionClipboard();
+  },
+
+  swapFactoryRegistration: function(cid, contractID, newFactory, oldFactory) {
+    var componentRegistrar = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+
+    var unregisterFactory = newFactory;
+    var registerFactory = oldFactory;
+
+    if (cid == null) {
+      if (contractID != null) {
+        cid = componentRegistrar.contractIDToCID(contractID);
+        oldFactory = Components.manager.getClassObject(Components.classes[contractID],
+                                                            Components.interfaces.nsIFactory);
+      } else {
+        return {'error': "trying to register a new contract ID: Missing contractID"};
+      }
+
+      unregisterFactory = oldFactory;
+      registerFactory = newFactory;
+    }
+    componentRegistrar.unregisterFactory(cid,
+                                         unregisterFactory);
+
+    // Restore the original factory.
+    componentRegistrar.registerFactory(cid,
+                                       "",
+                                       contractID,
+                                       registerFactory);
+    return {'cid':cid, 'originalFactory':oldFactory};
+  },
+
+  _getElement: function(aWindow, id) {
+    return ((typeof(id) == "string") ?
+        aWindow.document.getElementById(id) : id);
+  },
+
+  dispatchEvent: function(aWindow, target, event) {
+    var el = this._getElement(aWindow, target);
+    return el.dispatchEvent(event);
+  },
+
+  get isDebugBuild() {
+    delete this.isDebugBuild;
+    var debug = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2);
+    return this.isDebugBuild = debug.isDebugBuild;
+  },
+  assertionCount: function() {
+    var debugsvc = Cc['@mozilla.org/xpcom/debug;1'].getService(Ci.nsIDebug2);
+    return debugsvc.assertionCount;
+  },
+
+  /**
+   * Get the message manager associated with an <iframe mozbrowser>.
+   */
+  getBrowserFrameMessageManager: function(aFrameElement) {
+    return this.wrap(aFrameElement.QueryInterface(Ci.nsIFrameLoaderOwner)
+                                  .frameLoader
+                                  .messageManager);
+  },
+
+  setFullscreenAllowed: function(document) {
+    var pm = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager);
+    pm.addFromPrincipal(document.nodePrincipal, "fullscreen", Ci.nsIPermissionManager.ALLOW_ACTION);
+    var obsvc = Cc['@mozilla.org/observer-service;1']
+                   .getService(Ci.nsIObserverService);
+    obsvc.notifyObservers(document, "fullscreen-approved", null);
+  },
+
+  removeFullscreenAllowed: function(document) {
+    var pm = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager);
+    pm.removeFromPrincipal(document.nodePrincipal, "fullscreen");
+  },
+
+  _getInfoFromPermissionArg: function(arg) {
+    let url = "";
+    let appId = Ci.nsIScriptSecurityManager.NO_APP_ID;
+    let isInBrowserElement = false;
+
+    if (typeof(arg) == "string") {
+      // It's an URL.
+      url = Cc["@mozilla.org/network/io-service;1"]
+              .getService(Ci.nsIIOService)
+              .newURI(arg, null, null)
+              .spec;
+    } else if (arg.manifestURL) {
+      // It's a thing representing an app.
+      let appsSvc = Cc["@mozilla.org/AppsService;1"]
+                      .getService(Ci.nsIAppsService)
+      let app = appsSvc.getAppByManifestURL(arg.manifestURL);
+
+      if (!app) {
+        throw "No app for this manifest!";
+      }
+
+      appId = appsSvc.getAppLocalIdByManifestURL(arg.manifestURL);
+      url = app.origin;
+      isInBrowserElement = arg.isInBrowserElement || false;
+    } else if (arg.nodePrincipal) {
+      // It's a document.
+      url = arg.nodePrincipal.URI.spec;
+      appId = arg.nodePrincipal.appId;
+      isInBrowserElement = arg.nodePrincipal.isInBrowserElement;
+    } else {
+      url = arg.url;
+      appId = arg.appId;
+      isInBrowserElement = arg.isInBrowserElement;
+    }
+
+    return [ url, appId, isInBrowserElement ];
+  },
+
+  addPermission: function(type, allow, arg) {
+    let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(arg);
+
+    let permission;
+    if (typeof allow !== 'boolean') {
+      permission = allow;
+    } else {
+      permission = allow ? Ci.nsIPermissionManager.ALLOW_ACTION
+                         : Ci.nsIPermissionManager.DENY_ACTION;
+    }
+
+    var msg = {
+      'op': 'add',
+      'type': type,
+      'permission': permission,
+      'url': url,
+      'appId': appId,
+      'isInBrowserElement': isInBrowserElement
+    };
+
+    this._sendSyncMessage('SPPermissionManager', msg);
+  },
+
+  removePermission: function(type, arg) {
+    let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(arg);
+
+    var msg = {
+      'op': 'remove',
+      'type': type,
+      'url': url,
+      'appId': appId,
+      'isInBrowserElement': isInBrowserElement
+    };
+
+    this._sendSyncMessage('SPPermissionManager', msg);
+  },
+
+  hasPermission: function (type, arg) {
+   let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(arg);
+
+    var msg = {
+      'op': 'has',
+      'type': type,
+      'url': url,
+      'appId': appId,
+      'isInBrowserElement': isInBrowserElement
+    };
+
+    return this._sendSyncMessage('SPPermissionManager', msg)[0];
+  },
+  testPermission: function (type, value, arg) {
+   let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(arg);
+
+    var msg = {
+      'op': 'test',
+      'type': type,
+      'value': value, 
+      'url': url,
+      'appId': appId,
+      'isInBrowserElement': isInBrowserElement
+    };
+    return this._sendSyncMessage('SPPermissionManager', msg)[0];
+  },
+
+  getMozFullPath: function(file) {
+    return file.mozFullPath;
+  },
+
+  isWindowPrivate: function(win) {
+    return PrivateBrowsingUtils.isWindowPrivate(win);
+  },
+
+  notifyObserversInParentProcess: function(subject, topic, data) {
+    if (subject) {
+      throw new Error("Can't send subject to another process!");
+    }
+    if (this.isMainProcess()) {
+      this.notifyObservers(subject, topic, data);
+      return;
+    }
+    var msg = {
+      'op': 'notify',
+      'observerTopic': topic,
+      'observerData': data
+    };
+    this._sendSyncMessage('SPObserverService', msg);
+  },
+};
+
+this.SpecialPowersAPI = SpecialPowersAPI;
+this.bindDOMWindowUtils = bindDOMWindowUtils;
+this.getRawComponents = getRawComponents;
diff --git a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/Assert.jsm b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/Assert.jsm
new file mode 100644
index 0000000..a80c407
--- /dev/null
+++ b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/Assert.jsm
@@ -0,0 +1,442 @@
+/* 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/. */
+
+// http://wiki.commonjs.org/wiki/Unit_Testing/1.0
+// When you see a javadoc comment that contains a number, it's a reference to a
+// specific section of the CommonJS spec.
+//
+// Originally from narwhal.js (http://narwhaljs.org)
+// Copyright (c) 2009 Thomas Robinson <280north.com>
+// MIT license: http://opensource.org/licenses/MIT
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+  "Assert"
+];
+
+/**
+ * 1. The assert module provides functions that throw AssertionError's when
+ * particular conditions are not met.
+ *
+ * To use the module you'll need to instantiate it first, which allows consumers
+ * to override certain behavior on the newly obtained instance. For examples,
+ * see the javadoc comments for the `report` member function.
+ */
+let Assert = this.Assert = function(reporterFunc) {
+  if (reporterFunc)
+    this.setReporter(reporterFunc);
+};
+
+function instanceOf(object, type) {
+  return Object.prototype.toString.call(object) == "[object " + type + "]";
+}
+
+function replacer(key, value) {
+  if (value === undefined) {
+    return "" + value;
+  }
+  if (typeof value === "number" && (isNaN(value) || !isFinite(value))) {
+    return value.toString();
+  }
+  if (typeof value === "function" || instanceOf(value, "RegExp")) {
+    return value.toString();
+  }
+  return value;
+}
+
+const kTruncateLength = 128;
+
+function truncate(text, newLength = kTruncateLength) {
+  if (typeof text == "string") {
+    return text.length < newLength ? text : text.slice(0, newLength);
+  } else {
+    return text;
+  }
+}
+
+function getMessage(error, prefix = "") {
+  let actual, expected;
+  // Wrap calls to JSON.stringify in try...catch blocks, as they may throw. If
+  // so, fall back to toString().
+  try {
+    actual = JSON.stringify(error.actual, replacer);
+  } catch (ex) {
+    actual = Object.prototype.toString.call(error.actual);
+  }
+  try {
+    expected = JSON.stringify(error.expected, replacer);
+  } catch (ex) {
+    expected = Object.prototype.toString.call(error.expected);
+  }
+  let message = prefix;
+  if (error.operator) {
+    message += (prefix ? " - " : "") + truncate(actual) + " " + error.operator +
+               " " + truncate(expected);
+  }
+  return message;
+}
+
+/**
+ * 2. The AssertionError is defined in assert.
+ *
+ * Example:
+ * new assert.AssertionError({
+ *   message: message,
+ *   actual: actual,
+ *   expected: expected,
+ *   operator: operator
+ * });
+ *
+ * At present only the four keys mentioned above are used and
+ * understood by the spec. Implementations or sub modules can pass
+ * other keys to the AssertionError's constructor - they will be
+ * ignored.
+ */
+Assert.AssertionError = function(options) {
+  this.name = "AssertionError";
+  this.actual = options.actual;
+  this.expected = options.expected;
+  this.operator = options.operator;
+  this.message = getMessage(this, options.message);
+  // The part of the stack that comes from this module is not interesting.
+  let stack = Components.stack;
+  do {
+    stack = stack.caller;
+  } while(stack.filename && stack.filename.contains("Assert.jsm"))
+  this.stack = stack;
+};
+
+// assert.AssertionError instanceof Error
+Assert.AssertionError.prototype = Object.create(Error.prototype, {
+  constructor: {
+    value: Assert.AssertionError,
+    enumerable: false,
+    writable: true,
+    configurable: true
+  }
+});
+
+let proto = Assert.prototype;
+
+proto._reporter = null;
+/**
+ * Set a custom assertion report handler function. Arguments passed in to this
+ * function are:
+ * err (AssertionError|null) An error object when the assertion failed or null
+ *                           when it passed
+ * message (string) Message describing the assertion
+ * stack (stack) Stack trace of the assertion function
+ *
+ * Example:
+ * ```js
+ * Assert.setReporter(function customReporter(err, message, stack) {
+ *   if (err) {
+ *     do_report_result(false, err.message, err.stack);
+ *   } else {
+ *     do_report_result(true, message, stack);
+ *   }
+ * });
+ * ```
+ *
+ * @param reporterFunc
+ *        (function) Report handler function
+ */
+proto.setReporter = function(reporterFunc) {
+  this._reporter = reporterFunc;
+};
+
+/**
+ * 3. All of the following functions must throw an AssertionError when a
+ * corresponding condition is not met, with a message that may be undefined if
+ * not provided.  All assertion methods provide both the actual and expected
+ * values to the assertion error for display purposes.
+ *
+ * This report method only throws errors on assertion failures, as per spec,
+ * but consumers of this module (think: xpcshell-test, mochitest) may want to
+ * override this default implementation.
+ *
+ * Example:
+ * ```js
+ * // The following will report an assertion failure.
+ * this.report(1 != 2, 1, 2, "testing JS number math!", "==");
+ * ```
+ *
+ * @param failed
+ *        (boolean) Indicates if the assertion failed or not
+ * @param actual
+ *        (mixed) The result of evaluating the assertion
+ * @param expected (optional)
+ *        (mixed) Expected result from the test author
+ * @param message (optional)
+ *        (string) Short explanation of the expected result
+ * @param operator (optional)
+ *        (string) Operation qualifier used by the assertion method (ex: '==')
+ */
+proto.report = function(failed, actual, expected, message, operator) {
+  let err = new Assert.AssertionError({
+    message: message,
+    actual: actual,
+    expected: expected,
+    operator: operator
+  });
+  if (!this._reporter) {
+    // If no custom reporter is set, throw the error.
+    if (failed) {
+      throw err;
+    }
+  } else {
+    this._reporter(failed ? err : null, message, err.stack);
+  }
+};
+
+/**
+ * 4. Pure assertion tests whether a value is truthy, as determined by !!guard.
+ * assert.ok(guard, message_opt);
+ * This statement is equivalent to assert.equal(true, !!guard, message_opt);.
+ * To test strictly for the value true, use assert.strictEqual(true, guard,
+ * message_opt);.
+ *
+ * @param value
+ *        (mixed) Test subject to be evaluated as truthy
+ * @param message (optional)
+ *        (string) Short explanation of the expected result
+ */
+proto.ok = function(value, message) {
+  this.report(!value, value, true, message, "==");
+};
+
+/**
+ * 5. The equality assertion tests shallow, coercive equality with ==.
+ * assert.equal(actual, expected, message_opt);
+ *
+ * @param actual
+ *        (mixed) Test subject to be evaluated as equivalent to `expected`
+ * @param expected
+ *        (mixed) Test reference to evaluate against `actual`
+ * @param message (optional)
+ *        (string) Short explanation of the expected result
+ */
+proto.equal = function equal(actual, expected, message) {
+  this.report(actual != expected, actual, expected, message, "==");
+};
+
+/**
+ * 6. The non-equality assertion tests for whether two objects are not equal
+ * with != assert.notEqual(actual, expected, message_opt);
+ *
+ * @param actual
+ *        (mixed) Test subject to be evaluated as NOT equivalent to `expected`
+ * @param expected
+ *        (mixed) Test reference to evaluate against `actual`
+ * @param message (optional)
+ *        (string) Short explanation of the expected result
+ */
+proto.notEqual = function notEqual(actual, expected, message) {
+  this.report(actual == expected, actual, expected, message, "!=");
+};
+
+/**
+ * 7. The equivalence assertion tests a deep equality relation.
+ * assert.deepEqual(actual, expected, message_opt);
+ *
+ * We check using the most exact approximation of equality between two objects
+ * to keep the chance of false positives to a minimum.
+ * `JSON.stringify` is not designed to be used for this purpose; objects may
+ * have ambiguous `toJSON()` implementations that would influence the test.
+ *
+ * @param actual
+ *        (mixed) Test subject to be evaluated as equivalent to `expected`, including nested properties
+ * @param expected
+ *        (mixed) Test reference to evaluate against `actual`
+ * @param message (optional)
+ *        (string) Short explanation of the expected result
+ */
+proto.deepEqual = function deepEqual(actual, expected, message) {
+  this.report(!_deepEqual(actual, expected), actual, expected, message, "deepEqual");
+};
+
+function _deepEqual(actual, expected) {
+  // 7.1. All identical values are equivalent, as determined by ===.
+  if (actual === expected) {
+    return true;
+  // 7.2. If the expected value is a Date object, the actual value is
+  // equivalent if it is also a Date object that refers to the same time.
+  } else if (instanceOf(actual, "Date") && instanceOf(expected, "Date")) {
+    return actual.getTime() === expected.getTime();
+  // 7.3 If the expected value is a RegExp object, the actual value is
+  // equivalent if it is also a RegExp object with the same source and
+  // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
+  } else if (instanceOf(actual, "RegExp") && instanceOf(expected, "RegExp")) {
+    return actual.source === expected.source &&
+           actual.global === expected.global &&
+           actual.multiline === expected.multiline &&
+           actual.lastIndex === expected.lastIndex &&
+           actual.ignoreCase === expected.ignoreCase;
+  // 7.4. Other pairs that do not both pass typeof value == "object",
+  // equivalence is determined by ==.
+  } else if (typeof actual != "object" && typeof expected != "object") {
+    return actual == expected;
+  // 7.5 For all other Object pairs, including Array objects, equivalence is
+  // determined by having the same number of owned properties (as verified
+  // with Object.prototype.hasOwnProperty.call), the same set of keys
+  // (although not necessarily the same order), equivalent values for every
+  // corresponding key, and an identical 'prototype' property. Note: this
+  // accounts for both named and indexed properties on Arrays.
+  } else {
+    return objEquiv(actual, expected);
+  }
+}
+
+function isUndefinedOrNull(value) {
+  return value === null || value === undefined;
+}
+
+function isArguments(object) {
+  return instanceOf(object, "Arguments");
+}
+
+function objEquiv(a, b) {
+  if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) {
+    return false;
+  }
+  // An identical 'prototype' property.
+  if (a.prototype !== b.prototype) {
+    return false;
+  }
+  // Object.keys may be broken through screwy arguments passing. Converting to
+  // an array solves the problem.
+  if (isArguments(a)) {
+    if (!isArguments(b)) {
+      return false;
+    }
+    a = pSlice.call(a);
+    b = pSlice.call(b);
+    return _deepEqual(a, b);
+  }
+  let ka, kb, key, i;
+  try {
+    ka = Object.keys(a);
+    kb = Object.keys(b);
+  } catch (e) {
+    // Happens when one is a string literal and the other isn't
+    return false;
+  }
+  // Having the same number of owned properties (keys incorporates
+  // hasOwnProperty)
+  if (ka.length != kb.length)
+    return false;
+  // The same set of keys (although not necessarily the same order),
+  ka.sort();
+  kb.sort();
+  // Equivalent values for every corresponding key, and possibly expensive deep 
+  // test
+  for (i = ka.length - 1; i >= 0; i--) {
+    key = ka[i];
+    if (!_deepEqual(a[key], b[key])) {
+      return false;
+    }
+  }
+  return true;
+}
+
+/**
+ * 8. The non-equivalence assertion tests for any deep inequality.
+ * assert.notDeepEqual(actual, expected, message_opt);
+ *
+ * @param actual
+ *        (mixed) Test subject to be evaluated as NOT equivalent to `expected`, including nested properties
+ * @param expected
+ *        (mixed) Test reference to evaluate against `actual`
+ * @param message (optional)
+ *        (string) Short explanation of the expected result
+ */
+proto.notDeepEqual = function notDeepEqual(actual, expected, message) {
+  this.report(_deepEqual(actual, expected), actual, expected, message, "notDeepEqual");
+};
+
+/**
+ * 9. The strict equality assertion tests strict equality, as determined by ===.
+ * assert.strictEqual(actual, expected, message_opt);
+ *
+ * @param actual
+ *        (mixed) Test subject to be evaluated as strictly equivalent to `expected`
+ * @param expected
+ *        (mixed) Test reference to evaluate against `actual`
+ * @param message (optional)
+ *        (string) Short explanation of the expected result
+ */
+proto.strictEqual = function strictEqual(actual, expected, message) {
+  this.report(actual !== expected, actual, expected, message, "===");
+};
+
+/**
+ * 10. The strict non-equality assertion tests for strict inequality, as
+ * determined by !==.  assert.notStrictEqual(actual, expected, message_opt);
+ *
+ * @param actual
+ *        (mixed) Test subject to be evaluated as NOT strictly equivalent to `expected`
+ * @param expected
+ *        (mixed) Test reference to evaluate against `actual`
+ * @param message (optional)
+ *        (string) Short explanation of the expected result
+ */
+proto.notStrictEqual = function notStrictEqual(actual, expected, message) {
+  this.report(actual === expected, actual, expected, message, "!==");
+};
+
+function expectedException(actual, expected) {
+  if (!actual || !expected) {
+    return false;
+  }
+
+  if (instanceOf(expected, "RegExp")) {
+    return expected.test(actual);
+  } else if (actual instanceof expected) {
+    return true;
+  } else if (expected.call({}, actual) === true) {
+    return true;
+  }
+
+  return false;
+}
+
+/**
+ * 11. Expected to throw an error:
+ * assert.throws(block, Error_opt, message_opt);
+ *
+ * @param block
+ *        (function) Function block to evaluate and catch eventual thrown errors
+ * @param expected (optional)
+ *        (mixed) Test reference to evaluate against the thrown result from `block`
+ * @param message (optional)
+ *        (string) Short explanation of the expected result
+ */
+proto.throws = function(block, expected, message) {
+  let actual;
+
+  if (typeof expected === "string") {
+    message = expected;
+    expected = null;
+  }
+
+  try {
+    block();
+  } catch (e) {
+    actual = e;
+  }
+
+  message = (expected && expected.name ? " (" + expected.name + ")." : ".") +
+            (message ? " " + message : ".");
+
+  if (!actual) {
+    this.report(true, actual, expected, "Missing expected exception" + message);
+  }
+
+  if ((actual && expected && !expectedException(actual, expected))) {
+    throw actual;
+  }
+
+  this.report(false, expected, expected, message);
+};
diff --git a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/MockColorPicker.jsm b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/MockColorPicker.jsm
new file mode 100644
index 0000000..ab9f00b
--- /dev/null
+++ b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/MockColorPicker.jsm
@@ -0,0 +1,125 @@
+/* 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/. */
+
+this.EXPORTED_SYMBOLS = ["MockColorPicker"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cm = Components.manager;
+const Cu = Components.utils;
+
+const CONTRACT_ID = "@mozilla.org/colorpicker;1";
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+var oldClassID = "", oldFactory = null;
+var newClassID = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID();
+var newFactory = function (window) {
+  return {
+    createInstance: function(aOuter, aIID) {
+      if (aOuter)
+        throw Components.results.NS_ERROR_NO_AGGREGATION;
+      return new MockColorPickerInstance(window).QueryInterface(aIID);
+    },
+    lockFactory: function(aLock) {
+      throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+    },
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
+  };
+}
+
+this.MockColorPicker = {
+  init: function(window) {
+    this.reset();
+    this.factory = newFactory(window);
+    if (!registrar.isCIDRegistered(newClassID)) {
+      try {
+        oldClassID = registrar.contractIDToCID(CONTRACT_ID);
+        oldFactory = Cm.getClassObject(Cc[CONTRACT_ID], Ci.nsIFactory);
+      } catch(ex) {
+        oldClassID = "";
+        oldFactory = null;
+        dump("TEST-INFO | can't get colorpicker registered component, " +
+             "assuming there is none");
+      }
+      if (oldClassID != "" && oldFactory != null) {
+        registrar.unregisterFactory(oldClassID, oldFactory);
+      }
+      registrar.registerFactory(newClassID, "", CONTRACT_ID, this.factory);
+    }
+  },
+
+  reset: function() {
+    this.returnColor = "";
+    this.showCallback = null;
+    this.shown = false;
+    this.showing = false;
+  },
+
+  cleanup: function() {
+    var previousFactory = this.factory;
+    this.reset();
+    this.factory = null;
+
+    registrar.unregisterFactory(newClassID, previousFactory);
+    if (oldClassID != "" && oldFactory != null) {
+      registrar.registerFactory(oldClassID, "", CONTRACT_ID, oldFactory);
+    }
+  }
+};
+
+function MockColorPickerInstance(window) {
+  this.window = window;
+};
+MockColorPickerInstance.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIColorPicker]),
+  init: function(aParent, aTitle, aInitialColor) {
+    this.parent = aParent;
+    this.initialColor = aInitialColor;
+  },
+  initialColor: "",
+  parent: null,
+  open: function(aColorPickerShownCallback) {
+    MockColorPicker.showing = true;
+    MockColorPicker.shown = true;
+
+    this.window.setTimeout(function() {
+      let result = "";
+      try {
+        if (typeof MockColorPicker.showCallback == "function") {
+          var updateCb = function(color) {
+            result = color;
+            aColorPickerShownCallback.update(color);
+          };
+          let returnColor = MockColorPicker.showCallback(this, updateCb);
+          if (typeof returnColor === "string") {
+            result = returnColor;
+          }
+        } else if (typeof MockColorPicker.returnColor === "string") {
+          result = MockColorPicker.returnColor;
+        }
+      } catch(ex) {
+        dump("TEST-UNEXPECTED-FAIL | Exception in MockColorPicker.jsm open() " +
+             "method: " + ex + "\n");
+      }
+      if (aColorPickerShownCallback) {
+        aColorPickerShownCallback.done(result);
+      }
+    }.bind(this), 0);
+  }
+};
+
+// Expose everything to content. We call reset() here so that all of the
+// relevant lazy expandos get added.
+MockColorPicker.reset();
+function exposeAll(obj) {
+  var props = {};
+  for (var prop in obj)
+    props[prop] = 'rw';
+  obj.__exposedProps__ = props;
+}
+exposeAll(MockColorPicker);
+exposeAll(MockColorPickerInstance.prototype);
diff --git a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/MockFilePicker.jsm b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/MockFilePicker.jsm
new file mode 100644
index 0000000..f7b130e
--- /dev/null
+++ b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/MockFilePicker.jsm
@@ -0,0 +1,236 @@
+/* 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/. */
+
+this.EXPORTED_SYMBOLS = ["MockFilePicker"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cm = Components.manager;
+const Cu = Components.utils;
+
+const CONTRACT_ID = "@mozilla.org/filepicker;1";
+
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+var oldClassID, oldFactory;
+var newClassID = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID();
+var newFactory = function (window) {
+  return {
+    createInstance: function(aOuter, aIID) {
+      if (aOuter)
+        throw Components.results.NS_ERROR_NO_AGGREGATION;
+      return new MockFilePickerInstance(window).QueryInterface(aIID);
+    },
+    lockFactory: function(aLock) {
+      throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+    },
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
+  };
+}
+
+this.MockFilePicker = {
+  returnOK: Ci.nsIFilePicker.returnOK,
+  returnCancel: Ci.nsIFilePicker.returnCancel,
+  returnReplace: Ci.nsIFilePicker.returnReplace,
+
+  filterAll: Ci.nsIFilePicker.filterAll,
+  filterHTML: Ci.nsIFilePicker.filterHTML,
+  filterText: Ci.nsIFilePicker.filterText,
+  filterImages: Ci.nsIFilePicker.filterImages,
+  filterXML: Ci.nsIFilePicker.filterXML,
+  filterXUL: Ci.nsIFilePicker.filterXUL,
+  filterApps: Ci.nsIFilePicker.filterApps,
+  filterAllowURLs: Ci.nsIFilePicker.filterAllowURLs,
+  filterAudio: Ci.nsIFilePicker.filterAudio,
+  filterVideo: Ci.nsIFilePicker.filterVideo,
+
+  window: null,
+
+  init: function(window) {
+    this.window = window;
+
+    this.reset();
+    this.factory = newFactory(window);
+    if (!registrar.isCIDRegistered(newClassID)) {
+      oldClassID = registrar.contractIDToCID(CONTRACT_ID);
+      oldFactory = Cm.getClassObject(Cc[CONTRACT_ID], Ci.nsIFactory);
+      registrar.unregisterFactory(oldClassID, oldFactory);
+      registrar.registerFactory(newClassID, "", CONTRACT_ID, this.factory);
+    }
+  },
+
+  reset: function() {
+    this.appendFilterCallback = null;
+    this.appendFiltersCallback = null;
+    this.displayDirectory = null;
+    this.filterIndex = 0;
+    this.mode = null;
+    this.returnFiles = [];
+    this.returnValue = null;
+    this.showCallback = null;
+    this.shown = false;
+    this.showing = false;
+  },
+
+  cleanup: function() {
+    var previousFactory = this.factory;
+    this.reset();
+    this.factory = null;
+    if (oldFactory) {
+      registrar.unregisterFactory(newClassID, previousFactory);
+      registrar.registerFactory(oldClassID, "", CONTRACT_ID, oldFactory);
+    }
+  },
+
+  useAnyFile: function() {
+    var file = FileUtils.getDir("TmpD", [], false);
+    file.append("testfile");
+    file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0644);
+    this.returnFiles = [file];
+  },
+
+  useBlobFile: function() {
+    var blob = new this.window.Blob([]);
+    var file = new this.window.File(blob, { name: 'helloworld.txt', type: 'plain/text' });
+    this.returnFiles = [file];
+  },
+
+  isNsIFile: function(aFile) {
+    let ret = false;
+    try {
+      if (aFile.QueryInterface(Ci.nsIFile))
+        ret = true;
+    } catch(e) {}
+
+    return ret;
+  }
+};
+
+function MockFilePickerInstance(window) {
+  this.window = window;
+};
+MockFilePickerInstance.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIFilePicker]),
+  init: function(aParent, aTitle, aMode) {
+    MockFilePicker.mode = aMode;
+    this.filterIndex = MockFilePicker.filterIndex;
+    this.parent = aParent;
+  },
+  appendFilter: function(aTitle, aFilter) {
+    if (typeof MockFilePicker.appendFilterCallback == "function")
+      MockFilePicker.appendFilterCallback(this, aTitle, aFilter);
+  },
+  appendFilters: function(aFilterMask) {
+    if (typeof MockFilePicker.appendFiltersCallback == "function")
+      MockFilePicker.appendFiltersCallback(this, aFilterMask);
+  },
+  defaultString: "",
+  defaultExtension: "",
+  parent: null,
+  filterIndex: 0,
+  displayDirectory: null,
+  get file() {
+    if (MockFilePicker.returnFiles.length >= 1 &&
+        // window.File does not implement nsIFile
+        MockFilePicker.isNsIFile(MockFilePicker.returnFiles[0])) {
+      return MockFilePicker.returnFiles[0];
+    }
+
+    return null;
+  },
+  get domfile()  {
+    if (MockFilePicker.returnFiles.length >= 1) {
+      // window.File does not implement nsIFile
+      if (!MockFilePicker.isNsIFile(MockFilePicker.returnFiles[0])) {
+        return MockFilePicker.returnFiles[0];
+      }
+
+      let utils = this.parent.QueryInterface(Ci.nsIInterfaceRequestor)
+                             .getInterface(Ci.nsIDOMWindowUtils);
+      return utils.wrapDOMFile(MockFilePicker.returnFiles[0]);
+    }
+    return null;
+  },
+  get fileURL() {
+    if (MockFilePicker.returnFiles.length >= 1 &&
+        // window.File does not implement nsIFile
+        MockFilePicker.isNsIFile(MockFilePicker.returnFiles[0])) {
+      return Services.io.newFileURI(MockFilePicker.returnFiles[0]);
+    }
+
+    return null;
+  },
+  get files() {
+    return {
+      index: 0,
+      QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),
+      hasMoreElements: function() {
+        return this.index < MockFilePicker.returnFiles.length;
+      },
+      getNext: function() {
+        // window.File does not implement nsIFile
+        if (!MockFilePicker.isNsIFile(MockFilePicker.returnFiles[this.index])) {
+          return null;
+        }
+        return MockFilePicker.returnFiles[this.index++];
+      }
+    };
+  },
+  get domfiles()  {
+    let utils = this.parent.QueryInterface(Ci.nsIInterfaceRequestor)
+                           .getInterface(Ci.nsIDOMWindowUtils);
+    return {
+      index: 0,
+      QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),
+      hasMoreElements: function() {
+        return this.index < MockFilePicker.returnFiles.length;
+      },
+      getNext: function() {
+        // window.File does not implement nsIFile
+        if (!MockFilePicker.isNsIFile(MockFilePicker.returnFiles[this.index])) {
+          return MockFilePicker.returnFiles[this.index++];
+        }
+        return utils.wrapDOMFile(MockFilePicker.returnFiles[this.index++]);
+      }
+    };
+  },
+  show: function() {
+    MockFilePicker.displayDirectory = this.displayDirectory;
+    MockFilePicker.shown = true;
+    if (typeof MockFilePicker.showCallback == "function") {
+      var returnValue = MockFilePicker.showCallback(this);
+      if (typeof returnValue != "undefined")
+        return returnValue;
+    }
+    return MockFilePicker.returnValue;
+  },
+  open: function(aFilePickerShownCallback) {
+    MockFilePicker.showing = true;
+    this.window.setTimeout(function() {
+      let result = Components.interfaces.nsIFilePicker.returnCancel;
+      try {
+        result = this.show();
+      } catch(ex) {
+      }
+      if (aFilePickerShownCallback) {
+        aFilePickerShownCallback.done(result);
+      }
+    }.bind(this), 0);
+  }
+};
+
+// Expose everything to content. We call reset() here so that all of the relevant
+// lazy expandos get added.
+MockFilePicker.reset();
+function exposeAll(obj) {
+  var props = {};
+  for (var prop in obj)
+    props[prop] = 'rw';
+  obj.__exposedProps__ = props;
+}
+exposeAll(MockFilePicker);
+exposeAll(MockFilePickerInstance.prototype);
diff --git a/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/MockPermissionPrompt.jsm b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/MockPermissionPrompt.jsm
new file mode 100644
index 0000000..e07f8e0
--- /dev/null
+++ b/test/resources/firefox/extensions/special-powers at mozilla.org/chrome/specialpowers/modules/MockPermissionPrompt.jsm
@@ -0,0 +1,97 @@
+/* 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/. */
+
+this.EXPORTED_SYMBOLS = ["MockPermissionPrompt"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cm = Components.manager;
+const Cu = Components.utils;
+
+const CONTRACT_ID = "@mozilla.org/content-permission/prompt;1";
+
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+var oldClassID, oldFactory;
+var newClassID = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID();
+var newFactory = {
+  createInstance: function(aOuter, aIID) {
+    if (aOuter)
+      throw Components.results.NS_ERROR_NO_AGGREGATION;
+    return new MockPermissionPromptInstance().QueryInterface(aIID);
+  },
+  lockFactory: function(aLock) {
+    throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+  },
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
+};
+
+this.MockPermissionPrompt = {
+  init: function() {
+    this.reset();
+    if (!registrar.isCIDRegistered(newClassID)) {
+      try {
+        oldClassID = registrar.contractIDToCID(CONTRACT_ID);
+        oldFactory = Cm.getClassObject(Cc[CONTRACT_ID], Ci.nsIFactory);
+      } catch (ex) {
+        oldClassID = "";
+        oldFactory = null;
+        dump("TEST-INFO | can't get permission prompt registered component, " +
+            "assuming there is none");
+      }
+      if (oldFactory) {
+        registrar.unregisterFactory(oldClassID, oldFactory);
+      }
+      registrar.registerFactory(newClassID, "", CONTRACT_ID, newFactory);
+    }
+  },
+  
+  reset: function() {
+  },
+  
+  cleanup: function() {
+    this.reset();
+    if (oldFactory) {
+      registrar.unregisterFactory(newClassID, newFactory);
+      registrar.registerFactory(oldClassID, "", CONTRACT_ID, oldFactory);
+    }
+  },
+};
+
+function MockPermissionPromptInstance() { };
+MockPermissionPromptInstance.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionPrompt]),
+
+  promptResult: Ci.nsIPermissionManager.UNKNOWN_ACTION,
+
+  prompt: function(request) {
+
+    let perms = request.types.QueryInterface(Ci.nsIArray);
+    for (let idx = 0; idx < perms.length; idx++) {
+      let perm = perms.queryElementAt(idx, Ci.nsIContentPermissionType);
+      if (Services.perms.testExactPermissionFromPrincipal(
+           request.principal, perm.type) != Ci.nsIPermissionManager.ALLOW_ACTION) {
+        request.cancel();
+        return;
+      }
+    }
+
+    request.allow();
+  }
+};
+
+// Expose everything to content. We call reset() here so that all of the relevant
+// lazy expandos get added.
+MockPermissionPrompt.reset();
+function exposeAll(obj) {
+  var props = {};
+  for (var prop in obj)
+    props[prop] = 'rw';
+  obj.__exposedProps__ = props;
+}
+exposeAll(MockPermissionPrompt);
+exposeAll(MockPermissionPromptInstance.prototype);
diff --git a/test/resources/firefox/extensions/special-powers at mozilla.org/components/SpecialPowersObserver.js b/test/resources/firefox/extensions/special-powers at mozilla.org/components/SpecialPowersObserver.js
old mode 100755
new mode 100644
index 90655e2..7adc637
--- a/test/resources/firefox/extensions/special-powers at mozilla.org/components/SpecialPowersObserver.js
+++ b/test/resources/firefox/extensions/special-powers at mozilla.org/components/SpecialPowersObserver.js
@@ -1,40 +1,6 @@
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Special Powers code
- *
- * The Initial Developer of the Original Code is
- * Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2010
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Jesse Ruderman <jruderman at mozilla.com>
- *   Robert Sayre <sayrer at gmail.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK *****/
+/* 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/. */
 
 // Based on:
 // https://bugzilla.mozilla.org/show_bug.cgi?id=549539
@@ -50,34 +16,33 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 const CHILD_SCRIPT = "chrome://specialpowers/content/specialpowers.js"
+const CHILD_SCRIPT_API = "chrome://specialpowers/content/specialpowersAPI.js"
+const CHILD_LOGGER_SCRIPT = "chrome://specialpowers/content/MozillaLogger.js"
 
-/**
- * Special Powers Exception - used to throw exceptions nicely
- **/
-function SpecialPowersException(aMsg) {
-  this.message = aMsg;
-  this.name = "SpecialPowersException";
-}
 
-SpecialPowersException.prototype.toString = function() {
-  return this.name + ': "' + this.message + '"';
-};
+// Glue to add in the observer API to this object.  This allows us to share code with chrome tests
+var loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
+                       .getService(Components.interfaces.mozIJSSubScriptLoader);
+loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserverAPI.js");
 
 /* XPCOM gunk */
-function SpecialPowersObserver() {
+this.SpecialPowersObserver = function SpecialPowersObserver() {
   this._isFrameScriptLoaded = false;
+  this._mmIsGlobal = true;
   this._messageManager = Cc["@mozilla.org/globalmessagemanager;1"].
-                         getService(Ci.nsIChromeFrameMessageManager);
+                         getService(Ci.nsIMessageBroadcaster);
 }
 
-SpecialPowersObserver.prototype = {
-  classDescription: "Special powers Observer for use in testing.",
-  classID:          Components.ID("{59a52458-13e0-4d93-9d85-a637344f29a1}"),
-  contractID:       "@mozilla.org/special-powers-observer;1",
-  QueryInterface:   XPCOMUtils.generateQI([Components.interfaces.nsIObserver]),
-  _xpcom_categories: [{category: "profile-after-change", service: true }],
 
-  observe: function(aSubject, aTopic, aData)
+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":
@@ -90,190 +55,105 @@ SpecialPowersObserver.prototype = {
           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("SPPermissionManager", this);
+          this._messageManager.addMessageListener("SPWebAppService", this);
+          this._messageManager.addMessageListener("SPObserverService", this);
+          this._messageManager.addMessageListener("SPLoadChromeScript", this);
+          this._messageManager.addMessageListener("SPChromeScriptMessage", this);
+
+          this._messageManager.loadFrameScript(CHILD_LOGGER_SCRIPT, true);
+          this._messageManager.loadFrameScript(CHILD_SCRIPT_API, true);
           this._messageManager.loadFrameScript(CHILD_SCRIPT, true);
           this._isFrameScriptLoaded = true;
         }
         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 "plugin-crashed":
-      case "ipc:content-shutdown":
-        function addDumpIDToMessage(propertyName) {
-          var id = aSubject.getPropertyAsAString(propertyName);
-          if (id) {
-            message.dumpIDs.push(id);
-          }
-        }
-
-        var message = { type: "crash-observed", dumpIDs: [] };
-        aSubject = aSubject.QueryInterface(Ci.nsIPropertyBag2);
-        if (aTopic == "plugin-crashed") {
-          addDumpIDToMessage("pluginDumpID");
-          addDumpIDToMessage("browserDumpID");
-        } else { // ipc:content-shutdown
-          addDumpIDToMessage("dumpID");
-        }
-        this._messageManager.sendAsyncMessage("SPProcessCrashService", message);
+      default:
+        this._observe(aSubject, aTopic, aData);
         break;
     }
-  },
+  };
 
-  init: function()
+  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.init = function(messageManager)
   {
     var obs = Services.obs;
     obs.addObserver(this, "xpcom-shutdown", false);
     obs.addObserver(this, "chrome-document-global-created", false);
-  },
+    obs.addObserver(this, "http-on-modify-request", false);
 
-  uninit: function()
+    if (messageManager) {
+      this._messageManager = messageManager;
+      this._mmIsGlobal = false;
+    }
+  };
+
+  SpecialPowersObserver.prototype.uninit = function()
   {
     var obs = Services.obs;
-    obs.removeObserver(this, "chrome-document-global-created", false);
-    this.removeProcessCrashObservers();
-  },
-  
-  addProcessCrashObservers: function() {
+    obs.removeObserver(this, "chrome-document-global-created");
+    obs.removeObserver(this, "http-on-modify-request");
+    this._removeProcessCrashObservers();
+  };
+
+  SpecialPowersObserver.prototype._addProcessCrashObservers = function() {
     if (this._processCrashObserversRegistered) {
       return;
     }
 
-    Services.obs.addObserver(this, "plugin-crashed", false);
-    Services.obs.addObserver(this, "ipc:content-shutdown", false);
+    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;
-  },
+  };
 
-  removeProcessCrashObservers: function() {
+  SpecialPowersObserver.prototype._removeProcessCrashObservers = function() {
     if (!this._processCrashObserversRegistered) {
       return;
     }
 
-    Services.obs.removeObserver(this, "plugin-crashed");
-    Services.obs.removeObserver(this, "ipc:content-shutdown");
-    this._processCrashObserversRegistered = false;
-  },
+    var obs = Components.classes["@mozilla.org/observer-service;1"]
+                        .getService(Components.interfaces.nsIObserverService);
 
-  getCrashDumpDir: function() {
-    if (!this._crashDumpDir) {
-      var directoryService = Cc["@mozilla.org/file/directory_service;1"]
-                             .getService(Ci.nsIProperties);
-      this._crashDumpDir = directoryService.get("ProfD", Ci.nsIFile);
-      this._crashDumpDir.append("minidumps");
-    }
-    return this._crashDumpDir;
-  },
-
-  deleteCrashDumpFiles: function(aFilenames) {
-    var crashDumpDir = this.getCrashDumpDir();
-    if (!crashDumpDir.exists()) {
-      return false;
-    }
-
-    var success = aFilenames.length != 0;
-    aFilenames.forEach(function(crashFilename) {
-      var file = crashDumpDir.clone();
-      file.append(crashFilename);
-      if (file.exists()) {
-        file.remove(false);
-      } else {
-        success = false;
-      }
-    });
-    return success;
-  },
-
-  findCrashDumpFiles: function(aToIgnore) {
-    var crashDumpDir = this.getCrashDumpDir();
-    var entries = crashDumpDir.exists() && crashDumpDir.directoryEntries;
-    if (!entries) {
-      return [];
-    }
-
-    var crashDumpFiles = [];
-    while (entries.hasMoreElements()) {
-      var file = entries.getNext().QueryInterface(Ci.nsIFile);
-      var path = String(file.path);
-      if (path.match(/\.(dmp|extra)$/) && !aToIgnore[path]) {
-        crashDumpFiles.push(path);
-      }
-    }
-    return crashDumpFiles.concat();
-  },
+    obs.removeObserver(this, "plugin-crashed");
+    obs.removeObserver(this, "ipc:content-shutdown");
+    this._processCrashObserversRegistered = false;
+  };
 
   /**
    * messageManager callback function
    * This will get requests from our API in the window and process them in chrome for it
    **/
-  receiveMessage: function(aMessage) {
+  SpecialPowersObserver.prototype.receiveMessage = function(aMessage) {
     switch(aMessage.name) {
-      case "SPPrefService":
-        var prefs = Services.prefs;
-        var prefType = aMessage.json.prefType.toUpperCase();
-        var prefName = aMessage.json.prefName;
-        var prefValue = "prefValue" in aMessage.json ? aMessage.json.prefValue : null;
-
-        if (aMessage.json.op == "get") {
-          if (!prefName || !prefType)
-            throw new SpecialPowersException("Invalid parameters for get in SPPrefService");
-        } else if (aMessage.json.op == "set") {
-          if (!prefName || !prefType  || prefValue === null)
-            throw new SpecialPowersException("Invalid parameters for set in SPPrefService");
-        } else if (aMessage.json.op == "clear") {
-          if (!prefName)
-            throw new SpecialPowersException("Invalid parameters for clear in SPPrefService");
-        } else {
-          throw new SpecialPowersException("Invalid operation for SPPrefService");
-        }
-        // Now we make the call
-        switch(prefType) {
-          case "BOOL":
-            if (aMessage.json.op == "get")
-              return(prefs.getBoolPref(prefName));
-            else 
-              return(prefs.setBoolPref(prefName, prefValue));
-          case "INT":
-            if (aMessage.json.op == "get") 
-              return(prefs.getIntPref(prefName));
-            else
-              return(prefs.setIntPref(prefName, prefValue));
-          case "CHAR":
-            if (aMessage.json.op == "get")
-              return(prefs.getCharPref(prefName));
-            else
-              return(prefs.setCharPref(prefName, prefValue));
-          case "COMPLEX":
-            if (aMessage.json.op == "get")
-              return(prefs.getComplexValue(prefName, prefValue[0]));
-            else
-              return(prefs.setComplexValue(prefName, prefValue[0], prefValue[1]));
-          case "":
-            if (aMessage.json.op == "clear") {
-              prefs.clearUserPref(prefName);
-              return;
-            }
-        }
-        break;
-
-      case "SPProcessCrashService":
-        switch (aMessage.json.op) {
-          case "register-observer":
-            this.addProcessCrashObservers();
-            break;
-          case "unregister-observer":
-            this.removeProcessCrashObservers();
-            break;
-          case "delete-crash-dump-files":
-            return this.deleteCrashDumpFiles(aMessage.json.filenames);
-          case "find-crash-dump-files":
-            return this.findCrashDumpFiles(aMessage.json.crashDumpFilesToIgnore);
-          default:
-            throw new SpecialPowersException("Invalid operation for SPProcessCrashService");
-        }
-        break;
-
       case "SPPingService":
         if (aMessage.json.op == "ping") {
           aMessage.target
@@ -283,11 +163,16 @@ SpecialPowersObserver.prototype = {
                   .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;
       default:
-        throw new SpecialPowersException("Unrecognized Special Powers API");
+        return this._receiveMessage(aMessage);
     }
-  }
-};
+  };
 
-const NSGetFactory = XPCOMUtils.generateNSGetFactory([SpecialPowersObserver]);
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SpecialPowersObserver]);
diff --git a/test/resources/firefox/prefs.js b/test/resources/firefox/prefs.js
index 8d59d66..3433843 100644
--- a/test/resources/firefox/prefs.js
+++ b/test/resources/firefox/prefs.js
@@ -1 +1,2 @@
 user_pref("browser.shell.checkDefaultBrowser", false);
+user_pref('extensions.autoDisableScopes', 14);

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/pdf.js.git



More information about the Pkg-javascript-commits mailing list