Bug 676274 - refactor specialpowers so we can use the api in mochitest-chrome without specialpowers. r=ted, a=test-only.
authorJoel Maher <jmaher@mozilla.com>
Fri, 26 Aug 2011 15:47:18 -0400
changeset 76999 97d43d93ab55b2b00db0ca9ed3217764f758a739
parent 76998 cff7682167aab9811838880f26a9e25228dc367c
child 77000 14463b9d2d143045b7d88b601207f9dcd5a0e45d
push idunknown
push userunknown
push dateunknown
reviewersted, test-only
bugs676274
milestone9.0a1
Bug 676274 - refactor specialpowers so we can use the api in mochitest-chrome without specialpowers. r=ted, a=test-only.
content/events/test/test_bug648573.html
content/events/test/test_bug662678.html
layout/base/tests/chrome/chrome_content_integration_window.xul
layout/style/test/test_pointer-events.html
testing/mochitest/Makefile.in
testing/mochitest/browser-test-overlay.xul
testing/mochitest/browser-test.js
testing/mochitest/harness-overlay.xul
testing/mochitest/jar.mn
testing/mochitest/redirect.html
testing/mochitest/redirect.js
testing/mochitest/runtests.py
testing/mochitest/specialpowers/components/SpecialPowersObserver.js
testing/mochitest/specialpowers/content/specialpowers.js
testing/mochitest/specialpowers/jar.mn
testing/mochitest/tests/SimpleTest/ChromePowers.js
testing/mochitest/tests/SimpleTest/Makefile.in
testing/mochitest/tests/SimpleTest/SimpleTest.js
testing/mochitest/tests/SimpleTest/SpecialPowersObserverAPI.js
testing/mochitest/tests/SimpleTest/TestRunner.js
testing/mochitest/tests/SimpleTest/specialpowersAPI.js
--- a/content/events/test/test_bug648573.html
+++ b/content/events/test/test_bug648573.html
@@ -14,17 +14,19 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 648573 **/
 
-ok(!SpecialPowers.DOMWindowUtils.mayHaveTouchEventListeners,
+var utils = SpecialPowers.getDOMWindowUtils(window);
+
+ok(!utils.mayHaveTouchEventListeners,
   "There shouldn't be any touch event listeners yet.");
 
 ok("createTouch" in document, "Should have createTouch function");
 ok("createTouchList" in document, "Should have createTouchList function");
 ok(document.createEvent("touchevent"), "Should be able to create TouchEvent objects");
 
 var t1 = document.createTouch(window, document, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
 is(t1.target, document, "Wrong target");
@@ -96,15 +98,15 @@ function runEventTest(type) {
   t.dispatchEvent(e);
   ok(t.didCall, "Should have called the listener(2)");
 }
 
 for (var i = 0; i < events.length; ++i) {
   runEventTest(events[i]);
 }
 
-ok(SpecialPowers.DOMWindowUtils.mayHaveTouchEventListeners,
+ok(utils.mayHaveTouchEventListeners,
   "There should be touch event listeners.");
 </script>
 </pre>
 </body>
 </html>
 
--- a/content/events/test/test_bug662678.html
+++ b/content/events/test/test_bug662678.html
@@ -13,40 +13,44 @@ https://bugzilla.mozilla.org/show_bug.cg
 <p id="display"></p>
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 662678 **/
+SimpleTest.waitForExplicitFinish();
 
-window.addEventListener("devicemotion", function(event) {
+var checkMotion = function(event) {
+  window.removeEventListener("devicemotion", checkMotion, true);
+
   is(event.acceleration.x, 1.5);
   is(event.acceleration.y, 1.5);
   is(event.acceleration.z, 1.5);
 
   is(event.accelerationIncludingGravity.x, 1.5);
   is(event.accelerationIncludingGravity.y, 1.5);
   is(event.accelerationIncludingGravity.z, 1.5);
 
   is(event.rotationRate.alpha, 1.5);
   is(event.rotationRate.beta, 1.5);
   is(event.rotationRate.gamma, 1.5);
   SimpleTest.finish();
-}, true);
+};
+
+window.addEventListener("devicemotion", checkMotion, true);
 
 var event = DeviceMotionEvent;
 ok(!!event, "Should have seen DeviceMotionEvent!");
 
 event = document.createEvent("DeviceMotionEvent");
 event.initDeviceMotionEvent('devicemotion', true, true, 
                             {x:1.5,y:1.5,z:1.5},
                             {x:1.5,y:1.5,z:1.5},
                             {alpha:1.5,beta:1.5,gamma:1.5},
                             0);
 window.dispatchEvent(event);
-SimpleTest.waitForExplicitFinish();
 
 </script>
 </pre>
 </body>
 </html>
--- a/layout/base/tests/chrome/chrome_content_integration_window.xul
+++ b/layout/base/tests/chrome/chrome_content_integration_window.xul
@@ -15,25 +15,26 @@
             transparent="transparent"
             src="data:text/html,&lt;div style='position:absolute;left:0;top:0;width:100%;height:100px;background:yellow;border-bottom:100px solid black'&gt;"/>
     <!-- the top 100px is a strip of black above the content iframe -->
     <vbox style="border-top:100px solid black;"/>
   </stack>
 
   <script type="application/javascript">
     <![CDATA[
-    var imports = [ "SimpleTest", "is", "isnot", "ok" ];
+    //we need SpecialPowers for WindowSnapshot.snapshotWindow()
+    var imports = [ "SimpleTest", "is", "isnot", "ok", "SpecialPowers"];
     for each (var name in imports) {
       window[name] = window.opener.wrappedJSObject[name];
     }
     
     function runTests() {
-      var testCanvas = window.opener.SpecialPowers.snapshotWindow(window);
+      var testCanvas = snapshotWindow(window);
 
-      var refCanvas = window.opener.SpecialPowers.snapshotWindow(window);
+      var refCanvas = snapshotWindow(window);
       var ctx = refCanvas.getContext('2d');
       ctx.fillStyle = "black";
       ctx.fillRect(0, 0, refCanvas.width, refCanvas.height);
 
       var comparison = compareSnapshots(testCanvas, refCanvas, true);
       ok(comparison[0], "Rendering OK, got " + comparison[1] + ", expected " + comparison[2]);
 
       var tester = window.SimpleTest;
--- a/layout/style/test/test_pointer-events.html
+++ b/layout/style/test/test_pointer-events.html
@@ -64,18 +64,19 @@ function catches_pointer_events(element_
 function synthesizeMouseEvent(type,                // string
                               x,                   // float
                               y,                   // float
                               button,              // long
                               clickCount,          // long
                               modifiers,           // long
                               ignoreWindowBounds)  // boolean
 {
-  SpecialPowers.DOMWindowUtils.sendMouseEvent(type, x, y, button, clickCount,
-                                              modifiers, ignoreWindowBounds);
+  var utils = SpecialPowers.getDOMWindowUtils(window);
+  utils.sendMouseEvent(type, x, y, button, clickCount,
+                       modifiers, ignoreWindowBounds);
 }
 
 function run_test()
 {
     ok(catches_pointer_events("one"), "one: div should default to catching pointer events");
     ok(catches_pointer_events("two"), "two: div should catch pointer events with explicit visiblePainted");
     ok(!catches_pointer_events("three"), "three: div should not catch pointer events with explicit none");
     ok(!catches_pointer_events("four"), "four: div should not catch pointer events with inherited none");
--- a/testing/mochitest/Makefile.in
+++ b/testing/mochitest/Makefile.in
@@ -90,17 +90,16 @@ include $(topsrcdir)/build/automation-bu
 		harness.xul \
 		browser-test-overlay.xul \
 		browser-test.js \
 		chrome-harness.js \
 		ipc-overlay.xul \
 		ipc.js \
 		browser-harness.xul \
 		redirect.html \
-		redirect.js \
 		$(topsrcdir)/build/pgo/server-locations.txt \
 		$(topsrcdir)/netwerk/test/httpserver/httpd.js \
 		mozprefs.js \
 		pywebsocket_wrapper.py \
  	 	plain-loop.html \
 		$(NULL)	
 
 _PYWEBSOCKET_FILES = \
--- a/testing/mochitest/browser-test-overlay.xul
+++ b/testing/mochitest/browser-test-overlay.xul
@@ -18,16 +18,17 @@
    - The Initial Developer of the Original Code is
    - Mozilla Corporation.
    -
    - Portions created by the Initial Developer are Copyright (C) 2007
    - the Initial Developer. All Rights Reserved.
    -
    - Contributor(s):
    -      Gavin Sharp <gavin@gavinsharp.com> (original author)
+   -      Joel Maher <joel.maher@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
--- a/testing/mochitest/browser-test.js
+++ b/testing/mochitest/browser-test.js
@@ -7,34 +7,50 @@ if (Cc === undefined) {
   var Ci = Components.interfaces;
   var Cu = Components.utils;
 }
 window.addEventListener("load", testOnLoad, false);
 
 function testOnLoad() {
   window.removeEventListener("load", testOnLoad, false);
 
-  // Make sure to launch the test harness for the first opened window only
-  var prefs = Cc["@mozilla.org/preferences-service;1"].
-              getService(Ci.nsIPrefBranch);
-  if (prefs.prefHasUserValue("testing.browserTestHarness.running"))
-    return;
+  gConfig = readConfig();
+  if (gConfig.testRoot == "browser") {
+    // Make sure to launch the test harness for the first opened window only
+    var prefs = Cc["@mozilla.org/preferences-service;1"].
+                getService(Ci.nsIPrefBranch);
+    if (prefs.prefHasUserValue("testing.browserTestHarness.running"))
+      return;
 
-  prefs.setBoolPref("testing.browserTestHarness.running", true);
-  gConfig = readConfig();
+    prefs.setBoolPref("testing.browserTestHarness.running", true);
 
-  if (gConfig.testRoot == "browser") {
     var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
              getService(Ci.nsIWindowWatcher);
     var sstring = Cc["@mozilla.org/supports-string;1"].
                   createInstance(Ci.nsISupportsString);
     sstring.data = location.search;
 
     ww.openWindow(window, "chrome://mochikit/content/browser-harness.xul", "browserTest",
                   "chrome,centerscreen,dialog=no,resizable,titlebar,toolbar=no,width=800,height=600", sstring);
+  } else {
+    // This code allows us to redirect without requiring specialpowers for chrome and a11y tests.
+    function messageHandler(m) {
+      messageManager.removeMessageListener("chromeEvent", messageHandler);
+      var url = m.json.data;
+
+      // Window is the [ChromeWindow] for messageManager, so we need content.window 
+      // Currently chrome tests are run in a content window instead of a ChromeWindow
+      var webNav = content.window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                         .getInterface(Components.interfaces.nsIWebNavigation);
+      webNav.loadURI(url, null, null, null, null);
+    }
+
+    var listener = 'data:,function doLoad(e) { var data=e.getData("data");removeEventListener("contentEvent", function (e) { doLoad(e); }, false, true);sendAsyncMessage("chromeEvent", {"data":data}); };addEventListener("contentEvent", function (e) { doLoad(e); }, false, true);';
+    messageManager.loadFrameScript(listener, true);
+    messageManager.addMessageListener("chromeEvent", messageHandler);
   }
 }
 
 function Tester(aTests, aDumper, aCallback) {
   this.dumper = aDumper;
   this.tests = aTests;
   this.callback = aCallback;
   this._cs = Cc["@mozilla.org/consoleservice;1"].
@@ -43,16 +59,19 @@ function Tester(aTests, aDumper, aCallba
              getService(Ci.nsIWindowMediator);
   this._fm = Cc["@mozilla.org/focus-manager;1"].
              getService(Ci.nsIFocusManager);
 
   this._scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
                        getService(Ci.mozIJSSubScriptLoader);
   this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", this.EventUtils);
   var simpleTestScope = {};
+  this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/specialpowersAPI.js", simpleTestScope);
+  this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/SpecialPowersObserverAPI.js", simpleTestScope);
+  this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/ChromePowers.js", simpleTestScope);
   this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/SimpleTest.js", simpleTestScope);
   this._scriptLoader.loadSubScript("chrome://mochikit/content/chrome-harness.js", simpleTestScope);
   this.SimpleTest = simpleTestScope.SimpleTest;
 }
 Tester.prototype = {
   EventUtils: {},
   SimpleTest: {},
 
--- a/testing/mochitest/harness-overlay.xul
+++ b/testing/mochitest/harness-overlay.xul
@@ -7,16 +7,22 @@
          xmlns:html="http://www.w3.org/1999/xhtml"
          xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
 <window>
   <script type="text/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/LogController.js"/>
   <script type="text/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/specialpowersAPI.js"/>
+  <script type="text/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SpecialPowersObserverAPI.js"/>
+  <script type="text/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/ChromePowers.js"/>
+  <script type="text/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/TestRunner.js"/>
   <script type="text/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/MozillaLogger.js"/>
   <script type="text/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/quit.js" />
   <script type="application/javascript"
           src="chrome://mochikit/content/chrome-harness.js" />
   <script type="text/javascript"
--- a/testing/mochitest/jar.mn
+++ b/testing/mochitest/jar.mn
@@ -5,20 +5,22 @@ mochikit.jar:
   content/browser-test-overlay.xul (browser-test-overlay.xul)
   content/chrome-harness.js (chrome-harness.js)
   content/harness-overlay.xul (harness-overlay.xul)
   content/harness.xul (harness.xul)
   content/ipc.js (ipc.js)
   content/ipc-overlay.xul (ipc-overlay.xul)
   content/mozprefs.js (mozprefs.js)
   content/redirect.html (redirect.html)
-  content/redirect.js (redirect.js)
   content/server.js (server.js)
   content/dynamic/getMyDirectory.sjs (dynamic/getMyDirectory.sjs)
   content/static/harness.css (static/harness.css)
+  content/tests/SimpleTest/ChromePowers.js (tests/SimpleTest/ChromePowers.js)
+  content/tests/SimpleTest/specialpowersAPI.js (tests/SimpleTest/specialpowersAPI.js)
+  content/tests/SimpleTest/SpecialPowersObserverAPI.js (tests/SimpleTest/SpecialPowersObserverAPI.js)
   content/tests/SimpleTest/EventUtils.js (tests/SimpleTest/EventUtils.js)
   content/tests/SimpleTest/ChromeUtils.js (tests/SimpleTest/ChromeUtils.js)
   content/tests/SimpleTest/MozillaLogger.js (tests/SimpleTest/MozillaLogger.js)
   content/tests/SimpleTest/LogController.js (tests/SimpleTest/LogController.js)
   content/tests/SimpleTest/PluginUtils.js (tests/SimpleTest/PluginUtils.js)
   content/tests/SimpleTest/quit.js (tests/SimpleTest/quit.js)
   content/tests/SimpleTest/setup.js (tests/SimpleTest/setup.js)
   content/tests/SimpleTest/SimpleTest.js (tests/SimpleTest/SimpleTest.js)
--- a/testing/mochitest/redirect.html
+++ b/testing/mochitest/redirect.html
@@ -1,14 +1,21 @@
 <html>
 <head>
   <title>redirecting...</title>
 
-  <script type="text/javascript" src="redirect.js"></script>
+  <script type="text/javascript">
+    function redirect(aURL)
+    {
+      var element = document.createEvent("datacontainerevent");
+      element.initEvent("contentEvent", true, false);
+      element.setData("data", aURL + location.search);
+      element.setData("type", "loadURI");
+      document.dispatchEvent(element);
+    }
 
-  <script type="text/javascript">
     redirect("chrome://mochikit/content/harness.xul");
   </script>
 </head>
 <body>
 redirecting...
 </body>
 </html>
deleted file mode 100644
--- a/testing/mochitest/redirect.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* ***** 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 MozJSHTTP code.
- *
- * The Initial Developer of the Original Code is
- * Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2008
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Robert Sayre <sayrer@gmail.com>
- *   Alexander Surkov <surkov.alexander@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 ***** */
-
-function redirect(aURL)
-{
-  SpecialPowers.loadURI(window, aURL + location.search,
-                 null, null, null, null);
-}
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -505,17 +505,21 @@ class Mochitest(object):
     self.automation.log.info("INFO | runtests.py | Done installing extension.")
 
   def buildProfile(self, options):
     """ create the profile and add optional chrome bits and files if requested """
     self.automation.initializeProfile(options.profilePath, options.extraPrefs, useServerLocations = True)
     manifest = self.addChromeToProfile(options)
     self.copyExtraFilesToProfile(options)
 
-    self.installSpecialPowersExtension(options)
+    # We only need special powers in non-chrome harnesses
+    if (not options.browserChrome and
+        not options.chrome and
+        not options.a11y):
+      self.installSpecialPowersExtension(options)
     self.installExtensionsToProfile(options)
     return manifest
 
   def buildBrowserEnv(self, options):
     """ build the environment variables for the specific test and operating system """
     browserEnv = self.automation.environment(xrePath = options.xrePath)
 
     # These variables are necessary for correct application startup; change
--- a/testing/mochitest/specialpowers/components/SpecialPowersObserver.js
+++ b/testing/mochitest/specialpowers/components/SpecialPowersObserver.js
@@ -45,251 +45,109 @@
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 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._isFrameScriptLoaded = false;
   this._messageManager = Cc["@mozilla.org/globalmessagemanager;1"].
                          getService(Ci.nsIChromeFrameMessageManager);
 }
 
-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 }],
+
+SpecialPowersObserver.prototype = new SpecialPowersObserverAPI();
 
-  observe: function(aSubject, aTopic, aData)
+  SpecialPowersObserver.prototype.classDescription = "Special powers Observer for use in testing.";
+  SpecialPowersObserver.prototype.classID = Components.ID("{59a52458-13e0-4d93-9d85-a637344f29a1}");
+  SpecialPowersObserver.prototype.contractID = "@mozilla.org/special-powers-observer;1";
+  SpecialPowersObserver.prototype.QueryInterface = XPCOMUtils.generateQI([Components.interfaces.nsIObserver]);
+  SpecialPowersObserver.prototype._xpcom_categories = [{category: "profile-after-change", service: true }];
+
+  SpecialPowersObserver.prototype.observe = function(aSubject, aTopic, aData)
   {
     switch (aTopic) {
       case "profile-after-change":
         this.init();
         break;
 
       case "chrome-document-global-created":
         if (!this._isFrameScriptLoaded) {
           // Register for any messages our API needs us to handle
           this._messageManager.addMessageListener("SPPrefService", this);
           this._messageManager.addMessageListener("SPProcessCrashService", this);
           this._messageManager.addMessageListener("SPPingService", this);
 
           this._messageManager.loadFrameScript(CHILD_LOGGER_SCRIPT, true);
+          this._messageManager.loadFrameScript(CHILD_SCRIPT_API, true);
           this._messageManager.loadFrameScript(CHILD_SCRIPT, true);
           this._isFrameScriptLoaded = true;
         }
         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)
+  {
+    this._messageManager.sendAsyncMessage(msgname, msg);
+  };
+
+  SpecialPowersObserver.prototype._receiveMessage = function(aMessage) {
+    return this._receiveMessageAPI(aMessage);
+  };
+
+  SpecialPowersObserver.prototype.init = function()
   {
     var obs = Services.obs;
     obs.addObserver(this, "xpcom-shutdown", false);
     obs.addObserver(this, "chrome-document-global-created", false);
-  },
+  };
 
-  uninit: function()
+  SpecialPowersObserver.prototype.uninit = function()
   {
     var obs = Services.obs;
     obs.removeObserver(this, "chrome-document-global-created", false);
-    this.removeProcessCrashObservers();
-  },
-  
-  addProcessCrashObservers: function() {
-    if (this._processCrashObserversRegistered) {
-      return;
-    }
-
-    Services.obs.addObserver(this, "plugin-crashed", false);
-    Services.obs.addObserver(this, "ipc:content-shutdown", false);
-    this._processCrashObserversRegistered = true;
-  },
-
-  removeProcessCrashObservers: function() {
-    if (!this._processCrashObserversRegistered) {
-      return;
-    }
-
-    Services.obs.removeObserver(this, "plugin-crashed");
-    Services.obs.removeObserver(this, "ipc:content-shutdown");
-    this._processCrashObserversRegistered = false;
-  },
-
-  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();
-  },
+    this._removeProcessCrashObservers();
+  };
 
   /**
    * 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
                   .QueryInterface(Ci.nsIFrameLoaderOwner)
                   .frameLoader
                   .messageManager
                   .sendAsyncMessage("SPPingService", { op: "pong" });
         }
         break;
-
       default:
-        throw new SpecialPowersException("Unrecognized Special Powers API");
+        return this._receiveMessage(aMessage);
     }
-  }
-};
+  };
 
 const NSGetFactory = XPCOMUtils.generateNSGetFactory([SpecialPowersObserver]);
--- a/testing/mochitest/specialpowers/content/specialpowers.js
+++ b/testing/mochitest/specialpowers/content/specialpowers.js
@@ -33,381 +33,103 @@
  * 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 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._encounteredCrashDumpFiles = [];
-  this._unexpectedCrashDumpFiles = { };
-  this._crashDumpDir = null;
+  this.DOMWindowUtils = bindDOMWindowUtils(window);
   this._pongHandlers = [];
   this._messageListener = this._messageReceived.bind(this);
   addMessageListener("SPPingService", this._messageListener);
-  this._consoleListeners = [];
-}
-
-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];
-      try {
-        desc[prop] = function() { return oldval.apply(util, arguments); };
-      } catch (ex) {
-        dump("WARNING: Special Powers failed to rebind function: " + desc + "::" + prop + "\n");
-      }
-    }
-  }
-  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,
+SpecialPowers.prototype = new SpecialPowersAPI();
 
-  // 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));
-  },
+SpecialPowers.prototype.toString = function() { return "[SpecialPowers]"; };
+SpecialPowers.prototype.sanityCheck = function() { return "foo"; };
 
-  // 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': ""};
-    sendSyncMessage('SPPrefService', msg);
-  },
+// This gets filled in in the constructor.
+SpecialPowers.prototype.DOMWindowUtils = undefined;
 
-  // 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 = 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(sendSyncMessage('SPPrefService', msg)[0]);
-  },
+SpecialPowers.prototype._sendSyncMessage = function(msgname, msg) {
+  return sendSyncMessage(msgname, msg);
+};
 
-  //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");
-  },
+SpecialPowers.prototype._sendAsyncMessage = function(msgname, msg) {
+  sendAsyncMessage(msgname, msg);
+};
 
-  addChromeEventListener: function(type, listener, capture, allowUntrusted) {
-    addEventListener(type, listener, capture, allowUntrusted);
-  },
-  removeChromeEventListener: function(type, listener, capture) {
-    removeEventListener(type, listener, capture);
-  },
-
-  addErrorConsoleListener: function(listener) {
-    var consoleListener = {
-      userListener: listener,
-      observe: function(consoleMessage) {
-        this.userListener(consoleMessage.message);
-      }
-    };
+SpecialPowers.prototype.registerProcessCrashObservers = function() {
+  addMessageListener("SPProcessCrashService", this._messageListener);
+  sendSyncMessage("SPProcessCrashService", { op: "register-observer" });
+};
 
-    Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService)
-                                       .registerListener(consoleListener);
-
-    this._consoleListeners.push(consoleListener);
-  },
-
-  removeErrorConsoleListener: function(listener) {
-    for (var index in this._consoleListeners) {
-      var consoleListener = this._consoleListeners[index];
-      if (consoleListener.userListener == listener) {
-        Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService)
-                                           .unregisterListener(consoleListener);
-        this._consoleListeners = this._consoleListeners.splice(index, 1);
-        break;
-      }
-    }
-  },
+SpecialPowers.prototype.unregisterProcessCrashObservers = function() {
+  addMessageListener("SPProcessCrashService", this._messageListener);
+  sendSyncMessage("SPProcessCrashService", { op: "unregister-observer" });
+};
 
-  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;
-  },
-
-  createSystemXHR: function() {
-    return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
-             .createInstance(Ci.nsIXMLHttpRequest);
-  },
-
-  loadURI: function(window, uri, referrer, charset, x, y) {
-    var webNav = window.QueryInterface(Ci.nsIInterfaceRequestor)
-                       .getInterface(Ci.nsIWebNavigation);
-    webNav.loadURI(uri, referrer, charset, x, y);
-  },
-
-  snapshotWindow: function (win, withCaret) {
-    var el = this.window.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
-    el.width = win.innerWidth;
-    el.height = win.innerHeight;
-    var ctx = el.getContext("2d");
-    var flags = 0;
-
-    ctx.drawWindow(win, win.scrollX, win.scrollY,
-                   win.innerWidth, win.innerHeight,
-                   "rgb(255,255,255)",
-                   withCaret ? ctx.DRAWWINDOW_DRAW_CARET : 0);
-    return el;
-  },
-
-  gc: function() {
-    this.DOMWindowUtils.garbageCollect();
-  },
-
-  forceGC: function() {
-    Components.utils.forceGC();
-  },
-
-  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;
-    }
-  },
-
-  _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;
-  },
-
-  registerProcessCrashObservers: function() {
-    addMessageListener("SPProcessCrashService", this._messageListener);
-    sendSyncMessage("SPProcessCrashService", { op: "register-observer" });
-  },
+SpecialPowers.prototype._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;
 
-  _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;
-
-      case "SPPingService":
-        if (aMessage.json.op == "pong") {
-          var handler = this._pongHandlers.shift();
-          if (handler) {
-            handler();
-          }
+    case "SPPingService":
+      if (aMessage.json.op == "pong") {
+        var handler = this._pongHandlers.shift();
+        if (handler) {
+          handler();
         }
-        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;
-  },
+      break;
+  }
+  return true;
+};
 
-  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;
-  },
-
-  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();
-      }
-    }, Ci.nsIThread.DISPATCH_NORMAL);
-  },
-
-  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);
-  },
-
-  setLogFile: function(path) {
-    this._mfl = new MozillaFileLogger(path);
-  },
-
-  log: function(data) {
-    this._mfl.log(data);
-  },
-
-  closeLogFile: function() {
-    this._mfl.close();
-  },
+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.
 function attachSpecialPowersToWindow(aWindow) {
   try {
     if ((aWindow !== null) &&
         (aWindow !== undefined) &&
-        (aWindow.wrappedJSObject) &&
+        (aWindow.parent !== null) &&
+        (aWindow.parent !== undefined) &&
+        (aWindow.parent.wrappedJSObject.SpecialPowers) &&
         !(aWindow.wrappedJSObject.SpecialPowers)) {
+      aWindow.wrappedJSObject.SpecialPowers = aWindow.parent.SpecialPowers;
+    }
+    else if ((aWindow !== null) &&
+             (aWindow !== undefined) &&
+             (aWindow.wrappedJSObject) &&
+             !(aWindow.wrappedJSObject.SpecialPowers)) {
       aWindow.wrappedJSObject.SpecialPowers = new SpecialPowers(aWindow);
     }
   } catch(ex) {
     dump("TEST-INFO | specialpowers.js |  Failed to attach specialpowers to window exception: " + ex + "\n");
   }
 }
 
 // This is a frame script, so it may be running in a content process.
@@ -418,19 +140,19 @@ function attachSpecialPowersToWindow(aWi
 function SpecialPowersManager() {
   addEventListener("DOMWindowCreated", this, false);
 }
 
 SpecialPowersManager.prototype = {
   handleEvent: function handleEvent(aEvent) {
     var window = aEvent.target.defaultView;
 
-    // only add SpecialPowers to data pages, not about:*
+    // only add SpecialPowers to data pages, not about:* or chrome://*
     var uri = window.document.documentURIObject;
-    if (uri.spec.split(":")[0] == "about") {
+    if (uri.scheme === "chrome" || uri.spec.split(":")[0] == "about") {
       return;
     }
 
     attachSpecialPowersToWindow(window);
   }
 };
 
 var specialpowersmanager = new SpecialPowersManager();
--- a/testing/mochitest/specialpowers/jar.mn
+++ b/testing/mochitest/specialpowers/jar.mn
@@ -1,4 +1,6 @@
 specialpowers.jar:
 % content specialpowers %content/
   content/specialpowers.js (content/specialpowers.js)
+  content/specialpowersAPI.js (../tests/SimpleTest/specialpowersAPI.js)
+  content/SpecialPowersObserverAPI.js (../tests/SimpleTest/SpecialPowersObserverAPI.js)
   content/MozillaLogger.js (../tests/SimpleTest/MozillaLogger.js)
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/tests/SimpleTest/ChromePowers.js
@@ -0,0 +1,120 @@
+/* ***** 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) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Joel Maher <joel.maher@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 ***** */
+
+function ChromePowers(window) {
+  this.window = window;
+
+  // In the case of browser-chrome tests, we are running as a [ChromeWindow]
+  // and we have no window.QueryInterface available, content.window is what we need
+  if (typeof(window) == "ChromeWindow" && typeof(content.window) == "Window") {
+    this.DOMWindowUtils = bindDOMWindowUtils(content.window);
+    this.window = content.window
+  } else {
+    this.DOMWindowUtils = bindDOMWindowUtils(window);
+  }
+
+  this.spObserver = new SpecialPowersObserverAPI();
+}
+
+ChromePowers.prototype = new SpecialPowersAPI();
+
+ChromePowers.prototype.toString = function() { return "[ChromePowers]"; };
+ChromePowers.prototype.sanityCheck = function() { return "foo"; };
+
+// This gets filled in in the constructor.
+ChromePowers.prototype.DOMWindowUtils = undefined;
+
+ChromePowers.prototype._sendSyncMessage = function(type, msg) {
+  var aMessage = {'name':type, 'json': msg};
+  return [this._receiveMessage(aMessage)];
+};
+
+ChromePowers.prototype._sendAsyncMessage = function(type, msg) {
+  var aMessage = {'name':type, 'json': msg};
+  this._receiveMessage(aMessage);
+};
+
+ChromePowers.prototype.registerProcessCrashObservers = function() {
+  this._sendSyncMessage("SPProcessCrashService", { op: "register-observer" });
+};
+
+ChromePowers.prototype.unregisterProcessCrashObservers = function() {
+  this._sendSyncMessage("SPProcessCrashService", { op: "unregister-observer" });
+};
+
+ChromePowers.prototype._receiveMessage = function(aMessage) {
+  switch (aMessage.name) {
+    case "SPProcessCrashService":
+      if (aMessage.json.op == "register-observer" || aMessage.json.op == "unregister-observer") {
+        // Hack out register/unregister specifically for browser-chrome leaks
+        break;
+      } else if (aMessage.type == "crash-observed") {
+        var self = this;
+        msg.dumpIDs.forEach(function(id) {
+          self._encounteredCrashDumpFiles.push(id + ".dmp");
+          self._encounteredCrashDumpFiles.push(id + ".extra");
+        });
+      }
+    default:
+      // All calls go here, because we need to handle SPProcessCrashService calls as well
+      return this.spObserver._receiveMessageAPI(aMessage);
+      break;
+  }
+};
+
+ChromePowers.prototype.executeAfterFlushingMessageQueue = function(aCallback) {
+  aCallback();
+};
+
+// Expose everything but internal APIs (starting with underscores) to
+// web content.  We cannot use Object.keys to view SpecialPowers.prototype since
+// we are using the functions from SpecialPowersAPI.prototype
+ChromePowers.prototype.__exposedProps__ = {};
+for (var i in ChromePowers.prototype) {
+  if (i.charAt(0) != "_")
+    ChromePowers.prototype.__exposedProps__[i] = "r";
+}
+
+if ((window.parent !== null) &&
+    (window.parent !== undefined) &&
+    (window.parent.wrappedJSObject.SpecialPowers) &&
+    !(window.wrappedJSObject.SpecialPowers)) {
+  window.wrappedJSObject.SpecialPowers = window.parent.SpecialPowers;
+} else {
+  window.wrappedJSObject.SpecialPowers = new ChromePowers(window);
+}
+
--- a/testing/mochitest/tests/SimpleTest/Makefile.in
+++ b/testing/mochitest/tests/SimpleTest/Makefile.in
@@ -48,15 +48,17 @@ include $(topsrcdir)/config/rules.mk
 			SimpleTest.js \
 			test.css \
 			TestRunner.js \
 			setup.js \
 			EventUtils.js \
 			ChromeUtils.js \
 			WindowSnapshot.js \
 			PluginUtils.js \
+			specialpowersAPI.js \
+			SpecialPowersObserverAPI.js \
 			$(DEPTH)/toolkit/content/tests/browser/common/mockObjects.js \
                         $(DEPTH)/docshell/test/chrome/docshell_helpers.js \
 			$(NULL)
 
 libs:: $(_SIMPLETEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/$(relativesrcdir)
 
--- a/testing/mochitest/tests/SimpleTest/SimpleTest.js
+++ b/testing/mochitest/tests/SimpleTest/SimpleTest.js
@@ -17,16 +17,21 @@
 var SimpleTest = { };
 
 var parentRunner = null;
 if (parent) {
     parentRunner = parent.TestRunner;
     if (!parentRunner && parent.wrappedJSObject) {
         parentRunner = parent.wrappedJSObject.TestRunner;
     }
+
+    //This is the case where we have ChromePowers in harness.xul and we need it in the iframe
+    if (window.SpecialPowers == undefined && parent.SpecialPowers !== undefined) {
+        window.SpecialPowers = parent.SpecialPowers;
+    }
 }
 
 // running in e10s build and need to use IPC?
 var ipcMode = false;
 if (parentRunner) {
     ipcMode = parentRunner.ipcMode;
 } else if (typeof SpecialPowers != 'undefined') {
     ipcMode = SpecialPowers.hasContentProcesses();
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/tests/SimpleTest/SpecialPowersObserverAPI.js
@@ -0,0 +1,234 @@
+/* ***** 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) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Jesse Ruderman <jruderman@mozilla.com>
+ *   Robert Sayre <sayrer@gmail.com>
+ *   Joel Maher <joel.maher@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 *****/
+
+/**
+ * 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 + '"';
+};
+
+function SpecialPowersObserverAPI() {
+  this._crashDumpDir = null;
+  this._processCrashObserversRegistered = false;
+}
+
+SpecialPowersObserverAPI.prototype = {
+
+  _observe: function(aSubject, aTopic, aData) {
+    switch(aTopic) {
+      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(Components.interfaces.nsIPropertyBag2);
+        if (aTopic == "plugin-crashed") {
+          addDumpIDToMessage("pluginDumpID");
+          addDumpIDToMessage("browserDumpID");
+        } else { // ipc:content-shutdown
+          addDumpIDToMessage("dumpID");
+        }
+        this._sendAsyncMessage("SPProcessCrashService", message);
+        break;
+    }
+  },
+
+  _addProcessCrashObservers: function() {
+    if (this._processCrashObserversRegistered) {
+      return;
+    }
+
+    var obs = Components.classes["@mozilla.org/observer-service;1"]
+                        .getService(Components.interfaces.nsIObserverService);
+
+    obs.addObserver(this, "plugin-crashed", false);
+    obs.addObserver(this, "ipc:content-shutdown", false);
+    this._processCrashObserversRegistered = true;
+  },
+
+  _removeProcessCrashObservers: function() {
+    if (!this._processCrashObserversRegistered) {
+      return;
+    }
+
+    var obs = Components.classes["@mozilla.org/observer-service;1"]
+                        .getService(Components.interfaces.nsIObserverService);
+
+    obs.removeObserver(this, "plugin-crashed");
+    obs.removeObserver(this, "ipc:content-shutdown");
+    this._processCrashObserversRegistered = false;
+  },
+
+  _getCrashDumpDir: function() {
+    if (!this._crashDumpDir) {
+      var directoryService = Components.classes["@mozilla.org/file/directory_service;1"]
+                             .getService(Components.interfaces.nsIProperties);
+      this._crashDumpDir = directoryService.get("ProfD", Components.interfaces.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(Components.interfaces.nsIFile);
+      var path = String(file.path);
+      if (path.match(/\.(dmp|extra)$/) && !aToIgnore[path]) {
+        crashDumpFiles.push(path);
+      }
+    }
+    return crashDumpFiles.concat();
+  },
+
+  /**
+   * messageManager callback function
+   * This will get requests from our API in the window and process them in chrome for it
+   **/
+  _receiveMessageAPI: function(aMessage) {
+    switch(aMessage.name) {
+      case "SPPrefService":
+        var prefs = Components.classes["@mozilla.org/preferences-service;1"].
+                    getService(Components.interfaces.nsIPrefBranch2);
+        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;
+
+      default:
+        throw new SpecialPowersException("Unrecognized Special Powers API");
+    }
+  }
+};
+
--- a/testing/mochitest/tests/SimpleTest/TestRunner.js
+++ b/testing/mochitest/tests/SimpleTest/TestRunner.js
@@ -306,16 +306,18 @@ TestRunner.runNextTest = function() {
           // ... the count,
           $("fail-count").innerHTML = 1;
           // ... the indicator.
           var indicator = $("indicator");
           indicator.innerHTML = "Status: Fail (No checks actually run)";
           indicator.style.backgroundColor = "red";
         }
 
+        SpecialPowers.unregisterProcessCrashObservers();
+
         TestRunner.log("TEST-START | Shutdown"); // used by automation.py
         TestRunner.log("Passed: " + $("pass-count").innerHTML);
         TestRunner.log("Failed: " + $("fail-count").innerHTML);
         TestRunner.log("Todo:   " + $("todo-count").innerHTML);
         // If we are looping, don't send this cause it closes the log file
         if (TestRunner.loops == 0)
           TestRunner.log("SimpleTest FINISHED");
 
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/tests/SimpleTest/specialpowersAPI.js
@@ -0,0 +1,353 @@
+/* ***** 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@gmail.com
+ *   Joel Maher joel.maher@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 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 SpecialPowersAPI() { 
+  this._consoleListeners = [];
+  this._encounteredCrashDumpFiles = [];
+  this._unexpectedCrashDumpFiles = { };
+  this._crashDumpDir = null;
+  this._mfl = null;
+}
+
+function bindDOMWindowUtils(aWindow) {
+  if (!aWindow)
+    return
+
+  var util = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                   .getInterface(Components.interfaces.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];
+      try {
+        desc[prop] = function() { return oldval.apply(util, arguments); };
+      } catch (ex) {
+        dump("WARNING: Special Powers failed to rebind function: " + desc + "::" + prop + "\n");
+      }
+    }
+  }
+  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);
+  }
+  return target;
+}
+
+SpecialPowersAPI.prototype = {
+
+  getDOMWindowUtils: function(aWindow) {
+    if (aWindow == this.window && 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;
+  },
+
+  // 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]);
+  },
+
+  //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");
+  },
+
+  addChromeEventListener: function(type, listener, capture, allowUntrusted) {
+    addEventListener(type, listener, capture, allowUntrusted);
+  },
+  removeChromeEventListener: function(type, listener, capture) {
+    removeEventListener(type, listener, capture);
+  },
+
+  addErrorConsoleListener: function(listener) {
+    var consoleListener = {
+      userListener: listener,
+      observe: function(consoleMessage) {
+        this.userListener(consoleMessage.message);
+      }
+    };
+
+    Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService)
+                                       .registerListener(consoleListener);
+
+    this._consoleListeners.push(consoleListener);
+  },
+
+  removeErrorConsoleListener: function(listener) {
+    for (var index in this._consoleListeners) {
+      var consoleListener = this._consoleListeners[index];
+      if (consoleListener.userListener == listener) {
+        Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService)
+                                           .unregisterListener(consoleListener);
+        this._consoleListeners = this._consoleListeners.splice(index, 1);
+        break;
+      }
+    }
+  },
+
+  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;
+  },
+
+  createSystemXHR: function() {
+    return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+             .createInstance(Ci.nsIXMLHttpRequest);
+  },
+
+  snapshotWindow: function (win, withCaret) {
+    var el = this.window.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+    el.width = win.innerWidth;
+    el.height = win.innerHeight;
+    var ctx = el.getContext("2d");
+    var flags = 0;
+
+    ctx.drawWindow(win, win.scrollX, win.scrollY,
+                   win.innerWidth, win.innerHeight,
+                   "rgb(255,255,255)",
+                   withCaret ? ctx.DRAWWINDOW_DRAW_CARET : 0);
+    return el;
+  },
+
+  gc: function() {
+    this.DOMWindowUtils.garbageCollect();
+  },
+
+  forceGC: function() {
+    Components.utils.forceGC();
+  },
+
+  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;
+    }
+  },
+
+  _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;
+  },
+
+  executeSoon: function(aFunc) {
+    var tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
+    tm.mainThread.dispatch({
+      run: function() {
+        aFunc();
+      }
+    }, Ci.nsIThread.DISPATCH_NORMAL);
+  },
+
+  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);
+  },
+
+  setLogFile: function(path) {
+    this._mfl = new MozillaFileLogger(path);
+  },
+
+  log: function(data) {
+    this._mfl.log(data);
+  },
+
+  closeLogFile: function() {
+    this._mfl.close();
+  },
+};
+