Bug 1068440: Uplift Add-on SDK to Firefox.
authorDave Townsend <dtownsend@oxymoronical.com>
Fri, 26 Sep 2014 08:32:55 -0700
changeset 230683 8f7dd750e75fafc6460edd7f9fe6455029419f6c
parent 230682 44979dfa3bbe3bfaa0eeaf66fffa4925b8ce99c9
child 230684 240297b949aab7057f9619df2a6991dc4b82bd4b
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1068440
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1068440: Uplift Add-on SDK to Firefox. https://github.com/mozilla/addon-sdk/compare/cc3242d1ca8c...cbf6cdd0d630
addon-sdk/moz.build
addon-sdk/source/app-extension/bootstrap.js
addon-sdk/source/examples/actor-repl/data/index.html
addon-sdk/source/examples/ui-button-apis/lib/main.js
addon-sdk/source/examples/ui-button-apis/package.json
addon-sdk/source/examples/ui-button-apis/tests/test-main.js
addon-sdk/source/lib/dev/debuggee.js
addon-sdk/source/lib/dev/panel.js
addon-sdk/source/lib/dev/panel/view.js
addon-sdk/source/lib/sdk/addon/manager.js
addon-sdk/source/lib/sdk/content/content-worker.js
addon-sdk/source/lib/sdk/content/sandbox.js
addon-sdk/source/lib/sdk/context-menu.js
addon-sdk/source/lib/sdk/deprecated/traits-worker.js
addon-sdk/source/lib/sdk/deprecated/unit-test.js
addon-sdk/source/lib/sdk/deprecated/window-utils.js
addon-sdk/source/lib/sdk/io/buffer.js
addon-sdk/source/lib/sdk/io/fs.js
addon-sdk/source/lib/sdk/panel/window.js
addon-sdk/source/lib/sdk/private-browsing.js
addon-sdk/source/lib/sdk/private-browsing/utils.js
addon-sdk/source/lib/sdk/system.js
addon-sdk/source/lib/sdk/tabs/utils.js
addon-sdk/source/lib/sdk/test/utils.js
addon-sdk/source/lib/sdk/util/match-pattern.js
addon-sdk/source/lib/sdk/windows/dom.js
addon-sdk/source/lib/toolkit/loader.js
addon-sdk/source/python-lib/cuddlefish/__init__.py
addon-sdk/source/python-lib/cuddlefish/mobile-utils/bootstrap.js
addon-sdk/source/python-lib/cuddlefish/prefs.py
addon-sdk/source/python-lib/cuddlefish/rdf.py
addon-sdk/source/python-lib/cuddlefish/runner.py
addon-sdk/source/python-lib/cuddlefish/tests/bug-661083-files/packages/noLocalization/locale/en-GB.properties
addon-sdk/source/python-lib/cuddlefish/tests/bug-661083-files/packages/noLocalization/locale/en-US.properties
addon-sdk/source/python-lib/cuddlefish/tests/bug-661083-files/packages/noLocalization/package.json
addon-sdk/source/python-lib/cuddlefish/tests/bug-661083-files/packages/twoLanguages/locale/en-GB.properties
addon-sdk/source/python-lib/cuddlefish/tests/bug-661083-files/packages/twoLanguages/locale/en-US.properties
addon-sdk/source/python-lib/cuddlefish/tests/bug-661083-files/packages/twoLanguages/package.json
addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py
addon-sdk/source/python-lib/cuddlefish/tests/test_rdf.py
addon-sdk/source/test/addons/author-email/main.js
addon-sdk/source/test/addons/chrome/main.js
addon-sdk/source/test/addons/contributors/main.js
addon-sdk/source/test/addons/curly-id/lib/main.js
addon-sdk/source/test/addons/developers/main.js
addon-sdk/source/test/addons/e10s-tabs/lib/private-browsing/helper.js
addon-sdk/source/test/addons/e10s-tabs/lib/test-tab-utils.js
addon-sdk/source/test/addons/jetpack-addon.ini
addon-sdk/source/test/addons/manifest-localized/locale/en-US.properties
addon-sdk/source/test/addons/manifest-localized/main.js
addon-sdk/source/test/addons/manifest-localized/package.json
addon-sdk/source/test/addons/places/tests/test-places-bookmarks.js
addon-sdk/source/test/addons/places/tests/test-places-events.js
addon-sdk/source/test/addons/predefined-id-with-at/lib/main.js
addon-sdk/source/test/addons/preferences-branch/lib/main.js
addon-sdk/source/test/addons/private-browsing-supported/main.js
addon-sdk/source/test/addons/private-browsing-supported/test-global-private-browsing.js
addon-sdk/source/test/addons/simple-prefs-regression/lib/main.js
addon-sdk/source/test/addons/simple-prefs/lib/main.js
addon-sdk/source/test/addons/standard-id/lib/main.js
addon-sdk/source/test/addons/translators/main.js
addon-sdk/source/test/jetpack-package.ini
addon-sdk/source/test/private-browsing/global.js
addon-sdk/source/test/private-browsing/helper.js
addon-sdk/source/test/private-browsing/windows.js
addon-sdk/source/test/test-addon-installer.js
addon-sdk/source/test/test-addon-manager.js
addon-sdk/source/test/test-buffer.js
addon-sdk/source/test/test-context-menu.js
addon-sdk/source/test/test-dev-panel.js
addon-sdk/source/test/test-disposable.js
addon-sdk/source/test/test-loader.js
addon-sdk/source/test/test-match-pattern.js
addon-sdk/source/test/test-native-loader.js
addon-sdk/source/test/test-panel.js
addon-sdk/source/test/test-plain-text-console.js
addon-sdk/source/test/test-private-browsing.js
addon-sdk/source/test/test-self.js
addon-sdk/source/test/test-simple-prefs.js
addon-sdk/source/test/test-tab-utils.js
addon-sdk/source/test/test-tabs-common.js
addon-sdk/source/test/test-test-utils.js
addon-sdk/source/test/test-ui-action-button.js
addon-sdk/source/test/test-ui-frame.js
addon-sdk/source/test/test-ui-toggle-button.js
addon-sdk/source/test/test-unit-test-finder.js
addon-sdk/source/test/test-widget.js
addon-sdk/source/test/test-window-utils-global-private-browsing.js
--- a/addon-sdk/moz.build
+++ b/addon-sdk/moz.build
@@ -1,29 +1,32 @@
-# AUTOMATICALLY GENERATED FROM moz.build.in AND mach.  DO NOT EDIT.
+# AUTOMATICALLY GENERATED FROM mozbuild.template AND mach.  DO NOT EDIT.
 # 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/.
 
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 JETPACK_PACKAGE_MANIFESTS += ['source/test/jetpack-package.ini']
 JETPACK_ADDON_MANIFESTS += ['source/test/addons/jetpack-addon.ini']
 
-DIRS += ["source/modules/system"]
-
 EXTRA_JS_MODULES.sdk += [
     'source/app-extension/bootstrap.js',
 ]
 
+EXTRA_JS_MODULES.sdk.system += [
+    'source/modules/system/Startup.js',
+    'source/modules/system/XulApp.js',
+]
+
 if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk":
     EXTRA_JS_MODULES.commonjs.method.test += [
         'source/lib/method/test/browser.js',
         'source/lib/method/test/common.js',
     ]
 
     EXTRA_JS_MODULES.commonjs.sdk.deprecated += [
         'source/lib/sdk/deprecated/api-utils.js',
@@ -143,16 +146,20 @@ EXTRA_JS_MODULES.commonjs.dev += [
     'source/lib/dev/frame-script.js',
     'source/lib/dev/panel.js',
     'source/lib/dev/ports.js',
     'source/lib/dev/toolbox.js',
     'source/lib/dev/utils.js',
     'source/lib/dev/volcan.js',
 ]
 
+EXTRA_JS_MODULES.commonjs.dev.panel += [
+    'source/lib/dev/panel/view.js',
+]
+
 EXTRA_JS_MODULES.commonjs.diffpatcher += [
     'source/lib/diffpatcher/diff.js',
     'source/lib/diffpatcher/index.js',
     'source/lib/diffpatcher/patch.js',
     'source/lib/diffpatcher/rebase.js',
 ]
 
 EXTRA_JS_MODULES.commonjs.diffpatcher.test += [
@@ -206,16 +213,17 @@ EXTRA_JS_MODULES.commonjs.sdk += [
     'source/lib/sdk/widget.js',
     'source/lib/sdk/windows.js',
 ]
 
 EXTRA_JS_MODULES.commonjs.sdk.addon += [
     'source/lib/sdk/addon/events.js',
     'source/lib/sdk/addon/host.js',
     'source/lib/sdk/addon/installer.js',
+    'source/lib/sdk/addon/manager.js',
     'source/lib/sdk/addon/runner.js',
     'source/lib/sdk/addon/window.js',
 ]
 
 EXTRA_JS_MODULES.commonjs.sdk.browser += [
     'source/lib/sdk/browser/events.js',
 ]
 
--- a/addon-sdk/source/app-extension/bootstrap.js
+++ b/addon-sdk/source/app-extension/bootstrap.js
@@ -224,16 +224,20 @@ function startup(data, reasonCode) {
       prefixURI: prefixURI,
       // Add-on URI.
       rootURI: rootURI,
       // options used by system module.
       // File to write 'OK' or 'FAIL' (exit code emulation).
       resultFile: options.resultFile,
       // Arguments passed as --static-args
       staticArgs: options.staticArgs,
+
+      // Option to prevent automatic kill of firefox during tests
+      noQuit: options.no_quit,
+
       // Add-on preferences branch name
       preferencesBranch: options.preferencesBranch,
 
       // Arguments related to test runner.
       modules: {
         '@test/options': {
           iterations: options.iterations,
           filter: options.filter,
--- a/addon-sdk/source/examples/actor-repl/data/index.html
+++ b/addon-sdk/source/examples/actor-repl/data/index.html
@@ -13,96 +13,135 @@
           </section>
        </views>
   </head>
   <body>
       <pre class="input"></pre>
   </body>
   <script>
       function debounce(fn, ms) {
-        var id
+        var id;
         return function(...args) {
-          clearTimeout(id)
-          id = setTimeout(fn, ms, ...args)
-        }
+          clearTimeout(id);
+          id = setTimeout(fn, ms, ...args);
+        };
       }
 
       function Try(fn) {
         return function(...args) {
-          try { return fn(...args) }
-          catch (error) { return null }
+          try { return fn(...args); }
+          catch (error) { return null; }
+        };
+      }
+
+      var parse = Try(JSON.parse);
+
+      var CommandHistory = {
+        init: function() {
+          this._state = {};
+          this._state.els = document.querySelectorAll("body > section.task > .request");
+          this._state.idx = this._state.els.length;
+        },
+        get prev() {
+          if (!!this._state.els && this._state.idx > 0) {
+            this._state.idx--;
+            return this._state.els[this._state.idx].textContent;
+          }
+
+          return "";
+        },
+        get next() {
+          if (!!this._state.els && this._state.idx < this._state.els.length-1) {
+            this._state.idx++;
+            return this._state.els[this._state.idx].textContent;
+          }
+
+          return "";
         }
       }
 
-      var parse = Try(JSON.parse)
+      function cmdHistory(fn, editor) {
+        editor.setValue(fn());
+        document.querySelector(".input").scrollIntoView();
+      }
+
+      var cmdHistoryNext = cmdHistory.bind(null, () => CommandHistory.next);
+      var cmdHistoryBack = cmdHistory.bind(null, () => CommandHistory.prev);
 
       function send(editor) {
-          var input = editor.getWrapperElement().parentNode
-          var code = editor.getValue().trim()
-          var packet = parse(code)
+          var input = editor.getWrapperElement().parentNode;
+          var code = editor.getValue().trim();
+          var packet = parse(code);
           if (packet) {
-            var task = document.querySelector("views .task").cloneNode(true)
-            var request = task.querySelector(".request")
-            var response = task.querySelector(".response")
+            var task = document.querySelector("views .task").cloneNode(true);
+            var request = task.querySelector(".request");
+            var response = task.querySelector(".response");
 
-            input.parentNode.insertBefore(task, input)
+            input.parentNode.insertBefore(task, input);
 
             CodeMirror.runMode(JSON.stringify(packet, 2, 2),
                                "application/json",
-                               request)
-            response.classList.add("pending")
+                               request);
+            response.classList.add("pending");
 
-            editor.setValue("")
-            document.activeElement.scrollIntoView()
+            editor.setValue("");
+
+            document.querySelector(".input").scrollIntoView();
 
             port.postMessage(packet);
           }
       }
 
       var editor = CodeMirror(document.querySelector(".input"), {
         autofocus: true,
         mode: "application/json",
         matchBrackets: true,
         value: '{"to": "root", "type": "requestTypes"}',
         extraKeys: {"Cmd-Enter": send,
-                    "Ctrl-Enter": send}
+                    "Ctrl-Enter": send,
+                    "Cmd-Down": cmdHistoryNext,
+                    "Ctrl-Down": cmdHistoryNext,
+                    "Cmd-Up": cmdHistoryBack,
+                    "Ctrl-Up": cmdHistoryBack}
       });
       editor.on("change", debounce(function(editor) {
           var input = editor.getWrapperElement().parentNode;
-          if (parse(editor.getValue().trim()))
-            input.classList.remove("invalid")
-          else
-            input.classList.add("invalid")
-      }, 800))
+          if (parse(editor.getValue().trim())) {
+            input.classList.remove("invalid");
+          } else {
+            input.classList.add("invalid");
+          }
+      }, 800));
   </script>
   <script>
     window.addEventListener("message", event => {
-      console.log("REPL", event);
       window.port = event.ports[0];
       port.onmessage = onMessage;
     });
 
     var onMessage = (event) => {
       var packet = event.data;
       var code = JSON.stringify(packet, 2, 2);
 
-      var input = editor.getWrapperElement().parentNode;
-      var response = document.querySelector(".task .response.pending")
+      var input = document.querySelector(".input");
+      var response = document.querySelector(".task .response.pending");
 
       if (!response) {
-        message = document.querySelector("views .task").cloneNode(true)
-        response = message.querySelector(".response")
-        response.classList.add("message")
+        message = document.querySelector("views .task").cloneNode(true);
+        response = message.querySelector(".response");
+        response.classList.add("message");
 
         input.parentNode.insertBefore(message, input);
       }
 
-      if (packet.error)
+      if (packet.error) {
         response.classList.add("error");
-
+      }
 
       CodeMirror.runMode(code, "application/json", response);
       response.classList.remove("pending");
 
-      document.activeElement.scrollIntoView()
+      document.querySelector(".input").scrollIntoView();
+
+      CommandHistory.init();
     };
   </script>
 </html>
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/examples/ui-button-apis/lib/main.js
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+var data = require('sdk/self').data;
+var tabs = require('sdk/tabs');
+var { notify } = require('sdk/notifications');
+var { ActionButton, ToggleButton } = require('sdk/ui');
+
+var icon = 'chrome://mozapps/skin/extensions/extensionGeneric.png';
+exports.icon = icon;
+
+// your basic action button
+var action = ActionButton({
+  id: 'test-action-button',
+  label: 'Action Button',
+  icon: icon,
+  onClick: function (state) {
+    notify({
+      title: "Action!",
+      text: "This notification was triggered from an action button!",
+    });
+  }
+});
+exports.actionButton = action;
+
+var toggle = ToggleButton({
+  id: 'test-toggle-button',
+  label: 'Toggle Button',
+  icon: icon,
+  onClick: function (state) {
+    notify({
+      title: "Toggled!",
+      text: "The current state of the button is " + state.checked,
+    });
+  }
+});
+exports.toggleButton = toggle;
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/examples/ui-button-apis/package.json
@@ -0,0 +1,9 @@
+{
+  "name": "ui-button-apis",
+  "title": "Australis Button API Examples",
+  "id": "ui-button-apis@mozilla.org",
+  "description": "A Button API example",
+  "author": "jeff@canuckistani.ca (Jeff Griffiths | @canuckistani)",
+  "license": "MPL 2.0",
+  "version": "0.1"
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/examples/ui-button-apis/tests/test-main.js
@@ -0,0 +1,21 @@
+/* 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";
+
+var { actionButton, toggleButton, icon } = require("main");
+var self = require("sdk/self");
+
+exports.testActionButton = function(assert) {
+  assert.equal(actionButton.id, "test-action-button", "action button id is correct");
+  assert.equal(actionButton.label, "Action Button", "action button label is correct");
+  assert.equal(actionButton.icon, icon, "action button icon is correct");
+}
+
+exports.testToggleButton = function(assert) {
+  assert.equal(toggleButton.id, "test-toggle-button", "toggle button id is correct");
+  assert.equal(toggleButton.label, "Toggle Button", "toggle button label is correct");
+  assert.equal(toggleButton.icon, icon, "toggle button icon is correct");
+}
+
+require("sdk/test").run(exports);
--- a/addon-sdk/source/lib/dev/debuggee.js
+++ b/addon-sdk/source/lib/dev/debuggee.js
@@ -17,17 +17,16 @@ const outputs = new WeakMap();
 const inputs = new WeakMap();
 const targets = new WeakMap();
 const transports = new WeakMap();
 
 const inputFor = port => inputs.get(port);
 const outputFor = port => outputs.get(port);
 const transportFor = port => transports.get(port);
 
-
 const fromTarget = target => {
   const debuggee = new Debuggee();
   const { port1, port2 } = new MessageChannel();
   inputs.set(debuggee, port1);
   outputs.set(debuggee, port2);
   targets.set(debuggee, target);
 
   return debuggee;
--- a/addon-sdk/source/lib/dev/panel.js
+++ b/addon-sdk/source/lib/dev/panel.js
@@ -3,35 +3,35 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 module.metadata = {
   "stability": "experimental"
 };
 
-
 const { Cu } = require("chrome");
 const { Class } = require("../sdk/core/heritage");
 const { curry } = require("../sdk/lang/functional");
 const { EventTarget } = require("../sdk/event/target");
 const { Disposable, setup, dispose } = require("../sdk/core/disposable");
 const { emit, off, setListeners } = require("../sdk/event/core");
 const { when } = require("../sdk/event/utils");
 const { getFrameElement } = require("../sdk/window/utils");
 const { contract, validate } = require("../sdk/util/contract");
 const { data: { url: resolve }} = require("../sdk/self");
 const { identify } = require("../sdk/ui/id");
 const { isLocalURL, URL } = require("../sdk/url");
-const { defer } = require("../sdk/core/promise");
 const { encode } = require("../sdk/base64");
 const { marshal, demarshal } = require("./ports");
 const { fromTarget } = require("./debuggee");
 const { removed } = require("../sdk/dom/events");
 const { id: addonID } = require("../sdk/self");
+const { viewFor } = require("../sdk/view/core");
+const { createView } = require("./panel/view");
 
 const OUTER_FRAME_URI = module.uri.replace(/\.js$/, ".html");
 const FRAME_SCRIPT = module.uri.replace("/panel.js", "/frame-script.js");
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 const makeID = name =>
   ("dev-panel-" + addonID + "-" + name).
@@ -51,16 +51,19 @@ const managerFor = x => managers.get(x);
 // `Panel` instances.
 const panels = new WeakMap();
 const panelFor = frame => panels.get(frame);
 
 // Weak mapping between panels and debugees they're targeting.
 const debuggees = new WeakMap();
 const debuggeeFor = panel => debuggees.get(panel);
 
+const frames = new WeakMap();
+const frameFor = panel => frames.get(panel);
+
 const setAttributes = (node, attributes) => {
   for (var key in attributes)
     node.setAttribute(key, attributes[key]);
 };
 
 const onStateChange = ({target, data}) => {
   const panel = panelFor(target);
   panel.readyState = data.readyState;
@@ -160,32 +163,39 @@ validate.define(Panel, contract({
   }
 }));
 
 setup.define(Panel, (panel, {window, toolbox, url}) => {
   // Hack: Given that iframe created by devtools API is no good for us,
   // we obtain original iframe and replace it with the one that has
   // desired configuration.
   const original = getFrameElement(window);
-  const frame = original.cloneNode(true);
+  const container = original.parentNode;
+  original.remove();
+  const frame = createView(panel, container.ownerDocument);
 
+  // Following modifications are a temporary workaround until Bug 1049188
+  // is fixed.
+  // Enforce certain iframe customizations regardless of users request.
   setAttributes(frame, {
+    "id": original.id,
     "src": url,
-    "sandbox": "allow-scripts",
-    // It would be great if we could allow remote iframes for sandboxing
-    // panel documents in a content process, but for now platform implementation
-    // is buggy on linux so this is disabled.
-    // "remote": true,
-    "type": "content",
-    "transparent": true,
-    "seamless": "seamless"
+    "flex": 1,
+    "forceOwnRefreshDriver": "",
+    "tooltip": "aHTMLTooltip"
   });
+  frame.style.visibility = "hidden";
+  frame.classList.add("toolbox-panel-iframe");
+  // Inject iframe into designated node until add-on author decides
+  // to inject it elsewhere instead.
+  if (!frame.parentNode)
+    container.appendChild(frame);
 
-  original.parentNode.replaceChild(frame, original);
-  frame.style.visibility = "hidden";
+  // associate view with a panel
+  frames.set(panel, frame);
 
   // associate panel model with a frame view.
   panels.set(frame, panel);
 
   const debuggee = fromTarget(toolbox.target);
   // associate debuggee with a panel.
   debuggees.set(panel, debuggee);
 
@@ -208,16 +218,34 @@ setup.define(Panel, (panel, {window, too
 
   // set listeners if there are ones defined on the prototype.
   setListeners(panel, Object.getPrototypeOf(panel));
 
 
   panel.setup({ debuggee: debuggee });
 });
 
+createView.define(Panel, (panel, document) => {
+  const frame = document.createElement("iframe");
+  setAttributes(frame, {
+    "sandbox": "allow-scripts",
+    // It would be great if we could allow remote iframes for sandboxing
+    // panel documents in a content process, but for now platform implementation
+    // is buggy on linux so this is disabled.
+    // "remote": true,
+    "type": "content",
+    "transparent": true,
+    "seamless": "seamless",
+  });
+  return frame;
+});
+
 dispose.define(Panel, function(panel) {
   debuggeeFor(panel).close();
 
   debuggees.delete(panel);
   managers.delete(panel);
+  frames.delete(panel);
   panel.readyState = "destroyed";
   panel.dispose();
 });
+
+viewFor.define(Panel, frameFor);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/dev/panel/view.js
@@ -0,0 +1,14 @@
+/* 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";
+
+module.metadata = {
+  "stability": "experimental"
+};
+
+const { method } = require("method/core");
+
+const createView = method("dev/panel/view#createView");
+exports.createView = createView;
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/addon/manager.js
@@ -0,0 +1,18 @@
+/* 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";
+
+module.metadata = {
+  "stability": "experimental"
+};
+
+const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
+const { defer } = require("../core/promise");
+
+function getAddonByID(id) {
+  let { promise, resolve } = defer();
+  AddonManager.getAddonByID(id, resolve);
+  return promise;
+}
+exports.getAddonByID = getAddonByID;
--- a/addon-sdk/source/lib/sdk/content/content-worker.js
+++ b/addon-sdk/source/lib/sdk/content/content-worker.js
@@ -41,25 +41,19 @@ Object.freeze({
         return [];
       let args = Array.slice(arguments, 1);
       let results = [];
       for (let callback of listeners[name]) {
         results.push(callback.apply(null, args));
       }
       return results;
     }
-    function hasListenerFor(name) {
-      if (!(name in listeners))
-        return false;
-      return listeners[name].length > 0;
-    }
     return {
       eventEmitter: eventEmitter,
-      emit: onEvent,
-      hasListenerFor: hasListenerFor
+      emit: onEvent
     };
   },
 
   /**
    * Create an EventEmitter instance to communicate with chrome module
    * by passing only strings between compartments.
    * This function expects `emitToChrome` function, that allows to send
    * events to the chrome module. It returns the EventEmitter as `pipe`
@@ -78,30 +72,29 @@ Object.freeze({
         typeof(v) === "function"
           ? (type === "console" ? Function.toString.call(v) : void(0))
           : v;
 
       let str = JSON.stringify([type, ...args], replacer);
       emitToChrome(str);
     }
 
-    let { eventEmitter, emit, hasListenerFor } =
+    let { eventEmitter, emit } =
       ContentWorker.createEventEmitter(onEvent);
 
     return {
       pipe: eventEmitter,
       onChromeEvent: function onChromeEvent(array) {
         // We either receive a stringified array, or a real array.
         // We still allow to pass an array of objects, in WorkerSandbox.emitSync
         // in order to allow sending DOM node reference between content script
         // and modules (only used for context-menu API)
         let args = typeof array == "string" ? JSON.parse(array) : array;
         return emit.apply(null, args);
-      },
-      hasListenerFor: hasListenerFor
+      }
     };
   },
 
   injectConsole: function injectConsole(exports, pipe) {
     exports.console = Object.freeze({
       log: pipe.emit.bind(null, "console", "log"),
       info: pipe.emit.bind(null, "console", "info"),
       warn: pipe.emit.bind(null, "console", "warn"),
@@ -320,26 +313,23 @@ Object.freeze({
   },
 
   injectOptions: function (exports, options) {
     Object.defineProperty( exports.self, "options", { value: JSON.parse( options ) });
   },
 
   inject: function (exports, chromeAPI, emitToChrome, options) {
     let ContentWorker = this;
-    let { pipe, onChromeEvent, hasListenerFor } =
+    let { pipe, onChromeEvent } =
       ContentWorker.createPipe(emitToChrome);
 
     ContentWorker.injectConsole(exports, pipe);
     ContentWorker.injectTimers(exports, chromeAPI, pipe, exports.console);
     ContentWorker.injectMessageAPI(exports, pipe, exports.console);
     if ( options !== undefined ) {
       ContentWorker.injectOptions(exports, options);
     }
 
     Object.freeze( exports.self );
 
-    return {
-      emitToContent: onChromeEvent,
-      hasListenerFor: hasListenerFor
-    };
+    return onChromeEvent;
   }
 });
--- a/addon-sdk/source/lib/sdk/content/sandbox.js
+++ b/addon-sdk/source/lib/sdk/content/sandbox.js
@@ -73,25 +73,16 @@ const WorkerSandbox = Class({
    *     Doesn't ensure passing only JSON values.
    *     Mainly used by context-menu in order to avoid breaking it.
    */
   emitSync: function emitSync(...args) {
     return emitToContent(this, args);
   },
 
   /**
-   * Tells if content script has at least one listener registered for one event,
-   * through `self.on('xxx', ...)`.
-   * /!\ Shouldn't be used. Implemented to avoid breaking context-menu API.
-   */
-  hasListenerFor: function hasListenerFor(name) {
-    return modelFor(this).hasListenerFor(name);
-  },
-
-  /**
    * Configures sandbox and loads content scripts into it.
    * @param {Worker} worker
    *    content worker
    */
   initialize: function WorkerSandbox(worker, window) {
     let model = {};
     sandboxes.set(this, model);
     model.worker = worker;
@@ -185,20 +176,19 @@ const WorkerSandbox = Class({
     // Bug 758203: We have to explicitely define `__exposedProps__` in order
     // to allow access to these chrome object attributes from this sandbox with
     // content priviledges
     // https://developer.mozilla.org/en/XPConnect_wrappers#Other_security_wrappers
     let onEvent = onContentEvent.bind(null, this);
     let chromeAPI = createChromeAPI();
     let result = Cu.waiveXrays(ContentWorker).inject(content, chromeAPI, onEvent, options);
 
-    // Merge `emitToContent` and `hasListenerFor` into our private
-    // model of the WorkerSandbox so we can communicate with content
-    // script
-    merge(model, result);
+    // Merge `emitToContent` into our private model of the
+    // WorkerSandbox so we can communicate with content script
+    model.emitToContent = result;
 
     let console = new PlainTextConsole(null, getInnerId(window));
 
     // Handle messages send by this script:
     setListeners(this, console);
 
     // Inject `addon` global into target document if document is trusted,
     // `addon` in document is equivalent to `self` in content script.
--- a/addon-sdk/source/lib/sdk/context-menu.js
+++ b/addon-sdk/source/lib/sdk/context-menu.js
@@ -43,18 +43,19 @@ const SEPARATOR_CLASS = "addon-context-m
 // If more than this number of items are added to the context menu, all items
 // overflow into a "Jetpack" submenu.
 const OVERFLOW_THRESH_DEFAULT = 10;
 const OVERFLOW_THRESH_PREF =
   "extensions.addon-sdk.context-menu.overflowThreshold";
 
 // The label of the overflow sub-xul:menu.
 //
-// TODO: Localize this.
+// TODO: Localize these.
 const OVERFLOW_MENU_LABEL = "Add-ons";
+const OVERFLOW_MENU_ACCESSKEY = "A";
 
 // The class of the overflow sub-xul:menu.
 const OVERFLOW_MENU_CLASS = "addon-content-menu-overflow-menu";
 
 // The class of the overflow submenu's xul:menupopup.
 const OVERFLOW_POPUP_CLASS = "addon-content-menu-overflow-popup";
 
 //These are used by PageContext.isCurrent below. If the popupNode or any of
@@ -272,17 +273,17 @@ function populateCallbackNodeData(node) 
 
 function removeItemFromArray(array, item) {
   return array.filter(function(i) i !== item);
 }
 
 // Converts anything that isn't false, null or undefined into a string
 function stringOrNull(val) val ? String(val) : val;
 
-// Shared option validation rules for Item and Menu
+// Shared option validation rules for Item, Menu, and Separator
 let baseItemRules = {
   parentMenu: {
     is: ["object", "undefined"],
     ok: function (v) {
       if (!v)
         return true;
       return (v instanceof ItemContainer) || (v instanceof Menu);
     },
@@ -308,16 +309,27 @@ let baseItemRules = {
 
 let labelledItemRules =  mix(baseItemRules, {
   label: {
     map: stringOrNull,
     is: ["string"],
     ok: function (v) !!v,
     msg: "The item must have a non-empty string label."
   },
+  accesskey: {
+    map: stringOrNull,
+    is: ["string", "undefined", "null"],
+    ok: (v) => {
+      if (!v) {
+        return true;
+      }
+      return typeof v == "string" && v.length === 1;
+    },
+    msg: "The item must have a single character accesskey, or no accesskey."
+  },
   image: {
     map: stringOrNull,
     is: ["string", "undefined", "null"],
     ok: function (url) {
       if (!url)
         return true;
       return isValidURI(url);
     },
@@ -347,28 +359,25 @@ let menuRules = mix(labelledItemRules, {
     msg: "items must be an array, and each element in the array must be an " +
          "Item, Menu, or Separator."
   }
 });
 
 let ContextWorker = Class({
   implements: [ Worker ],
 
-  //Returns true if any context listeners are defined in the worker's port.
-  anyContextListeners: function anyContextListeners() {
-    return this.getSandbox().hasListenerFor("context");
-  },
-
   // Calls the context workers context listeners and returns the first result
   // that is either a string or a value that evaluates to true. If all of the
-  // listeners returned false then returns false. If there are no listeners
-  // then returns null.
+  // listeners returned false then returns false. If there are no listeners,
+  // returns true (show the menu item by default).
   getMatchedContext: function getCurrentContexts(popupNode) {
     let results = this.getSandbox().emitSync("context", popupNode);
-    return results.reduce(function(val, result) val || result, null);
+    if (!results.length)
+      return true;
+    return results.reduce((val, result) => val || result);
   },
 
   // Emits a click event in the worker's port. popupNode is the node that was
   // context-clicked, and clickedItemData is the data of the item that was
   // clicked.
   fireClick: function fireClick(popupNode, clickedItemData) {
     this.getSandbox().emitSync("click", popupNode, clickedItemData);
   }
@@ -384,27 +393,27 @@ function hasMatchingContext(contexts, po
 
   return true;
 }
 
 // Gets the matched context from any worker for this item. If there is no worker
 // or no matched context then returns false.
 function getCurrentWorkerContext(item, popupNode) {
   let worker = getItemWorkerForWindow(item, popupNode.ownerDocument.defaultView);
-  if (!worker || !worker.anyContextListeners())
+  if (!worker)
     return true;
   return worker.getMatchedContext(popupNode);
 }
 
 // Tests whether an item should be visible or not based on its contexts and
 // content scripts
 function isItemVisible(item, popupNode, defaultVisibility) {
   if (!item.context.length) {
     let worker = getItemWorkerForWindow(item, popupNode.ownerDocument.defaultView);
-    if (!worker || !worker.anyContextListeners())
+    if (!worker)
       return defaultVisibility;
   }
 
   if (!hasMatchingContext(item.context, popupNode))
     return false;
 
   let context = getCurrentWorkerContext(item, popupNode);
   if (typeof(context) === "string" && context != "")
@@ -440,28 +449,28 @@ function getItemWorkerForWindow(item, wi
 
   internal(item).workerMap.set(id, worker);
 
   return worker;
 }
 
 // Called when an item is clicked to send out click events to the content
 // scripts
-function itemClicked(item, clickedItem, popupNode) {
+function itemActivated(item, clickedItem, popupNode) {
   let worker = getItemWorkerForWindow(item, popupNode.ownerDocument.defaultView);
 
   if (worker) {
     let adjustedNode = popupNode;
     for (let context in item.context)
         adjustedNode = context.adjustPopupNode(adjustedNode);
     worker.fireClick(adjustedNode, clickedItem.data);
   }
 
   if (item.parentMenu)
-    itemClicked(item.parentMenu, clickedItem, popupNode);
+    itemActivated(item.parentMenu, clickedItem, popupNode);
 }
 
 // All things that appear in the context menu extend this
 let BaseItem = Class({
   initialize: function initialize() {
     addCollectionProperty(this, "context");
 
     // Used to cache content script workers and the windows they have been
@@ -528,16 +537,26 @@ let LabelledItem = Class({
   },
 
   set label(val) {
     internal(this).options.label = val;
 
     MenuManager.updateItem(this);
   },
 
+  get accesskey() {
+    return internal(this).options.accesskey;
+  },
+
+  set accesskey(val) {
+    internal(this).options.accesskey = val;
+
+    MenuManager.updateItem(this);
+  },
+
   get image() {
     return internal(this).options.image;
   },
 
   set image(val) {
     internal(this).options.image = val;
 
     MenuManager.updateItem(this);
@@ -869,33 +888,35 @@ let MenuWrapper = Class({
       type = "menu";
     else if (item instanceof Separator)
       type = "menuseparator";
 
     let xulNode = this.window.document.createElement(type);
     xulNode.setAttribute("class", ITEM_CLASS);
     if (item instanceof LabelledItem) {
       xulNode.setAttribute("label", item.label);
+      if (item.accesskey)
+        xulNode.setAttribute("accesskey", item.accesskey);
       if (item.image) {
         xulNode.setAttribute("image", item.image);
         if (item instanceof Menu)
           xulNode.classList.add("menu-iconic");
         else
           xulNode.classList.add("menuitem-iconic");
       }
       if (item.data)
         xulNode.setAttribute("value", item.data);
 
       let self = this;
       xulNode.addEventListener("command", function(event) {
         // Only care about clicks directly on this item
         if (event.target !== xulNode)
           return;
 
-        itemClicked(item, item, self.contextMenu.triggerNode);
+        itemActivated(item, item, self.contextMenu.triggerNode);
       }, false);
     }
 
     this.insertIntoXUL(item, xulNode, after);
     this.updateXULClass(xulNode);
     xulNode.data = item.data;
 
     if (item instanceof Menu) {
@@ -910,16 +931,17 @@ let MenuWrapper = Class({
   updateItem: function updateItem(item) {
     if (!this.populated)
       return;
 
     let xulNode = this.getXULNodeForItem(item);
 
     // TODO figure out why this requires setAttribute
     xulNode.setAttribute("label", item.label);
+    xulNode.setAttribute("accesskey", item.accesskey || "");
 
     if (item.image) {
       xulNode.setAttribute("image", item.image);
       if (item instanceof Menu)
         xulNode.classList.add("menu-iconic");
       else
         xulNode.classList.add("menuitem-iconic");
     }
@@ -1045,16 +1067,17 @@ let MenuWrapper = Class({
           overflowPopup.parentNode.hidden = false;
 
         if (toplevel.length > 0) {
           // The overflow menu shouldn't exist here but let's play it safe
           if (!overflowPopup) {
             let overflowMenu = this.window.document.createElement("menu");
             overflowMenu.setAttribute("class", OVERFLOW_MENU_CLASS);
             overflowMenu.setAttribute("label", OVERFLOW_MENU_LABEL);
+            overflowMenu.setAttribute("accesskey", OVERFLOW_MENU_ACCESSKEY);
             this.contextMenu.insertBefore(overflowMenu, this.separator.nextSibling);
 
             overflowPopup = this.window.document.createElement("menupopup");
             overflowPopup.setAttribute("class", OVERFLOW_POPUP_CLASS);
             overflowMenu.appendChild(overflowPopup);
           }
 
           for (let xulNode of toplevel) {
--- a/addon-sdk/source/lib/sdk/deprecated/traits-worker.js
+++ b/addon-sdk/source/lib/sdk/deprecated/traits-worker.js
@@ -82,25 +82,16 @@ const WorkerSandbox = EventEmitter.compo
    *     Mainly used by context-menu in order to avoid breaking it.
    */
   emitSync: function emitSync() {
     let args = Array.slice(arguments);
     return this._emitToContent(args);
   },
 
   /**
-   * Tells if content script has at least one listener registered for one event,
-   * through `self.on('xxx', ...)`.
-   * /!\ Shouldn't be used. Implemented to avoid breaking context-menu API.
-   */
-  hasListenerFor: function hasListenerFor(name) {
-    return this._hasListenerFor(name);
-  },
-
-  /**
    * Method called by the worker sandbox when it needs to send a message
    */
   _onContentEvent: function onContentEvent(args) {
     // As `emit`, we ensure having an asynchronous behavior
     let self = this;
     timer.setTimeout(function () {
       // We emit event to chrome/addon listeners
       self._emit.apply(self, JSON.parse(args));
@@ -218,18 +209,17 @@ const WorkerSandbox = EventEmitter.compo
       },
       __exposedProps__: {
         timers: 'r',
         sandbox: 'r',
       }
     };
     let onEvent = this._onContentEvent.bind(this);
     let result = Cu.waiveXrays(ContentWorker).inject(content, chromeAPI, onEvent, options);
-    this._emitToContent = result.emitToContent;
-    this._hasListenerFor = result.hasListenerFor;
+    this._emitToContent = result;
 
     // Handle messages send by this script:
     let self = this;
     // console.xxx calls
     this.on("console", function consoleListener(kind) {
       console[kind].apply(console, Array.slice(arguments, 1));
     });
 
--- a/addon-sdk/source/lib/sdk/deprecated/unit-test.js
+++ b/addon-sdk/source/lib/sdk/deprecated/unit-test.js
@@ -7,18 +7,19 @@ module.metadata = {
   "stability": "deprecated"
 };
 
 const memory = require("./memory");
 const timer = require("../timers");
 const cfxArgs = require("../test/options");
 const { getTabs, closeTab, getURI } = require("../tabs/utils");
 const { windows, isBrowser, getMostRecentBrowserWindow } = require("../window/utils");
-const { defer, all, Debugging: PromiseDebugging } = require("../core/promise");
+const { defer, all, Debugging: PromiseDebugging, resolve } = require("../core/promise");
 const { getInnerId } = require("../window/utils");
+const { cleanUI } = require("../test/utils")
 
 const findAndRunTests = function findAndRunTests(options) {
   var TestFinder = require("./unit-test-finder").TestFinder;
   var finder = new TestFinder({
     filter: options.filter,
     testInProcess: options.testInProcess,
     testOutOfProcess: options.testOutOfProcess
   });
@@ -263,98 +264,101 @@ TestRunner.prototype = {
     this.assertStrictEqual('[object Array]', Object.prototype.toString.apply(a), message);
   },
 
   assertNumber: function(a, message) {
     this.assertStrictEqual('[object Number]', Object.prototype.toString.apply(a), message);
   },
 
   done: function done() {
-    if (!this.isDone) {
-      this.isDone = true;
-      if(this.test.teardown) {
-        this.test.teardown(this);
+    if (this.isDone) {
+      return resolve();
+    }
+
+    this.isDone = true;
+    if (this.test.teardown) {
+      this.test.teardown(this);
+    }
+    if (this.waitTimeout !== null) {
+      timer.clearTimeout(this.waitTimeout);
+      this.waitTimeout = null;
+    }
+    // Do not leave any callback set when calling to `waitUntil`
+    this.waitUntilCallback = null;
+    if (this.test.passed == 0 && this.test.failed == 0) {
+      this._logTestFailed("empty test");
+      if ("testMessage" in this.console) {
+        this.console.testMessage(false, false, this.test.name, "Empty test");
       }
-      if (this.waitTimeout !== null) {
-        timer.clearTimeout(this.waitTimeout);
-        this.waitTimeout = null;
+      else {
+        this.console.error("fail:", "Empty test")
+      }
+      this.failed++;
+      this.test.failed++;
+    }
+
+    let wins = windows(null, { includePrivate: true });
+    let winPromises = wins.map(win =>  {
+      let { promise, resolve } = defer();
+      if (["interactive", "complete"].indexOf(win.document.readyState) >= 0) {
+        resolve()
       }
-      // Do not leave any callback set when calling to `waitUntil`
-      this.waitUntilCallback = null;
-      if (this.test.passed == 0 && this.test.failed == 0) {
-        this._logTestFailed("empty test");
-        if ("testMessage" in this.console) {
-          this.console.testMessage(false, false, this.test.name, "Empty test");
+      else {
+        win.addEventListener("DOMContentLoaded", function onLoad() {
+          win.removeEventListener("DOMContentLoaded", onLoad, false);
+          resolve();
+        }, false);
+      }
+      return promise;
+    });
+
+    PromiseDebugging.flushUncaughtErrors();
+
+    return all(winPromises).then(() => {
+      let browserWins = wins.filter(isBrowser);
+      let tabs = browserWins.reduce((tabs, window) => tabs.concat(getTabs(window)), []);
+
+      if (wins.length != 1 || getInnerId(wins[0]) !== runnerWindows.get(this))
+        this.fail("Should not be any unexpected windows open");
+
+      let hasMoreTabsOpen = browserWins.length && tabs.length != 1;
+      if (hasMoreTabsOpen)
+        this.fail("Should not be any unexpected tabs open");
+
+      if (hasMoreTabsOpen || wins.length != 1) {
+        console.log("Windows open:");
+        for (let win of wins) {
+          if (isBrowser(win)) {
+            tabs = getTabs(win);
+            console.log(win.location + " - " + tabs.map(getURI).join(", "));
+          }
+          else {
+            console.log(win.location);
+          }
         }
-        else {
-          this.console.error("fail:", "Empty test")
-        }
-        this.failed++;
-        this.test.failed++;
       }
 
-      let wins = windows(null, { includePrivate: true });
-      let winPromises = wins.map(win =>  {
-        let { promise, resolve } = defer();
-        if (["interactive", "complete"].indexOf(win.document.readyState) >= 0) {
-          resolve()
-        }
-        else {
-          win.addEventListener("DOMContentLoaded", function onLoad() {
-            win.removeEventListener("DOMContentLoaded", onLoad, false);
-            resolve();
-          }, false);
-        }
-        return promise;
+      return null;
+    }).
+    then(cleanUI).
+    then(() => {
+      this.testRunSummary.push({
+        name: this.test.name,
+        passed: this.test.passed,
+        failed: this.test.failed,
+        errors: [error for (error in this.test.errors)].join(", ")
       });
 
-      PromiseDebugging.flushUncaughtErrors();
-
-      all(winPromises).then(_ => {
-        let tabs = [];
-        for (let win of wins.filter(isBrowser)) {
-          for (let tab of getTabs(win)) {
-            tabs.push(tab);
-          }
-        }
-        let leftover = tabs.slice(1);
-
-        if (wins.length != 1 || getInnerId(wins[0]) !== runnerWindows.get(this))
-          this.fail("Should not be any unexpected windows open");
-        if (tabs.length != 1)
-          this.fail("Should not be any unexpected tabs open");
-        if (tabs.length != 1 || wins.length != 1) {
-          console.log("Windows open:");
-          for (let win of wins) {
-            if (isBrowser(win)) {
-              tabs = getTabs(win);
-              console.log(win.location + " - " + tabs.map(getURI).join(", "));
-            }
-            else {
-              console.log(win.location);
-            }
-          }
-        }
-
-        leftover.forEach(closeTab);
-
-        this.testRunSummary.push({
-          name: this.test.name,
-          passed: this.test.passed,
-          failed: this.test.failed,
-          errors: [error for (error in this.test.errors)].join(", ")
-        });
-
-        if (this.onDone !== null) {
-          let onDone = this.onDone;
-          this.onDone = null;
-          timer.setTimeout(_ => onDone(this), 0);
-        }
-      });
-    }
+      if (this.onDone !== null) {
+        let onDone = this.onDone;
+        this.onDone = null;
+        timer.setTimeout(_ => onDone(this));
+      }
+    }).
+    catch(e => console.exception(e));
   },
 
   // Set of assertion functions to wait for an assertion to become true
   // These functions take the same arguments as the TestRunner.assert* methods.
   waitUntil: function waitUntil() {
     return this._waitUntil(this.assert, arguments);
   },
 
--- a/addon-sdk/source/lib/sdk/deprecated/window-utils.js
+++ b/addon-sdk/source/lib/sdk/deprecated/window-utils.js
@@ -11,26 +11,26 @@ const { Cc, Ci } = require('chrome');
 const { EventEmitter } = require('../deprecated/events');
 const { Trait } = require('../deprecated/traits');
 const { when } = require('../system/unload');
 const events = require('../system/events');
 const { getInnerId, getOuterId, windows, isDocumentLoaded, isBrowser,
         getMostRecentBrowserWindow, getMostRecentWindow } = require('../window/utils');
 const errors = require('../deprecated/errors');
 const { deprecateFunction } = require('../util/deprecate');
-const { ignoreWindow, isGlobalPBSupported } = require('sdk/private-browsing/utils');
+const { ignoreWindow } = require('sdk/private-browsing/utils');
 const { isPrivateBrowsingSupported } = require('../self');
 
 const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1'].
                        getService(Ci.nsIWindowWatcher);
 const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
                         getService(Ci.nsIAppShellService);
 
 // Bug 834961: ignore private windows when they are not supported
-function getWindows() windows(null, { includePrivate: isPrivateBrowsingSupported || isGlobalPBSupported });
+function getWindows() windows(null, { includePrivate: isPrivateBrowsingSupported });
 
 /**
  * An iterator for XUL windows currently in the application.
  *
  * @return A generator that yields XUL windows exposing the
  *         nsIDOMWindow interface.
  */
 function windowIterator() {
--- a/addon-sdk/source/lib/sdk/io/buffer.js
+++ b/addon-sdk/source/lib/sdk/io/buffer.js
@@ -132,16 +132,17 @@ Buffer.concat = function(list, length) {
 
   return buffer;
 };
 
 // Node buffer is very much like Uint8Array although it has bunch of methods
 // that typically can be used in combination with `DataView` while preserving
 // access by index. Since in SDK each module has it's own set of bult-ins it
 // ok to patch ours to make it nodejs Buffer compatible.
+const Uint8ArraySet = Uint8Array.prototype.set
 Buffer.prototype = Uint8Array.prototype;
 Object.defineProperties(Buffer.prototype, {
   parent: {
     get: function() { return parents.get(this, undefined); }
   },
   view: {
     get: function () {
       let view = views.get(this, undefined);
@@ -199,17 +200,17 @@ Object.defineProperties(Buffer.prototype
         let remainingTarget = targetLength - offset;
         let remainingSource = length - start;
         if (remainingSource <= remainingTarget)
           end = length;
         else
           end = start + remainingTarget;
       }
 
-      Uint8Array.set(target, this.subarray(start, end), offset);
+      Uint8ArraySet.call(target, this.subarray(start, end), offset);
       return end - start;
     }
   },
   slice: {
     value: function(start, end) {
       let length = this.length;
       start = ~~start;
       end = end != null ? end : length;
@@ -262,17 +263,17 @@ Object.defineProperties(Buffer.prototype
       if (length == null || length + offset > this.length)
         length = this.length - offset;
 
       let buffer = new TextEncoder(encoding).encode(string);
       let result = Math.min(buffer.length, length);
       if (buffer.length !== length)
         buffer = buffer.subarray(0, length);
 
-      Uint8Array.set(this, buffer, offset);
+      Uint8ArraySet.call(this, buffer, offset);
       return result;
     }
   },
   fill: {
     value: function fill(value, start, end) {
       let length = this.length;
       value = value || 0;
       start = start || 0;
--- a/addon-sdk/source/lib/sdk/io/fs.js
+++ b/addon-sdk/source/lib/sdk/io/fs.js
@@ -642,36 +642,36 @@ exports.closeSync = closeSync;
  * given to the completion callback.
  */
 let close = Async(closeSync);
 exports.close = close;
 
 /**
  * Synchronous open(2).
  */
-function openSync(path, flags, mode) {
-  let [ fd, flags_, mode_, file ] =
-      [ { path: path }, Flags(flags), Mode(mode), nsILocalFile(path) ];
+function openSync(aPath, aFlag, aMode) {
+  let [ fd, flags, mode, file ] =
+      [ { path: aPath }, Flags(aFlag), Mode(aMode), nsILocalFile(aPath) ];
 
   nsIFile(fd, file);
 
   // If trying to open file for just read that does not exists
   // need to throw exception as node does.
-  if (!file.exists() && !isWritable(flags_))
-    throw FSError("open", "ENOENT", 34, path);
+  if (!file.exists() && !isWritable(flags))
+    throw FSError("open", "ENOENT", 34, aPath);
 
   // If we want to open file in read mode we initialize input stream.
-  if (isReadable(flags_)) {
-    let input = FileInputStream(file, flags_, mode_, DEFER_OPEN);
+  if (isReadable(flags)) {
+    let input = FileInputStream(file, flags, mode, DEFER_OPEN);
     nsIFileInputStream(fd, input);
   }
 
   // If we want to open file in write mode we initialize output stream for it.
-  if (isWritable(flags_)) {
-    let output = FileOutputStream(file, flags_, mode_, DEFER_OPEN);
+  if (isWritable(flags)) {
+    let output = FileOutputStream(file, flags, mode, DEFER_OPEN);
     nsIFileOutputStream(fd, output);
   }
 
   return fd;
 }
 exports.openSync = openSync;
 /**
  * Asynchronous file open. See open(2). Flags can be
@@ -817,17 +817,18 @@ function readFile(path, encoding, callba
       callback(error);
     });
     readStream.on("end", function onEnd() {
       // Note: Need to destroy before invoking a callback
       // so that file descriptor is released.
       readStream.destroy();
       callback(null, buffer);
     });
-  } catch (error) {
+  }
+  catch (error) {
     setTimeout(callback, 0, error);
   }
 };
 exports.readFile = readFile;
 
 /**
  * Synchronous version of `fs.readFile`. Returns the contents of the path.
  * If encoding is specified then this function returns a string.
--- a/addon-sdk/source/lib/sdk/panel/window.js
+++ b/addon-sdk/source/lib/sdk/panel/window.js
@@ -10,22 +10,21 @@ module.metadata = {
   'engines': {
     'Firefox': '*'
   }
 };
 
 const { getMostRecentBrowserWindow, windows: getWindows } = require('../window/utils');
 const { ignoreWindow } = require('../private-browsing/utils');
 const { isPrivateBrowsingSupported } = require('../self');
-const { isGlobalPBSupported } = require('../private-browsing/utils');
 
 function getWindow(anchor) {
   let window;
   let windows = getWindows("navigator:browser", {
-    includePrivate: isPrivateBrowsingSupported || isGlobalPBSupported
+    includePrivate: isPrivateBrowsingSupported
   });
 
   if (anchor) {
     let anchorWindow = anchor.ownerDocument.defaultView.top;
     let anchorDocument = anchorWindow.document;
 
     // loop thru supported windows
     for (let enumWindow of windows) {
--- a/addon-sdk/source/lib/sdk/private-browsing.js
+++ b/addon-sdk/source/lib/sdk/private-browsing.js
@@ -2,48 +2,11 @@
  * 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';
 
 module.metadata = {
   "stability": "stable"
 };
 
-const { setMode, getMode, on: onStateChange, isPrivate } = require('./private-browsing/utils');
-const { emit, on, once, off } = require('./event/core');
-const { when: unload } = require('./system/unload');
-const { deprecateFunction, deprecateEvent } = require('./util/deprecate');
-
-onStateChange('start', function onStart() {
-  emit(exports, 'start');
-});
-
-onStateChange('stop', function onStop() {
-  emit(exports, 'stop');
-});
-
-Object.defineProperty(exports, "isActive", {
-  get: deprecateFunction(getMode, 'require("private-browsing").isActive is deprecated.')
-});
-
-exports.activate = function activate() setMode(true);
-exports.deactivate = function deactivate() setMode(false);
-
-exports.on = deprecateEvents(on.bind(null, exports));
-exports.once = deprecateEvents(once.bind(null, exports));
-exports.removeListener = deprecateEvents(function removeListener(type, listener) {
-  // Note: We can't just bind `off` as we do it for other methods cause skipping
-  // a listener argument will remove all listeners for the given event type
-  // causing misbehavior. This way we make sure all arguments are passed.
-  off(exports, type, listener);
-});
+const { isPrivate } = require('./private-browsing/utils');
 
 exports.isPrivate = isPrivate;
-
-function deprecateEvents(func) deprecateEvent(
-  func,
-   'The require("sdk/private-browsing") module\'s "start" and "stop" events ' +
-   'are deprecated.',
-  ['start', 'stop']
-);
-
-// Make sure listeners are cleaned up.
-unload(function() off(exports));
--- a/addon-sdk/source/lib/sdk/private-browsing/utils.js
+++ b/addon-sdk/source/lib/sdk/private-browsing/utils.js
@@ -3,115 +3,52 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 'use strict';
 
 module.metadata = {
   "stability": "unstable"
 };
 
 const { Cc, Ci, Cu } = require('chrome');
-const { defer } = require('../lang/functional');
-const { emit, on, once, off } = require('../event/core');
-const { when: unload } = require('../system/unload');
-const events = require('../system/events');
-const { deprecateFunction } = require('../util/deprecate');
-const { isOneOf, is, satisfiesVersion, version } = require('../system/xul-app');
+const { is } = require('../system/xul-app');
 const { isWindowPrivate } = require('../window/utils');
 const { isPrivateBrowsingSupported } = require('../self');
 const { dispatcher } = require("../util/dispatcher");
 
-let deferredEmit = defer(emit);
-let pbService;
 let PrivateBrowsingUtils;
 
 // Private browsing is only supported in Fx
-if (isOneOf(['Firefox', 'Fennec'])) {
-  // get the nsIPrivateBrowsingService if it exists
-  try {
-    pbService = Cc["@mozilla.org/privatebrowsing;1"].
-                getService(Ci.nsIPrivateBrowsingService);
+try {
+  PrivateBrowsingUtils = Cu.import('resource://gre/modules/PrivateBrowsingUtils.jsm', {}).PrivateBrowsingUtils;
+}
+catch (e) {}
 
-    // a dummy service exists for the moment (Fx20 atleast), but will be removed eventually
-    // ie: the service will exist, but it won't do anything and the global private browing
-    //     feature is not really active.  See Bug 818800 and Bug 826037
-    if (!('privateBrowsingEnabled' in pbService))
-      pbService = undefined;
-  }
-  catch(e) { /* Private Browsing Service has been removed (Bug 818800) */ }
-
-  try {
-    PrivateBrowsingUtils = Cu.import('resource://gre/modules/PrivateBrowsingUtils.jsm', {}).PrivateBrowsingUtils;
-  }
-  catch(e) { /* if this file DNE then an error will be thrown */ }
-}
-
-// checks that global private browsing is implemented
-let isGlobalPBSupported = exports.isGlobalPBSupported = !!pbService && is('Firefox');
+exports.isGlobalPBSupported = false;
 
 // checks that per-window private browsing is implemented
 let isWindowPBSupported = exports.isWindowPBSupported =
-                          !pbService && !!PrivateBrowsingUtils && is('Firefox');
+                          !!PrivateBrowsingUtils && is('Firefox');
 
 // checks that per-tab private browsing is implemented
 let isTabPBSupported = exports.isTabPBSupported =
-                       !pbService && !!PrivateBrowsingUtils && is('Fennec') && satisfiesVersion(version, '>=20.0*');
+                       !!PrivateBrowsingUtils && is('Fennec');
 
 function isPermanentPrivateBrowsing() {
  return !!(PrivateBrowsingUtils && PrivateBrowsingUtils.permanentPrivateBrowsing);
 }
 exports.isPermanentPrivateBrowsing = isPermanentPrivateBrowsing;
 
 function ignoreWindow(window) {
-  return !isPrivateBrowsingSupported && isWindowPrivate(window) && !isGlobalPBSupported;
+  return !isPrivateBrowsingSupported && isWindowPrivate(window);
 }
 exports.ignoreWindow = ignoreWindow;
 
-function onChange() {
-  // Emit event with in next turn of event loop.
-  deferredEmit(exports, pbService.privateBrowsingEnabled ? 'start' : 'stop');
-}
-
-// Currently, only Firefox implements the private browsing service.
-if (isGlobalPBSupported) {
-  // set up an observer for private browsing switches.
-  events.on('private-browsing-transition-complete', onChange);
-}
-
-// We toggle private browsing mode asynchronously in order to work around
-// bug 659629.  Since private browsing transitions are asynchronous
-// anyway, this doesn't significantly change the behavior of the API.
-let setMode = defer(function setMode(value) {
-  value = !!value;  // Cast to boolean.
-
-  // default
-  return pbService && (pbService.privateBrowsingEnabled = value);
-});
-exports.setMode = deprecateFunction(
-  setMode,
-  'require("sdk/private-browsing").activate and ' +
-  'require("sdk/private-browsing").deactivate ' +
-  'are deprecated.'
-);
-
 let getMode = function getMode(chromeWin) {
-  if (chromeWin !== undefined && isWindowPrivate(chromeWin))
-    return true;
-
-  // default
-  return isGlobalPrivateBrowsing();
+  return (chromeWin !== undefined && isWindowPrivate(chromeWin));
 };
 exports.getMode = getMode;
 
-function isGlobalPrivateBrowsing() {
-  return pbService ? pbService.privateBrowsingEnabled : false;
-}
-
 const isPrivate = dispatcher("isPrivate");
 isPrivate.when(isPermanentPrivateBrowsing, _ => true);
 isPrivate.when(x => x instanceof Ci.nsIDOMWindow, isWindowPrivate);
 isPrivate.when(x => Ci.nsIPrivateBrowsingChannel && x instanceof Ci.nsIPrivateBrowsingChannel, x => x.isChannelPrivate);
-isPrivate.define(isGlobalPrivateBrowsing);
+isPrivate.define(() => false);
 exports.isPrivate = isPrivate;
-
-exports.on = on.bind(null, exports);
-
-// Make sure listeners are cleaned up.
-unload(function() off(exports));
--- a/addon-sdk/source/lib/sdk/system.js
+++ b/addon-sdk/source/lib/sdk/system.js
@@ -1,22 +1,22 @@
 /* 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';
 
 module.metadata = {
   "stability": "unstable"
 };
 
 const { Cc, Ci, CC } = require('chrome');
 const options = require('@loader/options');
 const file = require('./io/file');
 const runtime = require("./system/runtime");
+const { when: unload } = require("./system/unload");
 
 const appStartup = Cc['@mozilla.org/toolkit/app-startup;1'].
                    getService(Ci.nsIAppStartup);
 const appInfo = Cc["@mozilla.org/xre/app-info;1"].
                 getService(Ci.nsIXULAppInfo);
 const directoryService = Cc['@mozilla.org/file/directory_service;1'].
                          getService(Ci.nsIProperties);
 
@@ -56,29 +56,40 @@ exports.env = require('./system/environm
 let forcedExit = false;
 exports.exit = function exit(code) {
   if (forcedExit) {
     // a forced exit was already tried
     // NOTE: exit(0) is called twice sometimes (ex when using cfx testaddons)
     return;
   }
 
-  // This is used by 'cfx' to find out exit code.
-  if ('resultFile' in options && options.resultFile) {
+  let resultsFile = 'resultFile' in options && options.resultFile;
+  function unloader() {
+    // This is used by 'cfx' to find out exit code.
     let mode = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE;
     let stream = openFile(options.resultFile, mode);
     let status = code ? 'FAIL' : 'OK';
     stream.write(status, status.length);
     stream.flush();
     stream.close();
   }
 
   if (code == 0) {
     forcedExit = true;
   }
+
+  // Bug 856999: Prevent automatic kill of Firefox when running tests
+  if (options.noQuit) {
+    if (resultsFile) {
+      unload(unloader);
+    }
+    return;
+  }
+
+  unloader();
   appStartup.quit(code ? E_ATTEMPT : E_FORCE);
 };
 
 // Adapter for nodejs's stdout & stderr:
 // http://nodejs.org/api/process.html#process_process_stdout
 let stdout = Object.freeze({ write: dump, end: dump });
 exports.stdout = stdout;
 exports.stderr = stdout;
@@ -104,17 +115,17 @@ exports.pathFor = function pathFor(id) {
 
 /**
  * What platform you're running on (all lower case string).
  * For possible values see:
  * https://developer.mozilla.org/en/OS_TARGET
  */
 exports.platform = runtime.OS.toLowerCase();
 
-const [, architecture, compiler] = runtime.XPCOMABI ? 
+const [, architecture, compiler] = runtime.XPCOMABI ?
                                    runtime.XPCOMABI.match(/^([^-]*)-(.*)$/) :
                                    [, null, null];
 
 /**
  * What processor architecture you're running on:
  * `'arm', 'ia32', or 'x64'`.
  */
 exports.architecture = architecture;
--- a/addon-sdk/source/lib/sdk/tabs/utils.js
+++ b/addon-sdk/source/lib/sdk/tabs/utils.js
@@ -10,20 +10,19 @@ module.metadata = {
 
 // NOTE: This file should only deal with xul/native tabs
 
 
 const { Ci } = require('chrome');
 const { defer } = require("../lang/functional");
 const { windows, isBrowser } = require('../window/utils');
 const { isPrivateBrowsingSupported } = require('../self');
-const { isGlobalPBSupported } = require('../private-browsing/utils');
 
 // Bug 834961: ignore private windows when they are not supported
-function getWindows() windows(null, { includePrivate: isPrivateBrowsingSupported || isGlobalPBSupported });
+function getWindows() windows(null, { includePrivate: isPrivateBrowsingSupported });
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 // Define predicate functions that can be used to detech weather
 // we deal with fennec tabs or firefox tabs.
 
 // Predicate to detect whether tab is XUL "Tab" node.
 const isXULTab = tab =>
--- a/addon-sdk/source/lib/sdk/test/utils.js
+++ b/addon-sdk/source/lib/sdk/test/utils.js
@@ -1,20 +1,22 @@
 /* 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';
 
 module.metadata = {
   'stability': 'unstable'
 };
 
 const { defer } = require('../core/promise');
 const { setInterval, clearInterval } = require('../timers');
+const { getTabs, closeTab } = require("../tabs/utils");
+const { windows: getWindows } = require("../window/utils");
+const { close: closeWindow } = require("../window/helpers");
 
 function getTestNames (exports)
   Object.keys(exports).filter(name => /^test/.test(name))
 
 function isTestAsync (fn) fn.length > 1
 function isHelperAsync (fn) fn.length > 2
 
 /*
@@ -102,8 +104,24 @@ function waitUntil (predicate, delay) {
   let interval = setInterval(() => {
     if (!predicate()) return;
     clearInterval(interval);
     resolve();
   }, delay || 10);
   return promise;
 }
 exports.waitUntil = waitUntil;
+
+let cleanUI = function cleanUI() {
+  let { promise, resolve } = defer();
+
+  let windows = getWindows(null, { includePrivate: true });
+  if (windows.length > 1) {
+    return closeWindow(windows[1]).then(cleanUI);
+  }
+
+  getTabs(windows[0]).slice(1).forEach(closeTab);
+
+  resolve();
+
+  return promise;
+}
+exports.cleanUI = cleanUI;
--- a/addon-sdk/source/lib/sdk/util/match-pattern.js
+++ b/addon-sdk/source/lib/sdk/util/match-pattern.js
@@ -1,36 +1,30 @@
 /* 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";
 
 module.metadata = {
   "stability": "unstable"
 };
 
 const { URL } = require('../url');
 const cache = {};
 
 function MatchPattern(pattern) {
   if (cache[pattern]) return cache[pattern];
 
   if (typeof pattern.test == "function") {
-
     // For compatibility with -moz-document rules, we require the RegExp's
     // global, ignoreCase, and multiline flags to be set to false.
     if (pattern.global) {
       throw new Error("A RegExp match pattern cannot be set to `global` " +
                       "(i.e. //g).");
     }
-    if (pattern.ignoreCase) {
-      throw new Error("A RegExp match pattern cannot be set to `ignoreCase` " +
-                      "(i.e. //i).");
-    }
     if (pattern.multiline) {
       throw new Error("A RegExp match pattern cannot be set to `multiline` " +
                       "(i.e. //m).");
     }
 
     this.regexp = pattern;
   }
   else {
@@ -66,55 +60,54 @@ function MatchPattern(pattern) {
       }
     }
   }
 
   cache[pattern] = this;
 }
 
 MatchPattern.prototype = {
-
   test: function MatchPattern_test(urlStr) {
     try {
       var url = URL(urlStr);
     }
     catch (err) {
       return false;
     }
 
     // Test the URL against a RegExp pattern.  For compatibility with
     // -moz-document rules, we require the RegExp to match the entire URL,
     // so we not only test for a match, we also make sure the matched string
     // is the entire URL string.
     //
     // Assuming most URLs don't match most match patterns, we call `test` for
     // speed when determining whether or not the URL matches, then call `exec`
     // for the small subset that match to make sure the entire URL matches.
-    //
     if (this.regexp && this.regexp.test(urlStr) &&
         this.regexp.exec(urlStr)[0] == urlStr)
       return true;
 
     if (this.anyWebPage && /^(https?|ftp)$/.test(url.scheme))
       return true;
+
     if (this.exactURL && this.exactURL == urlStr)
       return true;
 
     // Tests the urlStr against domain and check if
     // wildcard submitted (*.domain.com), it only allows
     // subdomains (sub.domain.com) or from the root (http://domain.com)
     // and reject non-matching domains (otherdomain.com)
     // bug 856913
     if (this.domain && url.host &&
          (url.host === this.domain ||
           url.host.slice(-this.domain.length - 1) === "." + this.domain))
       return true;
+
     if (this.urlPrefix && 0 == urlStr.indexOf(this.urlPrefix))
       return true;
 
     return false;
   },
 
   toString: function () '[object MatchPattern]'
-
 };
 
 exports.MatchPattern = MatchPattern;
--- a/addon-sdk/source/lib/sdk/windows/dom.js
+++ b/addon-sdk/source/lib/sdk/windows/dom.js
@@ -1,16 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 'use strict';
 
 const { Trait } = require('../deprecated/traits');
-const { isWindowPrivate, getWindowTitle } = require('../window/utils');
-const { deprecateUsage } = require('../util/deprecate');
+const { getWindowTitle } = require('../window/utils');
 
 module.metadata = {
   "stability": "unstable"
 };
 
 const WindowDom = Trait.compose({
   _window: Trait.required,
   get title() {
@@ -20,18 +19,11 @@ const WindowDom = Trait.compose({
     let window = this._window;
     if (window) window.close();
     return this._public;
   },
   activate: function activate() {
     let window = this._window;
     if (window) window.focus();
     return this._public;
-  },
-  get isPrivateBrowsing() {
-    deprecateUsage('`browserWindow.isPrivateBrowsing` is deprecated, please ' +
-                   'consider using ' +
-                   '`require("sdk/private-browsing").isPrivate(browserWindow)` ' +
-                   'instead.');
-    return isWindowPrivate(this._window);
   }
 });
 exports.WindowDom = WindowDom;
--- a/addon-sdk/source/lib/toolkit/loader.js
+++ b/addon-sdk/source/lib/toolkit/loader.js
@@ -224,16 +224,20 @@ const Sandbox = iced(function Sandbox(op
     wantGlobalProperties: 'wantGlobalProperties' in options ?
                           options.wantGlobalProperties : [],
     sandboxPrototype: 'prototype' in options ? options.prototype : {},
     invisibleToDebugger: 'invisibleToDebugger' in options ?
                          options.invisibleToDebugger : false,
     metadata: 'metadata' in options ? options.metadata : {}
   };
 
+  if (options.metadata && options.metadata.addonID) {
+    options.addonId = options.metadata.addonID;
+  }
+
   let sandbox = Cu.Sandbox(options.principal, options);
 
   // Each sandbox at creation gets set of own properties that will be shadowing
   // ones from it's prototype. We override delete such `sandbox` properties
   // to avoid shadowing.
   delete sandbox.Iterator;
   delete sandbox.Components;
   delete sandbox.importFunction;
@@ -388,98 +392,134 @@ const resolve = iced(function resolve(id
   // We need to ensure the resolution still has the root
   if (isRelative(base))
     resolved = './' + resolved;
 
   return resolved;
 });
 exports.resolve = resolve;
 
+function fileExists(uri) {
+  let url = NetUtil.newURI(uri);
+
+  switch (url.scheme) {
+    case "jar":
+      let jarfile = url.QueryInterface(Ci.nsIJARURI).JARFile;
+
+      // Don't support nested JARs for now
+      if (!(jarfile instanceof Ci.nsIFileURL))
+        return false;
+
+      let zipcache = Cc["@mozilla.org/libjar/zip-reader-cache;1"].
+                     getService(Ci.nsIZipReaderCache);
+      let zipreader = zipcache.getZip(jarfile.file);
+      return zipreader.hasEntry(jarfile.JAREntry);
+
+    case "file":
+      return url.QueryInterface(Ci.nsIFileURL).file.exists();
+
+    case "chrome":
+      let registry = Cc["@mozilla.org/chrome/chrome-registry;1"].
+                     getService(Ci.nsIChromeRegistry)
+      return fileExists(ChromeRegistry.convertChromeURL(url).spec);
+
+    case "resource":
+      let handler = Cc["@mozilla.org/network/protocol;1?name=resource"].
+                    getService(Ci.nsIResProtocolHandler);
+      let resolved;
+      try {
+        resolved = handler.resolveURI(url);
+      }
+      catch (e) {
+        // Resource protocol handler throws for unknown mappings
+        return false;
+      }
+      return fileExists(resolved);
+
+    default:
+      // Don't handle other URI schemes for now
+      return false;
+  }
+}
+
 // Node-style module lookup
 // Takes an id and path and attempts to load a file using node's resolving
 // algorithm.
 // `id` should already be resolved relatively at this point.
 // http://nodejs.org/api/modules.html#modules_all_together
 const nodeResolve = iced(function nodeResolve(id, requirer, { rootURI }) {
   // Resolve again
   id = exports.resolve(id, requirer);
 
   // we assume that extensions are correct, i.e., a directory doesnt't have '.js'
   // and a js file isn't named 'file.json.js'
   let fullId = join(rootURI, id);
   let resolvedPath;
 
-  if ((resolvedPath = loadAsFile(fullId)))
+  if ((resolvedPath = findFile(fullId)))
     return stripBase(rootURI, resolvedPath);
 
   if ((resolvedPath = loadAsDirectory(fullId)))
     return stripBase(rootURI, resolvedPath);
 
   // If manifest has dependencies, attempt to look up node modules
   // in the `dependencies` list
   let dirs = getNodeModulePaths(dirname(join(rootURI, requirer))).map(dir => join(dir, id));
   for (let i = 0; i < dirs.length; i++) {
-    if ((resolvedPath = loadAsFile(dirs[i])))
+    if ((resolvedPath = findFile(dirs[i])))
       return stripBase(rootURI, resolvedPath);
 
     if ((resolvedPath = loadAsDirectory(dirs[i])))
       return stripBase(rootURI, resolvedPath);
   }
 
   // We would not find lookup for things like `sdk/tabs`, as that's part of
   // the alias mapping. If during `generateMap`, the runtime lookup resolves
   // with `resolveURI` -- if during runtime, then `resolve` will throw.
   return void 0;
 });
 exports.nodeResolve = nodeResolve;
 
-// Attempts to load `path` and then `path.js`
+// Attempts to find `path` and then `path.js`
 // Returns `path` with valid file, or `undefined` otherwise
-function loadAsFile (path) {
-  let found;
-
+function findFile (path) {
   // As per node's loader spec,
   // we first should try and load 'path' (with no extension)
   // before trying 'path.js'. We will not support this feature
   // due to performance, but may add it if necessary for adoption.
-  try {
-    // Append '.js' to path name unless it's another support filetype
-    path = normalizeExt(path);
-    readURI(path);
-    found = path;
-  } catch (e) {}
 
-  return found;
+  // Append '.js' to path name unless it's another support filetype
+  path = normalizeExt(path);
+  if (fileExists(path))
+    return path;
+
+  return null;
 }
 
 // Attempts to load `path/package.json`'s `main` entry,
 // followed by `path/index.js`, or `undefined` otherwise
 function loadAsDirectory (path) {
   try {
     // If `path/package.json` exists, parse the `main` entry
     // and attempt to load that
-    let main = getManifestMain(JSON.parse(readURI(path + '/package.json')));
-    if (main != null) {
-      let tmpPath = join(path, main);
-      let found = loadAsFile(tmpPath);
-      if (found)
-        return found
+    if (fileExists(path + '/package.json')) {
+      let main = getManifestMain(JSON.parse(readURI(path + '/package.json')));
+      if (main != null) {
+        let tmpPath = join(path, main);
+        let found = findFile(tmpPath);
+        if (found)
+          return found
+      }
     }
-    try {
-      let tmpPath = path + '/index.js';
-      readURI(tmpPath);
-      return tmpPath;
-    } catch (e) {}
-  } catch (e) {
-    try {
-      let tmpPath = path + '/index.js';
-      readURI(tmpPath);
-      return tmpPath;
-    } catch (e) {}
-  }
+  } catch (e) { }
+
+  let tmpPath = path + '/index.js';
+  if (fileExists(tmpPath))
+    return tmpPath;
+
   return void 0;
 }
 
 // From `resolve` module
 // https://github.com/substack/node-resolve/blob/master/lib/node-modules-paths.js
 function getNodeModulePaths (start) {
   // Configurable in node -- do we need this to be configurable?
   let moduleDir = 'node_modules';
--- a/addon-sdk/source/python-lib/cuddlefish/__init__.py
+++ b/addon-sdk/source/python-lib/cuddlefish/__init__.py
@@ -147,17 +147,17 @@ parser_groups = (
 
     ("Experimental Command-Specific Options", [
         (("-a", "--app",), dict(dest="app",
                                 help=("app to run: firefox (default), fennec, "
                                       "fennec-on-device, xulrunner or "
                                       "thunderbird"),
                                 metavar=None,
                                 type="choice",
-                                choices=["firefox", "fennec",
+                                choices=["firefox",
                                          "fennec-on-device", "thunderbird",
                                          "xulrunner"],
                                 default="firefox",
                                 cmds=['test', 'run', 'testex', 'testpkgs',
                                       'testall'])),
         (("-o", "--overload-modules",), dict(dest="overload_modules",
                                      help=("Overload JS modules integrated into"
                                            " Firefox with the one from your SDK"
@@ -184,16 +184,22 @@ parser_groups = (
                                      help=("Instead of launching the "
                                            "application, just show the command "
                                            "for doing so.  Use this to launch "
                                            "the application in a debugger like "
                                            "gdb."),
                                      action="store_true",
                                      default=False,
                                      cmds=['run', 'test'])),
+        (("", "--no-quit",), dict(dest="no_quit",
+                                     help=("Prevent from killing Firefox when"
+                                           "running tests"),
+                                     action="store_true",
+                                     default=False,
+                                     cmds=['run', 'test'])),
         (("", "--no-strip-xpi",), dict(dest="no_strip_xpi",
                                     help="retain unused modules in XPI",
                                     action="store_true",
                                     default=False,
                                     cmds=['xpi'])),
         (("", "--force-mobile",), dict(dest="enable_mobile",
                                     help="Force compatibility with Firefox Mobile",
                                     action="store_true",
@@ -659,17 +665,17 @@ def run(arguments=sys.argv[1:], target_c
         for k, v in packaging.load_json_file(options.manifest_overload).items():
             target_cfg[k] = v
 
     # At this point, we're either building an XPI or running Jetpack code in
     # a Mozilla application (which includes running tests).
 
     use_main = False
     inherited_options = ['verbose', 'enable_e10s', 'parseable', 'check_memory',
-                         'abort_on_missing']
+                         'no_quit', 'abort_on_missing']
     enforce_timeouts = False
 
     if command == "xpi":
         use_main = True
     elif command == "test":
         if 'tests' not in target_cfg:
             target_cfg['tests'] = []
         inherited_options.extend(['iterations', 'filter', 'profileMemory',
@@ -850,17 +856,18 @@ def run(arguments=sys.argv[1:], target_c
 
     from cuddlefish.rdf import gen_manifest, RDFUpdate
 
     manifest_rdf = gen_manifest(template_root_dir=app_extension_dir,
                                 target_cfg=target_cfg,
                                 jid=jid,
                                 update_url=options.update_url,
                                 bootstrap=True,
-                                enable_mobile=options.enable_mobile)
+                                enable_mobile=options.enable_mobile,
+                                harness_options=harness_options)
 
     if command == "xpi" and options.update_link:
         if not options.update_link.startswith("https"):
             raise optparse.OptionValueError("--update-link must start with 'https': %s" % options.update_link)
         rdf_name = UPDATE_RDF_FILENAME % target_cfg.name
         print >>stdout, "Exporting update description to %s." % rdf_name
         update = RDFUpdate()
         update.add(manifest_rdf, options.update_link)
@@ -931,16 +938,17 @@ def run(arguments=sys.argv[1:], target_c
                              verbose=options.verbose,
                              parseable=options.parseable,
                              enforce_timeouts=enforce_timeouts,
                              logfile=options.logfile,
                              addons=options.addons,
                              args=options.cmdargs,
                              extra_environment=extra_environment,
                              norun=options.no_run,
+                             noquit=options.no_quit,
                              used_files=used_files,
                              enable_mobile=options.enable_mobile,
                              mobile_app_name=options.mobile_app_name,
                              env_root=env_root,
                              is_running_tests=(command == "test"),
                              overload_modules=options.overload_modules,
                              bundle_sdk=options.bundle_sdk,
                              pkgdir=options.pkgdir,
--- a/addon-sdk/source/python-lib/cuddlefish/mobile-utils/bootstrap.js
+++ b/addon-sdk/source/python-lib/cuddlefish/mobile-utils/bootstrap.js
@@ -1,55 +1,48 @@
 /* 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";
 
-const Cc = Components.classes;
-const Ci = Components.interfaces;
 const Cu = Components.utils;
-const Cr = Components.results;
 
-Components.utils.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 
-const DEBUG = false;
-
-let log = DEBUG ? dump : function (){};
-
+let { log } = console;
 
 function startup(data, reason) {
   // This code allow to make all stdIO work
   try {
-    Components.utils.import("resource://gre/modules/ctypes.jsm");
+    Cu.import("resource://gre/modules/ctypes.jsm");
     let libdvm = ctypes.open("libdvm.so");
     let dvmStdioConverterStartup;
     // Starting with Android ICS, dalvik uses C++.
     // So that the symbol isn't a simple C one
     try {
       dvmStdioConverterStartup = libdvm.declare("_Z24dvmStdioConverterStartupv", ctypes.default_abi, ctypes.bool);
     }
     catch(e) {
       // Otherwise, before ICS, it was a pure C library
       dvmStdioConverterStartup = libdvm.declare("dvmStdioConverterStartup", ctypes.default_abi, ctypes.void_t);
     }
     dvmStdioConverterStartup();
-    log("MU: console redirected to adb logcat.\n");
+    log("MU: console redirected to adb logcat.");
   } catch(e) {
     Cu.reportError("MU: unable to execute jsctype hack: "+e);
   }
 
   try {
     let QuitObserver = {
       observe: function (aSubject, aTopic, aData) {
         Services.obs.removeObserver(QuitObserver, "quit-application");
         dump("MU: APPLICATION-QUIT\n");
       }
     };
     Services.obs.addObserver(QuitObserver, "quit-application", false);
-    log("MU: ready to watch firefox exit.\n");
+    log("MU: ready to watch firefox exit.");
   } catch(e) {
-    log("MU: unable to register quit-application observer: " + e + "\n");
+    log("MU: unable to register quit-application observer: " + e);
   }
 }
 
 function install() {}
 function shutdown() {}
--- a/addon-sdk/source/python-lib/cuddlefish/prefs.py
+++ b/addon-sdk/source/python-lib/cuddlefish/prefs.py
@@ -80,17 +80,17 @@ DEFAULT_FIREFOX_PREFS = {
 
     # From:
     # http://hg.mozilla.org/mozilla-central/file/1dd81c324ac7/build/automation.py.in#l388
     # Make url-classifier updates so rare that they won't affect tests.
     'urlclassifier.updateinterval' : 172800,
     # Point the url-classifier to a nonexistent local URL for fast failures.
     'browser.safebrowsing.provider.0.gethashURL' : 'http://localhost/safebrowsing-dummy/gethash',
     'browser.safebrowsing.provider.0.updateURL' : 'http://localhost/safebrowsing-dummy/update',
-    }
+}
 
 # When launching a temporary new Thunderbird profile, use these preferences.
 # Note that these were taken from:
 # http://mxr.mozilla.org/comm-central/source/mail/test/mozmill/runtest.py
 DEFAULT_THUNDERBIRD_PREFS = {
     # say no to slow script warnings
     'dom.max_chrome_script_run_time': 200,
     'dom.max_script_run_time': 0,
@@ -134,9 +134,14 @@ DEFAULT_THUNDERBIRD_PREFS = {
     'mail.server.server2.type' :  "pop3",
     'mail.server.server2.userName' :  "tinderbox",
     'mail.smtp.defaultserver' :  "smtp1",
     'mail.smtpserver.smtp1.hostname' :  "tinderbox",
     'mail.smtpserver.smtp1.username' :  "tinderbox",
     'mail.smtpservers' :  "smtp1",
     'mail.startup.enabledMailCheckOnce' :  True,
     'mailnews.start_page_override.mstone' :  "ignore",
-    }
+}
+
+DEFAULT_TEST_PREFS = {
+    'general.useragent.locale': "en-US",
+    'intl.locale.matchOS': "en-US"
+}
--- a/addon-sdk/source/python-lib/cuddlefish/rdf.py
+++ b/addon-sdk/source/python-lib/cuddlefish/rdf.py
@@ -1,33 +1,36 @@
 # 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/.
 
 import os
 import xml.dom.minidom
 import StringIO
+import codecs
+import glob
 
 RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 EM_NS = "http://www.mozilla.org/2004/em-rdf#"
 
 class RDF(object):
     def __str__(self):
         # real files have an .encoding attribute and use it when you
         # write() unicode into them: they read()/write() unicode and
         # put encoded bytes in the backend file. StringIO objects
         # read()/write() unicode and put unicode in the backing store,
         # so we must encode the output of getvalue() to get a
         # bytestring. (cStringIO objects are weirder: they effectively
         # have .encoding hardwired to "ascii" and put only bytes in
         # the backing store, so we can't use them here).
         #
-        # The encoding= argument to dom.writexml() merely sets the XML header's
-        # encoding= attribute. It still writes unencoded unicode to the output file,
-        # so we have to encode it for real afterwards.
+        # The encoding= argument to dom.writexml() merely sets the
+        # XML header's encoding= attribute. It still writes unencoded
+        # unicode to the output file, so we have to encode it for
+        # real afterwards.
         #
         # Also see: https://bugzilla.mozilla.org/show_bug.cgi?id=567660
 
         buf = StringIO.StringIO()
         self.dom.writexml(buf, encoding="utf-8")
         return buf.getvalue().encode('utf-8')
 
 class RDFUpdate(RDF):
@@ -107,51 +110,94 @@ class RDFManifest(RDF):
         if not elements:
             return True
         else:
             for i in elements:
                 i.parentNode.removeChild(i);
 
         return True;
 
-def gen_manifest(template_root_dir, target_cfg, jid,
+    def add_node(self, node):
+        top =  self.dom.documentElement.getElementsByTagName("Description")[0];
+        top.appendChild(node)
+
+
+def gen_manifest(template_root_dir, target_cfg, jid, harness_options={},
                  update_url=None, bootstrap=True, enable_mobile=False):
     install_rdf = os.path.join(template_root_dir, "install.rdf")
     manifest = RDFManifest(install_rdf)
     dom = manifest.dom
 
     manifest.set("em:id", jid)
     manifest.set("em:version",
                  target_cfg.get('version', '1.0'))
+
+    if "locale" in harness_options:
+        # addon_title       -> <em:name>
+        # addon_author      -> <em:creator>
+        # addon_description -> <em:description>
+        # addon_homepageURL -> <em:homepageURL>
+        localizable_in = ["title", "author", "description", "homepage"]
+        localized_out  = ["name", "creator", "description", "homepageURL"]
+        for lang in harness_options["locale"]:
+            desc = dom.createElement("Description")
+
+            for value_in in localizable_in:
+                key_in = "extensions." + target_cfg.get("id", "") + "." + value_in
+                tag_out = localized_out[localizable_in.index(value_in)]
+
+                if key_in in harness_options["locale"][lang]:
+                    elem = dom.createElement("em:" + tag_out)
+                    elem_value = harness_options["locale"][lang][key_in]
+                    elem.appendChild(dom.createTextNode(elem_value))
+                    desc.appendChild(elem)
+
+            # Don't add language if no localizeable field was localized
+            if desc.hasChildNodes():
+                locale = dom.createElement("em:locale")
+                locale.appendChild(dom.createTextNode(lang))
+                desc.appendChild(locale)
+
+                localized = dom.createElement("em:localized")
+                localized.appendChild(desc)
+                manifest.add_node(localized)
+
     manifest.set("em:name",
                  target_cfg.get('title', target_cfg.get('fullName', target_cfg['name'])))
     manifest.set("em:description",
                  target_cfg.get("description", ""))
     manifest.set("em:creator",
                  target_cfg.get("author", ""))
+
+    if target_cfg.get("homepage"):
+        manifest.set("em:homepageURL", target_cfg.get("homepage"))
+    else:
+        manifest.remove("em:homepageURL")
+
     manifest.set("em:bootstrap", str(bootstrap).lower())
+
     # XPIs remain packed by default, but package.json can override that. The
     # RDF format accepts "true" as True, anything else as False. We expect
     # booleans in the .json file, not strings.
     manifest.set("em:unpack", "true" if target_cfg.get("unpack") else "false")
 
     for translator in target_cfg.get("translators", [ ]):
         elem = dom.createElement("em:translator");
         elem.appendChild(dom.createTextNode(translator))
-        dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem)
+        manifest.add_node(elem)
 
     for developer in target_cfg.get("developers", [ ]):
         elem = dom.createElement("em:developer");
         elem.appendChild(dom.createTextNode(developer))
         dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem)
 
     for contributor in target_cfg.get("contributors", [ ]):
         elem = dom.createElement("em:contributor");
         elem.appendChild(dom.createTextNode(contributor))
-        dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem)
+        manifest.add_node(elem)
 
     if update_url:
         manifest.set("em:updateURL", update_url)
     else:
         manifest.remove("em:updateURL")
 
     if target_cfg.get("preferences"):
         manifest.set("em:optionsType", "2")
@@ -164,38 +210,33 @@ def gen_manifest(template_root_dir, targ
         if (os.path.exists(os.path.join(template_root_dir, "options.xul"))):
             manifest.remove("em:optionsURL")
     else:
         manifest.remove("em:optionsType")
         manifest.remove("em:optionsURL")
 
     if enable_mobile:
         target_app = dom.createElement("em:targetApplication")
-        dom.documentElement.getElementsByTagName("Description")[0].appendChild(target_app)
+        manifest.add_node(target_app)
 
         ta_desc = dom.createElement("Description")
         target_app.appendChild(ta_desc)
 
         elem = dom.createElement("em:id")
         elem.appendChild(dom.createTextNode("{aa3c5121-dab2-40e2-81ca-7ea25febc110}"))
         ta_desc.appendChild(elem)
 
         elem = dom.createElement("em:minVersion")
         elem.appendChild(dom.createTextNode("26.0"))
         ta_desc.appendChild(elem)
 
         elem = dom.createElement("em:maxVersion")
         elem.appendChild(dom.createTextNode("30.0a1"))
         ta_desc.appendChild(elem)
 
-    if target_cfg.get("homepage"):
-        manifest.set("em:homepageURL", target_cfg.get("homepage"))
-    else:
-        manifest.remove("em:homepageURL")
-
     return manifest
 
 if __name__ == "__main__":
     print "Running smoke test."
     root = os.path.join(os.path.dirname(__file__), '../../app-extension')
     manifest = gen_manifest(root, {'name': 'test extension'},
                             'fakeid', 'http://foo.com/update.rdf')
     update = RDFUpdate()
--- a/addon-sdk/source/python-lib/cuddlefish/runner.py
+++ b/addon-sdk/source/python-lib/cuddlefish/runner.py
@@ -13,16 +13,17 @@ import re
 import shutil
 
 import mozrunner
 from cuddlefish.prefs import DEFAULT_COMMON_PREFS
 from cuddlefish.prefs import DEFAULT_FIREFOX_PREFS
 from cuddlefish.prefs import DEFAULT_THUNDERBIRD_PREFS
 from cuddlefish.prefs import DEFAULT_FENNEC_PREFS
 from cuddlefish.prefs import DEFAULT_NO_CONNECTIONS_PREFS
+from cuddlefish.prefs import DEFAULT_TEST_PREFS
 
 # Used to remove noise from ADB output
 CLEANUP_ADB = re.compile(r'^(I|E)/(stdout|stderr|GeckoConsole)\s*\(\s*\d+\):\s*(.*)$')
 # Used to filter only messages send by `console` module
 FILTER_ONLY_CONSOLE_FROM_ADB = re.compile(r'^I/(stdout|stderr)\s*\(\s*\d+\):\s*((info|warning|error|debug): .*)$')
 
 # Used to detect the currently running test
 PARSEABLE_TEST_NAME = re.compile(r'TEST-START \| ([^\n]+)\n')
@@ -99,40 +100,16 @@ def check_output(*popenargs, **kwargs):
         raise CalledProcessError(retcode, cmd, output=output)
     return output
 
 
 class FennecProfile(mozrunner.Profile):
     preferences = {}
     names = ['fennec']
 
-class FennecRunner(mozrunner.Runner):
-    profile_class = FennecProfile
-
-    names = ['fennec']
-
-    __DARWIN_PATH = '/Applications/Fennec.app/Contents/MacOS/fennec'
-
-    def __init__(self, binary=None, **kwargs):
-        if sys.platform == 'darwin' and binary and binary.endswith('.app'):
-            # Assume it's a Fennec app dir.
-            binary = os.path.join(binary, 'Contents/MacOS/fennec')
-
-        self.__real_binary = binary
-
-        mozrunner.Runner.__init__(self, **kwargs)
-
-    def find_binary(self):
-        if not self.__real_binary:
-            if sys.platform == 'darwin':
-                if os.path.exists(self.__DARWIN_PATH):
-                    return self.__DARWIN_PATH
-            self.__real_binary = mozrunner.Runner.find_binary(self)
-        return self.__real_binary
-
 FENNEC_REMOTE_PATH = '/mnt/sdcard/jetpack-profile'
 
 class RemoteFennecRunner(mozrunner.Runner):
     profile_class = FennecProfile
 
     names = ['fennec']
 
     _INTENT_PREFIX = 'org.mozilla.'
@@ -162,21 +139,21 @@ class RemoteFennecRunner(mozrunner.Runne
         subprocess.call([self._adb_path, "shell",
                         "setprop log.redirect-stdio false"])
 
         # Android apps are launched by their "intent" name,
         # Automatically detect already installed firefox by using `pm` program
         # or use name given as cfx `--mobile-app` argument.
         intents = self.getIntentNames()
         if not intents:
-            raise ValueError("Unable to found any Firefox "
+            raise ValueError("Unable to find any Firefox "
                              "application on your device.")
         elif mobile_app_name:
             if not mobile_app_name in intents:
-                raise ValueError("Unable to found Firefox application "
+                raise ValueError("Unable to find Firefox application "
                                  "with intent name '%s'\n"
                                  "Available ones are: %s" %
                                  (mobile_app_name, ", ".join(intents)))
             self._intent_name = self._INTENT_PREFIX + mobile_app_name
         else:
             if "firefox" in intents:
                 self._intent_name = self._INTENT_PREFIX + "firefox"
             elif "firefox_beta" in intents:
@@ -407,17 +384,17 @@ def set_overloaded_modules(env_root, app
         else:
             preferences[prefName] = desktop_file_scheme + \
                 path.replace("\\", "/") + "/"
 
 def run_app(harness_root_dir, manifest_rdf, harness_options,
             app_type, binary=None, profiledir=None, verbose=False,
             parseable=False, enforce_timeouts=False,
             logfile=None, addons=None, args=None, extra_environment={},
-            norun=None,
+            norun=None, noquit=None,
             used_files=None, enable_mobile=False,
             mobile_app_name=None,
             env_root=None,
             is_running_tests=False,
             overload_modules=False,
             bundle_sdk=True,
             pkgdir="",
             enable_e10s=False,
@@ -428,41 +405,40 @@ def run_app(harness_root_dir, manifest_r
     if addons is None:
         addons = []
     else:
         addons = list(addons)
 
     cmdargs = []
     preferences = dict(DEFAULT_COMMON_PREFS)
 
+    if is_running_tests:
+      preferences.update(DEFAULT_TEST_PREFS)
+
     if no_connections:
       preferences.update(DEFAULT_NO_CONNECTIONS_PREFS)
 
     if enable_e10s:
         preferences['browser.tabs.remote.autostart'] = True
 
     # For now, only allow running on Mobile with --force-mobile argument
-    if app_type in ["fennec", "fennec-on-device"] and not enable_mobile:
+    if app_type in ["fennec-on-device"] and not enable_mobile:
         print """
   WARNING: Firefox Mobile support is still experimental.
   If you would like to run an addon on this platform, use --force-mobile flag:
 
     cfx --force-mobile"""
         return 0
 
     if app_type == "fennec-on-device":
         profile_class = FennecProfile
         preferences.update(DEFAULT_FENNEC_PREFS)
         runner_class = RemoteFennecRunner
         # We pass the intent name through command arguments
         cmdargs.append(mobile_app_name)
-    elif enable_mobile or app_type == "fennec":
-        profile_class = FennecProfile
-        preferences.update(DEFAULT_FENNEC_PREFS)
-        runner_class = FennecRunner
     elif app_type == "xulrunner":
         profile_class = XulrunnerAppProfile
         runner_class = XulrunnerAppRunner
         cmdargs.append(os.path.join(harness_root_dir, 'application.ini'))
     elif app_type == "firefox":
         profile_class = mozrunner.FirefoxProfile
         preferences.update(DEFAULT_FIREFOX_PREFS)
         runner_class = mozrunner.FirefoxRunner
@@ -758,17 +734,18 @@ def run_app(harness_root_dir, manifest_r
             if enforce_timeouts:
                 if time.time() - last_output_time > OUTPUT_TIMEOUT:
                     raise Timeout("Test output exceeded timeout (%ds)." %
                                   OUTPUT_TIMEOUT, test_name, parseable)
                 if time.time() - starttime > RUN_TIMEOUT:
                     raise Timeout("Test run exceeded timeout (%ds)." %
                                   RUN_TIMEOUT, test_name, parseable)
     except:
-        runner.stop()
+        if not noquit:
+            runner.stop()
         raise
     else:
         runner.wait(10)
         # double kill - hack for bugs 942111, 1006043..
         try:
             runner.stop()
         except:
             pass
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-661083-files/packages/noLocalization/locale/en-GB.properties
@@ -0,0 +1,1 @@
+some_key = some_value
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-661083-files/packages/noLocalization/locale/en-US.properties
@@ -0,0 +1,1 @@
+some_key = some_value
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-661083-files/packages/noLocalization/package.json
@@ -0,0 +1,11 @@
+{
+    "name": "nolocalization",
+    "id": "jid1-TBF7sWF7yT6xSQ",
+    "license": "MPL 2.0",
+    "version": "0.1",
+
+    "title": "tilteUnlocalized",
+    "author": "authorUnlocalized",
+    "description": "descriptionUnlocalized",
+    "homepage": "homepageUnlocalized"
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-661083-files/packages/twoLanguages/locale/en-GB.properties
@@ -0,0 +1,4 @@
+extensions.jid1-TBF7sWF7yT6xSQ@jetpack.title = title-en-GB
+extensions.jid1-TBF7sWF7yT6xSQ@jetpack.author = author-en-GB
+extensions.jid1-TBF7sWF7yT6xSQ@jetpack.description = description-en-GB
+extensions.jid1-TBF7sWF7yT6xSQ@jetpack.homepage = homepage-en-GB
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-661083-files/packages/twoLanguages/locale/en-US.properties
@@ -0,0 +1,4 @@
+extensions.jid1-TBF7sWF7yT6xSQ@jetpack.title = title-en-US
+extensions.jid1-TBF7sWF7yT6xSQ@jetpack.author = author-en-US
+extensions.jid1-TBF7sWF7yT6xSQ@jetpack.description = description-en-US
+extensions.jid1-TBF7sWF7yT6xSQ@jetpack.homepage = homepage-en-US
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-661083-files/packages/twoLanguages/package.json
@@ -0,0 +1,11 @@
+{
+    "name": "nolocalization",
+    "id": "jid1-TBF7sWF7yT6xSQ@jetpack",
+    "license": "MPL 2.0",
+    "version": "0.1",
+
+    "title": "tilteUnlocalized",
+    "author": "authorUnlocalized",
+    "description": "descriptionUnlocalized",
+    "homepage": "homepageUnlocalized"
+}
old mode 100755
new mode 100644
--- a/addon-sdk/source/python-lib/cuddlefish/tests/test_rdf.py
+++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_rdf.py
@@ -1,17 +1,17 @@
 # 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/.
 
 import unittest
 import xml.dom.minidom
 import os.path
 
-from cuddlefish import rdf, packaging
+from cuddlefish import rdf, packaging, property_parser
 
 parent = os.path.dirname
 test_dir = parent(os.path.abspath(__file__))
 template_dir = os.path.join(parent(test_dir), "../../app-extension")
 
 class RDFTests(unittest.TestCase):
     def testBug567660(self):
         obj = rdf.RDF()
@@ -44,11 +44,57 @@ class RDFTests(unittest.TestCase):
     def testTitle(self):
         basedir = os.path.join(test_dir, 'bug-906359-files')
         for n in ['title', 'fullName', 'none']:
             cfg = packaging.get_config_in_dir(os.path.join(basedir, n))
             m = rdf.gen_manifest(template_dir, cfg, jid='JID')
             self.failUnlessEqual(m.get('em:name'), 'a long ' + n)
             self.failUnlessIn('<em:name>a long ' + n + '</em:name>', str(m), n)
 
+    def testLocalization(self):
+        # addon_title       -> <em:name>
+        # addon_author      -> <em:creator>
+        # addon_description -> <em:description>
+        # addon_homepageURL -> <em:homepageURL>
+        localizable_in = ["title", "author", "description", "homepage"]
+        localized_out  = ["name", "creator", "description", "homepageURL"]
+
+        basedir = os.path.join(test_dir, "bug-661083-files/packages")
+        for n in ["noLocalization", "twoLanguages"]:
+            harness_options = { "locale" : {} }
+            pkgdir = os.path.join(basedir, n)
+            localedir = os.path.join(pkgdir, "locale")
+            files = os.listdir(localedir)
+
+            for file in files:
+                filepath = os.path.join(localedir, file)
+                if os.path.isfile(filepath) and file.endswith(".properties"):
+                    language = file[:-len(".properties")]
+                    try:
+                        parsed_file = property_parser.parse_file(filepath)
+                    except property_parser.MalformedLocaleFileError, msg:
+                        self.fail(msg)
+
+                    harness_options["locale"][language] = parsed_file
+
+            cfg = packaging.get_config_in_dir(pkgdir)
+            m = rdf.gen_manifest(template_dir, cfg, 'JID', harness_options)
+
+            if n == "noLocalization":
+                self.failIf("<em:locale>" in str(m))
+                continue
+
+            for lang in harness_options["locale"]:
+                rdfstr = str(m)
+                node = "<em:locale>" + lang + "</em:locale>"
+                self.failUnlessIn(node, rdfstr, n)
+
+                for value_in in localizable_in:
+                    key_in = "extensions." + m.get('em:id') + "." + value_in
+                    tag_out = localized_out[localizable_in.index(value_in)]
+                    if key_in in harness_options["locale"][lang]:
+                        # E.g. "<em:creator>author-en-US</em:creator>"
+                        node = "<em:" + tag_out + ">" + value_in + "-" + lang \
+                            + "</em:" + tag_out + ">"
+                        self.failUnlessIn(node , rdfstr, n)
 
 if __name__ == '__main__':
     unittest.main()
--- a/addon-sdk/source/test/addons/author-email/main.js
+++ b/addon-sdk/source/test/addons/author-email/main.js
@@ -1,17 +1,14 @@
 /* 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';
 
-const { Cu } = require('chrome');
-const self = require('sdk/self');
-const { AddonManager } = Cu.import('resource://gre/modules/AddonManager.jsm', {});
+const { id } = require('sdk/self');
+const { getAddonByID } = require('sdk/addon/manager');
 
-exports.testContributors = function(assert, done) {
-  AddonManager.getAddonByID(self.id, (addon) => {
-    assert.equal(addon.creator.name, 'test <test@mozilla.com>', '< and > characters work');
-    done();
-  });
+exports.testContributors = function*(assert) {
+  let addon = yield getAddonByID(id);
+  assert.equal(addon.creator.name, 'test <test@mozilla.com>', '< and > characters work');
 }
 
 require('sdk/test/runner').runTestsFromModule(module);
--- a/addon-sdk/source/test/addons/chrome/main.js
+++ b/addon-sdk/source/test/addons/chrome/main.js
@@ -4,16 +4,17 @@
 'use strict'
 
 const { Cu, Cc, Ci } = require('chrome');
 const Request = require('sdk/request').Request;
 const { WindowTracker } = require('sdk/deprecated/window-utils');
 const { close, open } = require('sdk/window/helpers');
 const { data } = require('sdk/self');
 const { Panel } = require('sdk/panel');
+const { setTimeout } = require("sdk/timers")
 
 const XUL_URL = 'chrome://test/content/new-window.xul'
 
 const { Services } = Cu.import('resource://gre/modules/Services.jsm', {});
 const { NetUtil } = Cu.import('resource://gre/modules/NetUtil.jsm', {});
 
 exports.testChromeSkin = function(assert, done) {
   let skinURL = 'chrome://test/skin/style.css';
@@ -73,20 +74,21 @@ exports.testChromeInPanel = function(ass
     contentScriptWhen: 'start',
     contentScriptFile: data.url('panel.js')
   });
   panel.once('show', _ => {
     assert.pass('panel shown');
     panel.port.once('echo', _ => {
       assert.pass('got echo');
       panel.once('hide', _ => {
+        assert.pass('panel hidden');
         panel.destroy();
         assert.pass('panel is destroyed');
         done();
       });
-      panel.hide();
+      setTimeout(() => panel.hide());
     });
     panel.port.emit('echo');
   });
   panel.show();
 }
 
 require('sdk/test/runner').runTestsFromModule(module);
--- a/addon-sdk/source/test/addons/contributors/main.js
+++ b/addon-sdk/source/test/addons/contributors/main.js
@@ -1,23 +1,19 @@
 /* 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';
 
-const { Cu } = require('chrome');
-const self = require('sdk/self');
-const { AddonManager } = Cu.import('resource://gre/modules/AddonManager.jsm', {});
+const { id } = require('sdk/self');
+const { getAddonByID } = require('sdk/addon/manager');
 
-exports.testContributors = function(assert, done) {
-  AddonManager.getAddonByID(self.id, function(addon) {
-    let count = 0;
-    addon.contributors.forEach(function({ name }) {
-      count++;
-      assert.equal(name, count == 1 ? 'A' : 'B', 'The contributors keys are correct');
-    });
-    assert.equal(count, 2, 'The key count is correct');
-    assert.equal(addon.contributors.length, 2, 'The key length is correct');
-    done();
+exports.testContributors = function*(assert) {
+  let addon = yield getAddonByID(id);
+  let count = 0;
+  addon.contributors.forEach(({ name }) => {
+    assert.equal(name, ++count == 1 ? 'A' : 'B', 'The contributors keys are correct');
   });
+  assert.equal(count, 2, 'The key count is correct');
+  assert.equal(addon.contributors.length, 2, 'The key length is correct');
 }
 
 require('sdk/test/runner').runTestsFromModule(module);
--- a/addon-sdk/source/test/addons/curly-id/lib/main.js
+++ b/addon-sdk/source/test/addons/curly-id/lib/main.js
@@ -1,43 +1,34 @@
 /* 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';
 
 const simple = require('sdk/simple-prefs');
 const service = require('sdk/preferences/service');
 const { id, preferencesBranch } = require('sdk/self');
-const { AddonManager } = require('chrome').Cu.import('resource://gre/modules/AddonManager.jsm');
+const { getAddonByID } = require('sdk/addon/manager');
 
 exports.testCurlyID = function(assert) {
   assert.equal(id, '{34a1eae1-c20a-464f-9b0e-000000000000}', 'curly ID is curly');
-
   assert.equal(simple.prefs.test13, 26, 'test13 is 26');
 
   simple.prefs.test14 = '15';
   assert.equal(service.get('extensions.{34a1eae1-c20a-464f-9b0e-000000000000}.test14'), '15', 'test14 is 15');
-
   assert.equal(service.get('extensions.{34a1eae1-c20a-464f-9b0e-000000000000}.test14'), simple.prefs.test14, 'simple test14 also 15');
-
 }
 
 exports.testInvalidPreferencesBranch = function(assert) {
   assert.notEqual(preferencesBranch, 'invalid^branch*name', 'invalid preferences-branch value ignored');
-
   assert.equal(preferencesBranch, '{34a1eae1-c20a-464f-9b0e-000000000000}', 'preferences-branch is {34a1eae1-c20a-464f-9b0e-000000000000}');
-
 }
 
 // from `/test/test-self.js`, adapted to `sdk/test/assert` API
-exports.testSelfID = function(assert, done) {
-
+exports.testSelfID = function*(assert) {
   assert.equal(typeof(id), 'string', 'self.id is a string');
   assert.ok(id.length > 0, 'self.id not empty');
 
-  AddonManager.getAddonByID(id, function(addon) {
-    assert.ok(addon, 'found addon with self.id');
-    done();
-  });
-
+  let addon = yield getAddonByID(id);
+  assert.ok(addon, 'found addon with self.id');
 }
 
 require('sdk/test/runner').runTestsFromModule(module);
--- a/addon-sdk/source/test/addons/developers/main.js
+++ b/addon-sdk/source/test/addons/developers/main.js
@@ -1,23 +1,19 @@
 /* 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';
 
-const { Cu } = require('chrome');
 const { id } = require('sdk/self');
-const { AddonManager } = Cu.import('resource://gre/modules/AddonManager.jsm', {});
+const { getAddonByID } = require('sdk/addon/manager');
 
-exports.testDevelopers = function(assert, done) {
-  AddonManager.getAddonByID(id, (addon) => {
-    let count = 0;
-    addon.developers.forEach(({ name }) => {
-      count++;
-      assert.equal(name, count == 1 ? 'A' : 'B', 'The developers keys are correct');
-    });
-    assert.equal(count, 2, 'The key count is correct');
-    assert.equal(addon.developers.length, 2, 'The key length is correct');
-    done();
+exports.testDevelopers = function*(assert) {
+  let addon = yield getAddonByID(id);
+  let count = 0;
+  addon.developers.forEach(({ name }) => {
+    assert.equal(name, ++count == 1 ? 'A' : 'B', 'The developers keys are correct');
   });
+  assert.equal(count, 2, 'The key count is correct');
+  assert.equal(addon.developers.length, 2, 'The key length is correct');
 }
 
 require('sdk/test/runner').runTestsFromModule(module);
--- a/addon-sdk/source/test/addons/e10s-tabs/lib/private-browsing/helper.js
+++ b/addon-sdk/source/test/addons/e10s-tabs/lib/private-browsing/helper.js
@@ -34,25 +34,16 @@ function LoaderWithHookedConsole(module)
   let loader = Loader(module, globals);
 
   return {
     loader: loader,
     errors: errors
   }
 }
 
-function deactivate(callback) {
-  if (pbUtils.isGlobalPBSupported) {
-    if (callback)
-      pb.once('stop', callback);
-    pb.deactivate();
-  }
-}
-exports.deactivate = deactivate;
-
 exports.pb = pb;
 exports.pbUtils = pbUtils;
 exports.LoaderWithHookedConsole = LoaderWithHookedConsole;
 
 exports.openWebpage = function openWebpage(url, enablePrivate) {
   if (xulApp.is("Fennec")) {
     let chromeWindow = getMostRecentBrowserWindow();
     let rawTab = openTab(chromeWindow, url, {
--- a/addon-sdk/source/test/addons/e10s-tabs/lib/test-tab-utils.js
+++ b/addon-sdk/source/test/addons/e10s-tabs/lib/test-tab-utils.js
@@ -1,15 +1,14 @@
 'use strict';
 
 const { getTabs } = require('sdk/tabs/utils');
 const { isGlobalPBSupported, isWindowPBSupported, isTabPBSupported } = require('sdk/private-browsing/utils');
 const { browserWindows } = require('sdk/windows');
 const tabs = require('sdk/tabs');
-const { pb } = require('./private-browsing/helper');
 const { isPrivate } = require('sdk/private-browsing');
 const { openTab, closeTab, getTabContentWindow, getOwnerWindow } = require('sdk/tabs/utils');
 const { open, close } = require('sdk/window/helpers');
 const { windows } = require('sdk/window/utils');
 const { getMostRecentBrowserWindow } = require('sdk/window/utils');
 const { fromIterator } = require('sdk/util/array');
 
 if (isWindowPBSupported) {
--- a/addon-sdk/source/test/addons/jetpack-addon.ini
+++ b/addon-sdk/source/test/addons/jetpack-addon.ini
@@ -9,16 +9,17 @@
 [e10s.xpi]
 skip-if = true
 [e10s-tabs.xpi]
 skip-if = true
 [l10n.xpi]
 [l10n-properties.xpi]
 [layout-change.xpi]
 [main.xpi]
+[manifest-localized.xpi]
 [packaging.xpi]
 [packed.xpi]
 [page-mod-debugger-post.xpi]
 [page-mod-debugger-pre.xpi]
 [places.xpi]
 [predefined-id-with-at.xpi]
 [preferences-branch.xpi]
 [private-browsing-supported.xpi]
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/manifest-localized/locale/en-US.properties
@@ -0,0 +1,4 @@
+extensions.manifest-localized@jetpack.title = title-en
+extensions.manifest-localized@jetpack.author = author-en
+extensions.manifest-localized@jetpack.description = description-en
+extensions.manifest-localized@jetpack.homepage = homepage-en
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/manifest-localized/main.js
@@ -0,0 +1,17 @@
+/* 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";
+
+const { id } = require('sdk/self');
+const { getAddonByID } = require('sdk/addon/manager');
+
+exports["test add-on manifest was localized"] = function*(assert) {
+  let addon = yield getAddonByID(id);
+  assert.equal(addon.name, "title-en", "title was translated");
+  assert.equal(addon.description, "description-en", "description was translated");
+  assert.equal(addon.creator, "author-en", "author was translated");
+  assert.equal(addon.homepageURL, "homepage-en", "homepage was translated");
+};
+
+require("sdk/test/runner").runTestsFromModule(module);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/manifest-localized/package.json
@@ -0,0 +1,10 @@
+{
+  "name": "manifest-localized",
+  "id": "manifest-localized@jetpack",
+  "license": "MPL 2.0",
+  "version": "0.1",
+  "title": "Manifest Not Localized",
+  "author": "Manifest Not Localized",
+  "description": "Manifest Not Localized",
+  "homepage": "Manifest Not Localized"
+}
--- a/addon-sdk/source/test/addons/places/tests/test-places-bookmarks.js
+++ b/addon-sdk/source/test/addons/places/tests/test-places-bookmarks.js
@@ -10,18 +10,17 @@ module.metadata = {
 };
 
 const { Cc, Ci } = require('chrome');
 const { request } = require('sdk/addon/host');
 const { filter } = require('sdk/event/utils');
 const { on, off } = require('sdk/event/core');
 const { setTimeout } = require('sdk/timers');
 const { newURI } = require('sdk/url/utils');
-const { defer, all } = require('sdk/core/promise');
-const { defer: async } = require('sdk/lang/functional');
+const { defer, all, resolve } = require('sdk/core/promise');
 const { before, after } = require('sdk/test/utils');
 
 const {
   Bookmark, Group, Separator,
   save, search, remove,
   MENU, TOOLBAR, UNSORTED
 } = require('sdk/places/bookmarks');
 const {
@@ -36,16 +35,17 @@ const tagsrv = Cc['@mozilla.org/browser/
                     getService(Ci.nsITaggingService);
 
 exports.testDefaultFolders = function (assert) {
   var ids = [
     bmsrv.bookmarksMenuFolder,
     bmsrv.toolbarFolder,
     bmsrv.unfiledBookmarksFolder
   ];
+
   [MENU, TOOLBAR, UNSORTED].forEach(function (g, i) {
     assert.ok(g.id === ids[i], ' default group matches id');
   });
 };
 
 exports.testValidation = function (assert) {
   assert.throws(() => {
     Bookmark({ title: 'a title' });
@@ -316,40 +316,37 @@ exports.testAddingToExistingParent = fun
     return saveP([
       { title: 'moz4', url: 'http://moz4.com', type: 'bookmark', group: group },
       { title: 'moz5', url: 'http://moz5.com', type: 'bookmark', group: group }
     ]);
   }, assert.fail).then(data => {
     secondBatch = data;
     assert.equal(firstBatch[0].group.id, secondBatch[0].group.id,
       'successfully saved to the same parent');
-    done();
-  }, assert.fail);
+  }).then(done).catch(assert.fail);
 };
 
 exports.testUpdateParent = function (assert, done) {
   let group = { type: 'group', title: 'mozgroup' };
   saveP(group).then(item => {
     item[0].title = 'mozgroup-resave';
     return saveP(item[0]);
   }).then(item => {
     assert.equal(item[0].title, 'mozgroup-resave', 'group saved successfully');
-    done();
-  });
+  }).then(done).catch(assert.fail);
 };
 
 exports.testUpdateSeparator = function (assert, done) {
   let sep = [Separator(), Separator(), Separator()];
   saveP(sep).then(item => {
     item[0].index = 2;
     return saveP(item[0]);
   }).then(item => {
     assert.equal(item[0].index, 2, 'updated index of separator');
-    done();
-  });
+  }).then(done).catch(assert.fail);
 };
 
 exports.testPromisedSave = function (assert, done) {
   let topFolder = Group({ title: 'top', group: MENU });
   let midFolder = Group({ title: 'middle', group: topFolder });
   let bookmarks = [
     Bookmark({ title: 'moz1', url: 'http://moz1.com', group: midFolder}),
     Bookmark({ title: 'moz2', url: 'http://moz2.com', group: midFolder}),
@@ -364,50 +361,52 @@ exports.testPromisedSave = function (ass
     assert.equal(second.index, 1);
     assert.equal(third.index, 2);
     first.index = 3;
     return saveP(first);
   }).then(() => {
     assert.equal(bmsrv.getItemIndex(first.id), 2, 'properly moved bookmark');
     assert.equal(bmsrv.getItemIndex(second.id), 0, 'other bookmarks adjusted');
     assert.equal(bmsrv.getItemIndex(third.id), 1, 'other bookmarks adjusted');
-    done();
-  });
+  }).then(done).catch(assert.fail);
 };
 
 exports.testPromisedErrorSave = function (assert, done) {
   let bookmarks = [
     { title: 'moz1', url: 'http://moz1.com', type: 'bookmark'},
     { title: 'moz2', url: 'invalidurl', type: 'bookmark'},
     { title: 'moz3', url: 'http://moz3.com', type: 'bookmark'}
   ];
+
   saveP(bookmarks).then(invalidResolve, reason => {
     assert.ok(
       /The `url` property must be a valid URL/.test(reason),
       'Error event called with correct reason');
 
     bookmarks[1].url = 'http://moz2.com';
     return saveP(bookmarks);
-  }).then(res => {
-    return searchP({ query: 'moz' });
-  }).then(res => {
+  }).
+  then(res => searchP({ query: 'moz' })).
+  then(res => {
     assert.equal(res.length, 3, 'all 3 should be saved upon retry');
     res.map(item => assert.ok(/moz\d\.com/.test(item.url), 'correct item'));
     done();
-  }, invalidReject);
+  }, invalidReject).
+  catch(assert.fail);
 };
 
 exports.testMovingChildren = function (assert, done) {
   let topFolder = Group({ title: 'top', group: MENU });
   let midFolder = Group({ title: 'middle', group: topFolder });
   let bookmarks = [
     Bookmark({ title: 'moz1', url: 'http://moz1.com', group: midFolder}),
     Bookmark({ title: 'moz2', url: 'http://moz2.com', group: midFolder}),
     Bookmark({ title: 'moz3', url: 'http://moz3.com', group: midFolder})
   ];
+
   save(bookmarks).on('end', bms => {
     let first = bms.filter(b => b.title === 'moz1')[0];
     let second = bms.filter(b => b.title === 'moz2')[0];
     let third = bms.filter(b => b.title === 'moz3')[0];
     assert.equal(first.index, 0);
     assert.equal(second.index, 1);
     assert.equal(third.index, 2);
     /* When moving down in the same container we take
@@ -483,17 +482,17 @@ exports.testRemove = function (assert, d
       assert.pass('data event should be called');
       assert.ok(!res, 'response should be empty');
     }).on('end', () => {
       assert.throws(function () {
         bmsrv.getItemTitle(id);
       }, 'item should no longer exist');
       done();
     });
-  });
+  }).catch(assert.fail);
 };
 
 /*
  * Tests recursively removing children when removing a group
  */
 exports.testRemoveAllChildren = function (assert, done) {
   let topFolder = Group({ title: 'new', group: MENU });
   let midFolder = Group({ title: 'middle', group: topFolder });
@@ -519,17 +518,17 @@ exports.testRemoveAllChildren = function
 
 exports.testResolution = function (assert, done) {
   let firstSave, secondSave;
   createBookmarkItem().then((item) => {
     firstSave = item;
     assert.ok(item.updated, 'bookmark has updated time');
     item.title = 'my title';
     // Ensure delay so a different save time is set
-    return delayed(item);
+    return resolve(item);
   }).then(saveP)
   .then(items => {
     let item = items[0];
     secondSave = item;
     assert.ok(firstSave.updated < secondSave.updated, 'snapshots have different update times');
     firstSave.title = 'updated title';
     return saveP(firstSave, { resolve: (mine, theirs) => {
       assert.equal(mine.title, 'updated title', 'correct data for my object');
@@ -541,66 +540,66 @@ exports.testResolution = function (asser
       assert.equal(mine.toString(), '[object Object]', 'serialized objects');
       assert.equal(theirs.toString(), '[object Object]', 'serialized objects');
       mine.title = 'a new title';
       return mine;
     }});
   }).then((results) => {
     let result = results[0];
     assert.equal(result.title, 'a new title', 'resolve handles results');
-    done();
-  });
+  }).then(done).catch(assert.fail);;
 };
 
 /*
  * Same as the resolution test, but with the 'unsaved' snapshot
  */
 exports.testResolutionMapping = function (assert, done) {
   let bookmark = Bookmark({ title: 'moz', url: 'http://bookmarks4life.com/' });
   let saved;
+
   saveP(bookmark).then(data => {
     saved = data[0];
     saved.title = 'updated title';
     // Ensure a delay for different updated times
-    return delayed(saved);
-  }).then(saveP)
-  .then(() => {
+    return resolve(saved);
+  }).
+  then(saveP).
+  then(() => {
     bookmark.title = 'conflicting title';
     return saveP(bookmark, { resolve: (mine, theirs) => {
       assert.equal(mine.title, 'conflicting title', 'correct data for my object');
       assert.equal(theirs.title, 'updated title', 'correct data for their object');
       assert.equal(mine.url, theirs.url, 'other data is equal');
       assert.equal(mine.group, theirs.group, 'other data is equal');
       assert.ok(mine !== bookmark, 'instance is not passed in');
       assert.ok(theirs !== saved, 'instance is not passed in');
       assert.equal(mine.toString(), '[object Object]', 'serialized objects');
       assert.equal(theirs.toString(), '[object Object]', 'serialized objects');
       mine.title = 'a new title';
       return mine;
     }});
   }).then((results) => {
     let result = results[0];
     assert.equal(result.title, 'a new title', 'resolve handles results');
-    done();
-  });
+  }).then(done).catch(assert.fail);
 };
 
 exports.testUpdateTags = function (assert, done) {
   createBookmarkItem({ tags: ['spidermonkey'] }).then(bookmark => {
     bookmark.tags.add('jagermonkey');
     bookmark.tags.add('ionmonkey');
     bookmark.tags.delete('spidermonkey');
     save(bookmark).on('data', saved => {
       assert.equal(saved.tags.size, 2, 'should have 2 tags');
       assert.ok(saved.tags.has('jagermonkey'), 'should have added tag');
       assert.ok(saved.tags.has('ionmonkey'), 'should have added tag');
       assert.ok(!saved.tags.has('spidermonkey'), 'should not have removed tag');
       done();
     });
-  });
+  }).catch(assert.fail);
 };
 
 /*
  * View `createBookmarkTree` in `./places-helper.js` to see
  * expected tree construction
  */
 
 exports.testSearchByGroupSimple = function (assert, done) {
@@ -620,36 +619,34 @@ exports.testSearchByGroupSimple = functi
     });
   }).then(results => {
     let groups = results.filter(({type}) => type === 'group');
     assert.equal(groups.length, 1, 'returns one subfolder');
     assert.equal(results.length, 6,
       'returns all children bookmarks/folders');
     assert.ok(results.filter(({url}) => url === 'http://w3schools.com/'),
       'returns nested children');
-    done();
-  }).then(null, assert.fail);
+  }).then(done).catch(assert.fail);
 };
 
 exports.testSearchByGroupComplex = function (assert, done) {
   let mozgroup;
   createBookmarkTree().then(results => {
     mozgroup = results.filter(({title}) => title === 'mozgroup')[0];
     return searchP({ group: mozgroup, query: 'javascript' });
   }).then(results => {
     assert.equal(results.length, 1, 'only one javascript result under mozgroup');
     assert.equal(results[0].url, 'http://w3schools.com/', 'correct result');
     return searchP({ group: mozgroup, url: '*.mozilla.org' });
   }).then(results => {
     assert.equal(results.length, 2, 'expected results');
     assert.ok(
       !results.filter(({url}) => /developer.mozilla/.test(url)).length,
       'does not find results from other folders');
-    done();
-  }, assert.fail);
+  }).then(done).catch(assert.fail);
 };
 
 exports.testSearchEmitters = function (assert, done) {
   createBookmarkTree().then(() => {
     let count = 0;
     search({ tags: ['mozilla', 'firefox'] }).on('data', data => {
       assert.ok(/mozilla|firefox/.test(data.title), 'one of the correct items');
       assert.ok(data.tags.has('firefox'), 'has firefox tag');
@@ -661,17 +658,17 @@ exports.testSearchEmitters = function (a
       assert.equal(data.length, 3,
         'should return two bookmarks that have both mozilla AND firefox');
       assert.equal(data[0].title, 'mozilla.com', 'returns correct bookmark');
       assert.equal(data[1].title, 'mozilla.org', 'returns correct bookmark');
       assert.equal(data[2].title, 'firefox', 'returns correct bookmark');
       assert.equal(data[0] + '', '[object Bookmark]', 'returns bookmarks');
       done();
     });
-  });
+  }).catch(assert.fail);
 };
 
 exports.testSearchTags = function (assert, done) {
   createBookmarkTree().then(() => {
     // AND tags
     return searchP({ tags: ['mozilla', 'firefox'] });
   }).then(data => {
     assert.equal(data.length, 3,
@@ -680,18 +677,17 @@ exports.testSearchTags = function (asser
     assert.equal(data[1].title, 'mozilla.org', 'returns correct bookmark');
     assert.equal(data[2].title, 'firefox', 'returns correct bookmark');
     assert.equal(data[0] + '', '[object Bookmark]', 'returns bookmarks');
     return searchP([{tags: ['firefox']}, {tags: ['javascript']}]);
   }).then(data => {
     // OR tags
     assert.equal(data.length, 6,
       'should return all bookmarks with firefox OR javascript tag');
-    done();
-  });
+  }).then(done).catch(assert.fail);
 };
 
 /*
  * Tests 4 scenarios
  * '*.mozilla.com'
  * 'mozilla.com'
  * 'http://mozilla.com/'
  * 'http://mozilla.com/*'
@@ -719,19 +715,17 @@ exports.testSearchURL = function (assert
     assert.equal(data[0].url, 'http://mozilla.org/');
     assert.equal(data[1].url, 'http://mozilla.org/thunderbird/');
     return searchP([{ url: 'mozilla.org' }, { url: 'component.fm' }]);
   }).then(data => {
     assert.equal(data.length, 3, 'returns URLs that match EITHER query');
     assert.equal(data[0].url, 'http://mozilla.org/');
     assert.equal(data[1].url, 'http://mozilla.org/thunderbird/');
     assert.equal(data[2].url, 'http://component.fm/');
-  }).then(() => {
-    done();
-  });
+  }).then(done).catch(assert.fail);
 };
 
 /*
  * Searches url, title, tags
  */
 exports.testSearchQuery = function (assert, done) {
   createBookmarkTree().then(() => {
     return searchP({ query: 'thunder' });
@@ -747,19 +741,17 @@ exports.testSearchQuery = function (asse
     assert.equal(data[0].title, 'mozilla.com', 'query matches tag, url, or title');
     assert.equal(data[1].title, 'mozilla.org', 'query matches tag, url, or title');
     assert.equal(data[2].title, 'web audio components', 'query matches tag, url, or title');
     return searchP([{ query: 'moz', tags: ['javascript']}]);
   }).then(data => {
     assert.equal(data.length, 1);
     assert.equal(data[0].title, 'mdn',
       'only one item matches moz query AND has a javascript tag');
-  }).then(() => {
-    done();
-  });
+  }).then(done).catch(assert.fail);
 };
 
 /*
  * Test caching on bulk calls.
  * Each construction of a bookmark item snapshot results in
  * the recursive lookup of parent groups up to the root groups --
  * ensure that the appropriate instances equal each other, and no duplicate
  * fetches are called
@@ -799,37 +791,35 @@ exports.testCaching = function (assert, 
     count = 0;
     return searchP({ query: 'moz' });
   }).then(results => {
     // Should query for each bookmark (5) from the query (id -> data),
     // their parent during `construct` (1) and the root shouldn't
     // require a lookup
     assert.equal(count, 6, 'lookup occurs once for each item and parent');
     off(stream, 'data', handle);
-    done();
-  });
+  }).then(done).catch(assert.fail);
 
   function handle ({data}) count++
 };
 
 /*
  * Search Query Options
  */
 
 exports.testSearchCount = function (assert, done) {
   let max = 8;
   createBookmarkTree()
   .then(testCount(1))
   .then(testCount(2))
   .then(testCount(3))
   .then(testCount(5))
   .then(testCount(10))
-  .then(() => {
-    done();
-  });
+  .then(done)
+  .catch(assert.fail);;
 
   function testCount (n) {
     return function () {
       return searchP({}, { count: n }).then(results => {
         if (n > max) n = max;
         assert.equal(results.length, n,
           'count ' + n + ' returns ' + n + ' results');
       });
@@ -896,19 +886,17 @@ exports.testSearchSort = function (asser
     return searchP({}, { sort: 'lastModified' });
   }).then(results => {
     assert.equal(results[5].url, 'http://mozilla.com/webfwd/',
       'last modified should be last');
     return searchP({}, { sort: 'lastModified', descending: true });
   }).then(results => {
     assert.equal(results[0].url, 'http://mozilla.com/webfwd/',
       'last modified should be first');
-  }).then(() => {
-    done();
-  });
+  }).then(done).catch(assert.fail);;
 
   function checkOrder (results, nums) {
     assert.equal(results.length, nums.length, 'expected return count');
     for (let i = 0; i < nums.length; i++) {
       assert.equal(results[i].url, urls[nums[i]], 'successful order');
     }
   }
 };
@@ -921,45 +909,37 @@ exports.testSearchComplexQueryWithOption
     ], { sort: 'title' });
   }).then(results => {
     let expected = [
       'http://developer.mozilla.org/en-US/',
       'http://mozilla.org/'
     ];
     for (let i = 0; i < expected.length; i++)
       assert.equal(results[i].url, expected[i], 'correct ordering and item');
-    done();
-  });
+  }).then(done).catch(assert.fail);;
 };
 
 exports.testCheckSaveOrder = function (assert, done) {
   let group = Group({ title: 'mygroup' });
   let bookmarks = [
     Bookmark({ url: 'http://url1.com', title: 'url1', group: group }),
     Bookmark({ url: 'http://url2.com', title: 'url2', group: group }),
     Bookmark({ url: 'http://url3.com', title: 'url3', group: group }),
     Bookmark({ url: 'http://url4.com', title: 'url4', group: group }),
     Bookmark({ url: 'http://url5.com', title: 'url5', group: group })
   ];
   saveP(bookmarks).then(results => {
     for (let i = 0; i < bookmarks.length; i++)
       assert.equal(results[i].url, bookmarks[i].url,
         'correct ordering of bookmark results');
-    done();
-  });
+  }).then(done).catch(assert.fail);;
 };
 
 before(exports, (name, assert, done) => resetPlaces(done));
 after(exports, (name, assert, done) => resetPlaces(done));
 
 function saveP () {
   return promisedEmitter(save.apply(null, Array.slice(arguments)));
 }
 
 function searchP () {
   return promisedEmitter(search.apply(null, Array.slice(arguments)));
 }
-
-function delayed (value, ms) {
-  let { promise, resolve } = defer();
-  setTimeout(() => resolve(value), ms || 10);
-  return promise;
-}
--- a/addon-sdk/source/test/addons/places/tests/test-places-events.js
+++ b/addon-sdk/source/test/addons/places/tests/test-places-events.js
@@ -300,18 +300,26 @@ exports['test history-delete-url'] = fun
 exports['test history-page-changed'] = function (assert) {
   assert.pass('history-page-changed tested in test-places-favicons');
 };
 
 exports['test history-delete-visits'] = function (assert) {
   assert.pass('TODO test history-delete-visits');
 };
 
+// Bug 1060843
+// Wait a tick before finishing tests, as some bookmark activities require
+// completion of a result for events. For example, when creating a bookmark,
+// a `bookmark-item-added` event is fired, listened to by the first test here,
+// while constructing the bookmark item requires subsequent calls to that bookmark item.
+// If we destroy the underlying bookmark immediately, these calls will fail. 
+//
+// The places SDK abstraction around this alleviates it, but these are low level events.
+after(exports, (name, assert, done) => setTimeout(() => resetPlaces(done), 1));
 before(exports, (name, assert, done) => resetPlaces(done));
-after(exports, (name, assert, done) => resetPlaces(done));
 
 function saveP () {
   return promisedEmitter(save.apply(null, Array.slice(arguments)));
 }
 
 function makeCompleted (done, countTo) {
   let count = 0;
   countTo = countTo || 2;
--- a/addon-sdk/source/test/addons/predefined-id-with-at/lib/main.js
+++ b/addon-sdk/source/test/addons/predefined-id-with-at/lib/main.js
@@ -1,34 +1,32 @@
 /* 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';
 
 const { id, preferencesBranch } = require('sdk/self');
 const simple = require('sdk/simple-prefs');
 const service = require('sdk/preferences/service');
-const { AddonManager } = require('chrome').Cu.import('resource://gre/modules/AddonManager.jsm', {});
+const { getAddonByID } = require('sdk/addon/manager');
 
 const expected_id = 'predefined-id@test';
 
 exports.testExpectedID = function(assert) {
   assert.equal(id, expected_id, 'ID is as expected');
   assert.equal(preferencesBranch, expected_id, 'preferences-branch is ' + expected_id);
 
   assert.equal(simple.prefs.test, 5, 'test pref is 5');
 
   simple.prefs.test2 = '25';
   assert.equal(service.get('extensions.'+expected_id+'.test2'), '25', 'test pref is 25');
   assert.equal(service.get('extensions.'+expected_id+'.test2'), simple.prefs.test2, 'test pref is 25');
 }
 
-exports.testSelfID = function(assert, done) {
+exports.testSelfID = function*(assert) {
   assert.equal(typeof(id), 'string', 'self.id is a string');
   assert.ok(id.length > 0, 'self.id not empty');
 
-  AddonManager.getAddonByID(id, function(addon) {
-    assert.equal(addon.id, id, 'found addon with self.id');
-    done();
-  });
+  let addon = yield getAddonByID(id);
+  assert.equal(addon.id, id, 'found addon with self.id');
 }
 
 require('sdk/test/runner').runTestsFromModule(module);
--- a/addon-sdk/source/test/addons/preferences-branch/lib/main.js
+++ b/addon-sdk/source/test/addons/preferences-branch/lib/main.js
@@ -1,35 +1,28 @@
 /* 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';
 
 const { id, preferencesBranch } = require('sdk/self');
 const simple = require('sdk/simple-prefs');
 const service = require('sdk/preferences/service');
-const { AddonManager } = require('chrome').Cu.import('resource://gre/modules/AddonManager.jsm');
+const { getAddonByID } = require('sdk/addon/manager');
 
 exports.testPreferencesBranch = function(assert) {
   assert.equal(preferencesBranch, 'human-readable', 'preferencesBranch is human-readable');
-
   assert.equal(simple.prefs.test42, true, 'test42 is true');
 
   simple.prefs.test43 = 'movie';
   assert.equal(service.get('extensions.human-readable.test43'), 'movie', 'test43 is a movie');
 
 }
 
 // from `/test/test-self.js`, adapted to `sdk/test/assert` API
-exports.testSelfID = function(assert, done) {
-
+exports.testSelfID = function*(assert) {
   assert.equal(typeof(id), 'string', 'self.id is a string');
   assert.ok(id.length > 0, 'self.id not empty');
-
-  AddonManager.getAddonByID(id, function(addon) {
-    assert.ok(addon, 'found addon with self.id');
-    done();
-  });
-
+  let addon = yield getAddonByID(id);
+  assert.ok(addon, 'found addon with self.id');
 }
 
 require('sdk/test/runner').runTestsFromModule(module);
--- a/addon-sdk/source/test/addons/private-browsing-supported/main.js
+++ b/addon-sdk/source/test/addons/private-browsing-supported/main.js
@@ -1,23 +1,21 @@
 /* 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';
 
 const { merge } = require('sdk/util/object');
 const app = require('sdk/system/xul-app');
-const { isGlobalPBSupported } = require('sdk/private-browsing/utils');
 
 merge(module.exports,
   require('./test-tabs'),
   require('./test-page-mod'),
   require('./test-private-browsing'),
-  require('./test-sidebar'),
-  isGlobalPBSupported ? require('./test-global-private-browsing') : {}
+  require('./test-sidebar')
 );
 
 // Doesn't make sense to test window-utils and windows on fennec,
 // as there is only one window which is never private. Also ignore
 // unsupported modules (panel, selection)
 if (!app.is('Fennec')) {
   merge(module.exports,
     require('./test-selection'),
deleted file mode 100644
--- a/addon-sdk/source/test/addons/private-browsing-supported/test-global-private-browsing.js
+++ /dev/null
@@ -1,150 +0,0 @@
-/* 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';
-
-const windowUtils = require('sdk/deprecated/window-utils');
-const { isWindowPBSupported, isGlobalPBSupported } = require('sdk/private-browsing/utils');
-const { getFrames, getWindowTitle, onFocus, isWindowPrivate, windows, isBrowser } = require('sdk/window/utils');
-const { open, close, focus } = require('sdk/window/helpers');
-const { isPrivate } = require('sdk/private-browsing');
-const { Panel } = require('sdk/panel');
-const { Widget } = require('sdk/widget');
-const { fromIterator: toArray } = require('sdk/util/array');
-
-let { Loader } = require('sdk/test/loader');
-let loader = Loader(module, {
-  console: Object.create(console, {
-    error: {
-      value: function(e) !/DEPRECATED:/.test(e) ? console.error(e) : undefined
-    }
-  })
-});
-const pb = loader.require('sdk/private-browsing');
-
-function makeEmptyBrowserWindow(options) {
-  options = options || {};
-  return open('chrome://browser/content/browser.xul', {
-    features: {
-      chrome: true,
-      private: !!options.private,
-      toolbar: true
-    }
-  });
-}
-
-exports.testShowPanelAndWidgetOnPrivateWindow = function(assert, done) {
-  var myPrivateWindow;
-  var finished = false;
-  var privateWindow;
-  var privateWindowClosed = false;
-
-  pb.once('start', function() {
-    assert.pass('private browsing mode started');
-
-    // make a new private window
-    makeEmptyBrowserWindow().then(function(window) {
-      myPrivateWindow = window;
-
-      let wt = windowUtils.WindowTracker({
-        onTrack: function(window) {
-          if (!isBrowser(window) || window !== myPrivateWindow) return;
-
-          assert.ok(isWindowPrivate(window), 'window is private onTrack!');
-          let panel = Panel({
-            onShow: function() {
-              assert.ok(this.isShowing, 'the panel is showing on the private window');
-
-              let count = 0;
-              let widget = Widget({
-                id: "testShowPanelAndWidgetOnPrivateWindow-id",
-                label: "My Hello Widget",
-                content: "Hello!",
-                onAttach: function(mod) {
-                  count++;
-                  if (count == 2) {
-                    panel.destroy();
-                    widget.destroy();
-                    close(window);
-                  }
-                }
-              });
-            }
-          }).show(null, window.gBrowser);
-        },
-        onUntrack: function(window) {
-          if (window === myPrivateWindow) {
-            wt.unload();
-
-            pb.once('stop', function() {
-              assert.pass('private browsing mode end');
-              done();
-            });
-
-            pb.deactivate();
-          }
-        }
-      });
-
-      assert.equal(isWindowPrivate(window), true, 'the opened window is private');
-      assert.equal(isPrivate(window), true, 'the opened window is private');
-      assert.ok(getFrames(window).length > 1, 'there are frames for private window');
-      assert.equal(getWindowTitle(window), window.document.title,
-                   'getWindowTitle works');
-    });
-  });
-  pb.activate();
-};
-
-exports.testWindowTrackerDoesNotIgnorePrivateWindows = function(assert, done) {
-  var myPrivateWindow;
-  var count = 0;
-
-  let wt = windowUtils.WindowTracker({
-    onTrack: function(window) {
-      if (!isBrowser(window) || !isWindowPrivate(window)) return;
-      assert.ok(isWindowPrivate(window), 'window is private onTrack!');
-      if (++count == 1)
-        close(window);
-    },
-    onUntrack: function(window) {
-      if (count == 1 && isWindowPrivate(window)) {
-        wt.unload();
-
-        pb.once('stop', function() {
-          assert.pass('private browsing mode end');
-          done();
-        });
-        pb.deactivate();
-      }
-    }
-  });
-
-  pb.once('start', function() {
-    assert.pass('private browsing mode started');
-    makeEmptyBrowserWindow();
-  });
-  pb.activate();
-}
-
-exports.testWindowIteratorDoesNotIgnorePrivateWindows = function(assert, done) {
-  pb.once('start', function() {
-    // make a new private window
-    makeEmptyBrowserWindow().then(function(window) {
-      assert.ok(isWindowPrivate(window), "window is private");
-      assert.equal(isPrivate(window), true, 'the opened window is private');
-      assert.ok(toArray(windowUtils.windowIterator()).indexOf(window) > -1,
-                "window is in windowIterator()");
-      assert.ok(windows(null, { includePrivate: true }).indexOf(window) > -1,
-                "window is in windows()");
-
-      return close(window).then(function() {
-        pb.once('stop', function() {
-          done();
-        });
-        pb.deactivate();
-      });
-    }).then(null, assert.fail);
-  });
-  pb.activate();
-};
--- a/addon-sdk/source/test/addons/simple-prefs-regression/lib/main.js
+++ b/addon-sdk/source/test/addons/simple-prefs-regression/lib/main.js
@@ -1,49 +1,46 @@
 /* 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';
 
 const { Cu } = require('chrome');
 const sp = require('sdk/simple-prefs');
 const app = require('sdk/system/xul-app');
-const self = require('sdk/self');
 const tabs = require('sdk/tabs');
-const { preferencesBranch } = require('sdk/self');
-
+const { preferencesBranch, id } = require('sdk/self');
+const { getAddonByID } = require('sdk/addon/manager');
 const { AddonManager } = Cu.import('resource://gre/modules/AddonManager.jsm', {});
 
-exports.testRegression = function(assert) {
-  assert.equal(self.preferencesBranch, self.id, 'preferencesBranch returns id here');
+exports.testRegression = (assert) => {
+  assert.equal(preferencesBranch, id, 'preferencesBranch returns id here');
 }
 
-exports.testDefaultValues = function (assert) {
+exports.testDefaultValues = (assert) => {
   assert.equal(sp.prefs.myHiddenInt, 5, 'myHiddenInt default is 5');
   assert.equal(sp.prefs.myInteger, 8, 'myInteger default is 8');
   assert.equal(sp.prefs.somePreference, 'TEST', 'somePreference default is correct');
 }
 
-exports.testOptionsType = function(assert, done) {
-  AddonManager.getAddonByID(self.id, function(aAddon) {
-    assert.equal(aAddon.optionsType, AddonManager.OPTIONS_TYPE_INLINE, 'options type is inline');
-    done();
-  });
+exports.testOptionsType = function*(assert) {
+  let addon = yield getAddonByID(id);
+  assert.equal(addon.optionsType, AddonManager.OPTIONS_TYPE_INLINE, 'options type is inline');
 }
 
 if (app.is('Firefox')) {
   exports.testAOM = function(assert, done) {
       tabs.open({
       	url: 'about:addons',
       	onReady: function(tab) {
           tab.attach({
             contentScriptWhen: 'end',
           	contentScript: 'function onLoad() {\n' +
                              'unsafeWindow.removeEventListener("load", onLoad, false);\n' +
-                             'AddonManager.getAddonByID("' + self.id + '", function(aAddon) {\n' +
+                             'AddonManager.getAddonByID("' + id + '", function(aAddon) {\n' +
                                'unsafeWindow.gViewController.viewObjects.detail.node.addEventListener("ViewChanged", function whenViewChanges() {\n' +
                                  'unsafeWindow.gViewController.viewObjects.detail.node.removeEventListener("ViewChanged", whenViewChanges, false);\n' +
                                  'setTimeout(function() {\n' + // TODO: figure out why this is necessary..
                                      'self.postMessage({\n' +
                                        'somePreference: getAttributes(unsafeWindow.document.querySelector("setting[title=\'some-title\']")),\n' +
                                        'myInteger: getAttributes(unsafeWindow.document.querySelector("setting[title=\'my-int\']")),\n' +
                                        'myHiddenInt: getAttributes(unsafeWindow.document.querySelector("setting[title=\'hidden-int\']"))\n' +
                                      '});\n' +
@@ -65,23 +62,23 @@ if (app.is('Firefox')) {
                            'if (document.readyState == "complete") {\n' +
                              'onLoad()\n' +
                            '} else {\n' +
                              'unsafeWindow.addEventListener("load", onLoad, false);\n' +
                            '}\n',
             onMessage: function(msg) {
               // test somePreference
               assert.equal(msg.somePreference.type, 'string', 'some pref is a string');
-              assert.equal(msg.somePreference.pref, 'extensions.'+self.preferencesBranch+'.somePreference', 'somePreference path is correct');
+              assert.equal(msg.somePreference.pref, 'extensions.'+preferencesBranch+'.somePreference', 'somePreference path is correct');
               assert.equal(msg.somePreference.title, 'some-title', 'somePreference title is correct');
               assert.equal(msg.somePreference.desc, 'Some short description for the preference', 'somePreference description is correct');
 
               // test myInteger
               assert.equal(msg.myInteger.type, 'integer', 'myInteger is a int');
-              assert.equal(msg.myInteger.pref, 'extensions.'+self.preferencesBranch+'.myInteger', 'extensions.test-simple-prefs.myInteger');
+              assert.equal(msg.myInteger.pref, 'extensions.'+preferencesBranch+'.myInteger', 'extensions.test-simple-prefs.myInteger');
               assert.equal(msg.myInteger.title, 'my-int', 'myInteger title is correct');
               assert.equal(msg.myInteger.desc, 'How many of them we have.', 'myInteger desc is correct');
 
               // test myHiddenInt
               assert.equal(msg.myHiddenInt.type, undefined, 'myHiddenInt was not displayed');
               assert.equal(msg.myHiddenInt.pref, undefined, 'myHiddenInt was not displayed');
               assert.equal(msg.myHiddenInt.title, undefined, 'myHiddenInt was not displayed');
               assert.equal(msg.myHiddenInt.desc, undefined, 'myHiddenInt was not displayed');
--- a/addon-sdk/source/test/addons/simple-prefs/lib/main.js
+++ b/addon-sdk/source/test/addons/simple-prefs/lib/main.js
@@ -1,55 +1,52 @@
 /* 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';
 
 const { Cu } = require('chrome');
 const sp = require('sdk/simple-prefs');
 const app = require('sdk/system/xul-app');
-const self = require('sdk/self');
-const { preferencesBranch } = self;
+const { id, preferencesBranch } = require('sdk/self');
 const { open } = require('sdk/preferences/utils');
 const { getTabForId } = require('sdk/tabs/utils');
 const { Tab } = require('sdk/tabs/tab');
+const { getAddonByID } = require('sdk/addon/manager');
+const { AddonManager } = Cu.import('resource://gre/modules/AddonManager.jsm', {});
 require('sdk/tabs');
 
-const { AddonManager } = Cu.import('resource://gre/modules/AddonManager.jsm', {});
-
 exports.testDefaultValues = function (assert) {
   assert.equal(sp.prefs.myHiddenInt, 5, 'myHiddenInt default is 5');
   assert.equal(sp.prefs.myInteger, 8, 'myInteger default is 8');
   assert.equal(sp.prefs.somePreference, 'TEST', 'somePreference default is correct');
 }
 
-exports.testOptionsType = function(assert, done) {
-  AddonManager.getAddonByID(self.id, function(aAddon) {
-    assert.equal(aAddon.optionsType, AddonManager.OPTIONS_TYPE_INLINE, 'options type is inline');
-    done();
-  });
+exports.testOptionsType = function*(assert) {
+  let addon = yield getAddonByID(id);
+  assert.equal(addon.optionsType, AddonManager.OPTIONS_TYPE_INLINE, 'options type is inline');
 }
 
 exports.testButton = function(assert, done) {
-  open(self).then(({ tabId, document }) => {
+  open({ id: id }).then(({ tabId, document }) => {
     let tab = Tab({ tab: getTabForId(tabId) });
     sp.once('sayHello', _ => {
       assert.pass('The button was pressed!');
       tab.close(done);
     });
 
     tab.attach({
       contentScript: 'unsafeWindow.document.querySelector("button[label=\'Click me!\']").click();'
     });
   });
 }
 
 if (app.is('Firefox')) {
   exports.testAOM = function(assert, done) {
-    open(self).then(({ tabId }) => {
+    open({ id: id }).then(({ tabId }) => {
       let tab = Tab({ tab: getTabForId(tabId) });
       assert.pass('the add-on prefs page was opened.');
 
       tab.attach({
         contentScriptWhen: "end",
         contentScript: 'self.postMessage({\n' +
                          'someCount: unsafeWindow.document.querySelectorAll("setting[title=\'some-title\']").length,\n' +
                          'somePreference: getAttributes(unsafeWindow.document.querySelector("setting[title=\'some-title\']")),\n' +
@@ -68,46 +65,45 @@ if (app.is('Firefox')) {
                          '}\n' +
                        '}\n',
         onMessage: msg => {
           // test against doc caching
           assert.equal(msg.someCount, 1, 'there is exactly one <setting> node for somePreference');
 
           // test somePreference
           assert.equal(msg.somePreference.type, 'string', 'some pref is a string');
-          assert.equal(msg.somePreference.pref, 'extensions.'+self.id+'.somePreference', 'somePreference path is correct');
+          assert.equal(msg.somePreference.pref, 'extensions.' + id + '.somePreference', 'somePreference path is correct');
           assert.equal(msg.somePreference.title, 'some-title', 'somePreference title is correct');
           assert.equal(msg.somePreference.desc, 'Some short description for the preference', 'somePreference description is correct');
-          assert.equal(msg.somePreference['data-jetpack-id'], self.id, 'data-jetpack-id attribute value is correct');
+          assert.equal(msg.somePreference['data-jetpack-id'], id, 'data-jetpack-id attribute value is correct');
 
           // test myInteger
           assert.equal(msg.myInteger.type, 'integer', 'myInteger is a int');
-          assert.equal(msg.myInteger.pref, 'extensions.'+self.id+'.myInteger', 'extensions.test-simple-prefs.myInteger');
+          assert.equal(msg.myInteger.pref, 'extensions.' + id + '.myInteger', 'extensions.test-simple-prefs.myInteger');
           assert.equal(msg.myInteger.title, 'my-int', 'myInteger title is correct');
           assert.equal(msg.myInteger.desc, 'How many of them we have.', 'myInteger desc is correct');
-          assert.equal(msg.myInteger['data-jetpack-id'], self.id, 'data-jetpack-id attribute value is correct');
+          assert.equal(msg.myInteger['data-jetpack-id'], id, 'data-jetpack-id attribute value is correct');
 
           // test myHiddenInt
           assert.equal(msg.myHiddenInt.type, undefined, 'myHiddenInt was not displayed');
           assert.equal(msg.myHiddenInt.pref, undefined, 'myHiddenInt was not displayed');
           assert.equal(msg.myHiddenInt.title, undefined, 'myHiddenInt was not displayed');
           assert.equal(msg.myHiddenInt.desc, undefined, 'myHiddenInt was not displayed');
 
           // test sayHello
-          assert.equal(msg.sayHello['data-jetpack-id'], self.id, 'data-jetpack-id attribute value is correct');
+          assert.equal(msg.sayHello['data-jetpack-id'], id, 'data-jetpack-id attribute value is correct');
 
           tab.close(done);
         }
       });
     })
   }
 
   // run it again, to test against inline options document caching
   // and duplication of <setting> nodes upon re-entry to about:addons
   exports.testAgainstDocCaching = exports.testAOM;
-
 }
 
 exports.testDefaultPreferencesBranch = function(assert) {
-  assert.equal(preferencesBranch, self.id, 'preferencesBranch default the same as self.id');
+  assert.equal(preferencesBranch, id, 'preferencesBranch default the same as self.id');
 }
 
 require('sdk/test/runner').runTestsFromModule(module);
--- a/addon-sdk/source/test/addons/standard-id/lib/main.js
+++ b/addon-sdk/source/test/addons/standard-id/lib/main.js
@@ -2,43 +2,34 @@
  * 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';
 
 const { id, preferencesBranch } = require('sdk/self');
 const simple = require('sdk/simple-prefs');
 const service = require('sdk/preferences/service');
-const { AddonManager } = require('chrome').Cu.import('resource://gre/modules/AddonManager.jsm');
+const { getAddonByID } = require('sdk/addon/manager');
 
 exports.testStandardID = function(assert) {
   assert.equal(id, 'standard-id@jetpack', 'standard ID is standard');
 
   assert.equal(simple.prefs.test13, 26, 'test13 is 26');
 
   simple.prefs.test14 = '15';
   assert.equal(service.get('extensions.standard-id@jetpack.test14'), '15', 'test14 is 15');
-
   assert.equal(service.get('extensions.standard-id@jetpack.test14'), simple.prefs.test14, 'simple test14 also 15');
-
 }
 
 exports.testInvalidPreferencesBranch = function(assert) {
   assert.notEqual(preferencesBranch, 'invalid^branch*name', 'invalid preferences-branch value ignored');
-
   assert.equal(preferencesBranch, 'standard-id@jetpack', 'preferences-branch is standard-id@jetpack');
-
 }
 
 // from `/test/test-self.js`, adapted to `sdk/test/assert` API
-exports.testSelfID = function(assert, done) {
-
+exports.testSelfID = function*(assert) {
   assert.equal(typeof(id), 'string', 'self.id is a string');
   assert.ok(id.length > 0, 'self.id not empty');
-
-  AddonManager.getAddonByID(id, function(addon) {
-    assert.ok(addon, 'found addon with self.id');
-    done();
-  });
-
+  let addon = yield getAddonByID(id);
+  assert.ok(addon, 'found addon with self.id');
 }
 
 require('sdk/test/runner').runTestsFromModule(module);
--- a/addon-sdk/source/test/addons/translators/main.js
+++ b/addon-sdk/source/test/addons/translators/main.js
@@ -1,23 +1,20 @@
 /* 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';
 
-const { Cc, Ci, Cu, Cm, components } = require('chrome');
-const self = require('sdk/self');
-const { AddonManager } = Cu.import('resource://gre/modules/AddonManager.jsm', {});
+const { id } = require('sdk/self');
+const { getAddonByID } = require('sdk/addon/manager');
 
-exports.testTranslators = function(assert, done) {
-  AddonManager.getAddonByID(self.id, function(addon) {
-    let count = 0;
-    addon.translators.forEach(function({ name }) {
-      count++;
-      assert.equal(name, 'Erik Vold', 'The translator keys are correct');
-    });
-    assert.equal(count, 1, 'The translator key count is correct');
-    assert.equal(addon.translators.length, 1, 'The translator key length is correct');
-    done();
+exports.testTranslators = function*(assert) {
+  let addon = yield getAddonByID(id);
+  let count = 0;
+  addon.translators.forEach(function({ name }) {
+    count++;
+    assert.equal(name, 'Erik Vold', 'The translator keys are correct');
   });
+  assert.equal(count, 1, 'The translator key count is correct');
+  assert.equal(addon.translators.length, 1, 'The translator key length is correct');
 }
 
 require('sdk/test/runner').runTestsFromModule(module);
--- a/addon-sdk/source/test/jetpack-package.ini
+++ b/addon-sdk/source/test/jetpack-package.ini
@@ -13,16 +13,17 @@ support-files =
   windows/**
   zip/**
   fixtures.js
   pagemod-test-helpers.js
   test-context-menu.html
   test-tmp-file.txt
 
 [test-addon-installer.js]
+[test-addon-manager.js]
 [test-addon-window.js]
 [test-api-utils.js]
 [test-array.js]
 [test-base64.js]
 [test-bootstrap.js]
 [test-browser-events.js]
 [test-buffer.js]
 [test-byte-streams.js]
@@ -148,17 +149,16 @@ skip-if = true
 skip-if = true
 [test-url.js]
 [test-uuid.js]
 [test-weak-set.js]
 [test-widget.js]
 [test-window-events.js]
 [test-window-loader.js]
 [test-window-observer.js]
-[test-window-utils-global-private-browsing.js]
 [test-window-utils-private-browsing.js]
 [test-window-utils.js]
 [test-window-utils2.js]
 [test-windows-common.js]
 [test-windows.js]
 [test-xhr.js]
 [test-xpcom.js]
 [test-xul-app.js]
deleted file mode 100644
--- a/addon-sdk/source/test/private-browsing/global.js
+++ /dev/null
@@ -1,239 +0,0 @@
-/* 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';
-
-const timer = require("sdk/timers");
-const { LoaderWithHookedConsole, deactivate, pb, pbUtils } = require("./helper");
-const tabs = require("sdk/tabs");
-const { getMostRecentBrowserWindow, isWindowPrivate } = require('sdk/window/utils');
-const { set: setPref } = require("sdk/preferences/service");
-const DEPRECATE_PREF = "devtools.errorconsole.deprecation_warnings";
-
-exports["test activate private mode via handler"] = function(assert, done) {
-  function onReady(tab) {
-    if (tab.url == "about:robots")
-      tab.close(function() pb.activate());
-  }
-  function cleanup(tab) {
-    if (tab.url == "about:") {
-      tabs.removeListener("ready", cleanup);
-      tab.close(function onClose() {
-        done();
-      });
-    }
-  }
-
-  tabs.on("ready", onReady);
-  pb.once("start", function onStart() {
-    assert.pass("private mode was activated");
-    pb.deactivate();
-  });
-  pb.once("stop", function onStop() {
-    assert.pass("private mode was deactivated");
-    tabs.removeListener("ready", onReady);
-    tabs.on("ready", cleanup);
-  });
-  tabs.once("open", function onOpen() {
-    tabs.open("about:robots");
-  });
-  tabs.open("about:");
-};
-
-// tests that isActive has the same value as the private browsing service
-// expects
-exports.testGetIsActive = function (assert) {
-  assert.equal(pb.isActive, false,
-                   "private-browsing.isActive is correct without modifying PB service");
-  assert.equal(pb.isPrivate(), false,
-                   "private-browsing.sPrivate() is correct without modifying PB service");
-
-  pb.once("start", function() {
-    assert.ok(pb.isActive,
-                  "private-browsing.isActive is correct after modifying PB service");
-    assert.ok(pb.isPrivate(),
-                  "private-browsing.sPrivate() is correct after modifying PB service");
-    // Switch back to normal mode.
-    pb.deactivate();
-  });
-  pb.activate();
-
-  pb.once("stop", function() {
-    assert.ok(!pb.isActive,
-                "private-browsing.isActive is correct after modifying PB service");
-    assert.ok(!pb.isPrivate(),
-                "private-browsing.sPrivate() is correct after modifying PB service");
-    test.done();
-  });
-};
-
-exports.testStart = function(assert, done) {
-  pb.on("start", function onStart() {
-    assert.equal(this, pb, "`this` should be private-browsing module");
-    assert.ok(pbUtils.getMode(),
-                'private mode is active when "start" event is emitted');
-    assert.ok(pb.isActive,
-                '`isActive` is `true` when "start" event is emitted');
-    assert.ok(pb.isPrivate(),
-                '`isPrivate` is `true` when "start" event is emitted');
-    pb.removeListener("start", onStart);
-    deactivate(done);
-  });
-  pb.activate();
-};
-
-exports.testStop = function(assert, done) {
-  pb.once("stop", function onStop() {
-    assert.equal(this, pb, "`this` should be private-browsing module");
-    assert.equal(pbUtils.getMode(), false,
-                     "private mode is disabled when stop event is emitted");
-    assert.equal(pb.isActive, false,
-                     "`isActive` is `false` when stop event is emitted");
-    assert.equal(pb.isPrivate(), false,
-                     "`isPrivate()` is `false` when stop event is emitted");
-    done();
-  });
-  pb.activate();
-  pb.once("start", function() {
-    pb.deactivate();
-  });
-};
-
-exports.testBothListeners = function(assert, done) {
-  let stop = false;
-  let start = false;
-
-  function onStop() {
-    assert.equal(stop, false,
-                     "stop callback must be called only once");
-    assert.equal(pbUtils.getMode(), false,
-                     "private mode is disabled when stop event is emitted");
-    assert.equal(pb.isActive, false,
-                     "`isActive` is `false` when stop event is emitted");
-    assert.equal(pb.isPrivate(), false,
-                     "`isPrivate()` is `false` when stop event is emitted");
-
-    pb.on("start", finish);
-    pb.removeListener("start", onStart);
-    pb.removeListener("start", onStart2);
-    pb.activate();
-    stop = true;
-  }
-
-  function onStart() {
-    assert.equal(false, start,
-                     "stop callback must be called only once");
-    assert.ok(pbUtils.getMode(),
-                "private mode is active when start event is emitted");
-    assert.ok(pb.isActive,
-                "`isActive` is `true` when start event is emitted");
-    assert.ok(pb.isPrivate(),
-                "`isPrivate()` is `true` when start event is emitted");
-
-    pb.on("stop", onStop);
-    pb.deactivate();
-    start = true;
-  }
-
-  function onStart2() {
-    assert.ok(start, "start listener must be called already");
-    assert.equal(false, stop, "stop callback must not be called yet");
-  }
-
-  function finish() {
-    assert.ok(pbUtils.getMode(), true,
-                "private mode is active when start event is emitted");
-    assert.ok(pb.isActive,
-                "`isActive` is `true` when start event is emitted");
-    assert.ok(pb.isPrivate(),
-                "`isPrivate()` is `true` when start event is emitted");
-
-    pb.removeListener("start", finish);
-    pb.removeListener("stop", onStop);
-
-    pb.deactivate();
-    pb.once("stop", function () {
-      assert.equal(pbUtils.getMode(), false);
-      assert.equal(pb.isActive, false);
-      assert.equal(pb.isPrivate(), false);
-
-      done();
-    });
-  }
-
-  pb.on("start", onStart);
-  pb.on("start", onStart2);
-  pb.activate();
-};
-
-exports.testAutomaticUnload = function(assert, done) {
-  setPref(DEPRECATE_PREF, true);
-
-  // Create another private browsing instance and unload it
-  let { loader, errors } = LoaderWithHookedConsole(module);
-  let pb2 = loader.require("sdk/private-browsing");
-  let called = false;
-  pb2.on("start", function onStart() {
-    called = true;
-    assert.fail("should not be called:x");
-  });
-  loader.unload();
-
-  // Then switch to private mode in order to check that the previous instance
-  // is correctly destroyed
-  pb.once("start", function onStart() {
-    timer.setTimeout(function () {
-      assert.ok(!called, 
-        "First private browsing instance is destroyed and inactive");
-      // Must reset to normal mode, so that next test starts with it.
-      deactivate(function() {
-        assert.ok(errors.length, 0, "should have been 1 deprecation error");
-        done();
-      });
-    }, 0);
-  });
-
-  pb.activate();
-};
-
-exports.testUnloadWhileActive = function(assert, done) {
-  let called = false;
-  let { loader, errors } = LoaderWithHookedConsole(module);
-  let pb2 = loader.require("sdk/private-browsing");
-  let ul = loader.require("sdk/system/unload");
-
-  let unloadHappened = false;
-  ul.when(function() {
-    unloadHappened = true;
-    timer.setTimeout(function() {
-      pb.deactivate();
-    });
-  });
-  pb2.once("start", function() {
-    loader.unload();
-  });
-  pb2.once("stop", function() {
-    called = true;
-    assert.ok(unloadHappened, "the unload event should have already occurred.");
-    assert.fail("stop should not have been fired");
-  });
-  pb.once("stop", function() {
-    assert.ok(!called, "stop was not called on unload");
-    assert.ok(errors.length, 2, "should have been 2 deprecation errors");
-    done();
-  });
-
-  pb.activate();
-};
-
-exports.testIgnoreWindow = function(assert, done) {
-  let window = getMostRecentBrowserWindow();
-
-  pb.once('start', function() {
-    assert.ok(isWindowPrivate(window), 'window is private');
-    assert.ok(!pbUtils.ignoreWindow(window), 'window is not ignored');
-    pb.once('stop', done);
-    pb.deactivate();
-  });
-  pb.activate();
-};
--- a/addon-sdk/source/test/private-browsing/helper.js
+++ b/addon-sdk/source/test/private-browsing/helper.js
@@ -1,62 +1,20 @@
 /* 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';
 
-const { Loader } = require('sdk/test/loader');
-
-const { loader } = LoaderWithHookedConsole(module);
-
-const pb = loader.require('sdk/private-browsing');
-const pbUtils = loader.require('sdk/private-browsing/utils');
 const xulApp = require("sdk/system/xul-app");
 const { open: openWindow, getMostRecentBrowserWindow } = require('sdk/window/utils');
 const { openTab, getTabContentWindow, getActiveTab, setTabURL, closeTab } = require('sdk/tabs/utils');
 const promise = require("sdk/core/promise");
 const windowHelpers = require('sdk/window/helpers');
 const events = require("sdk/system/events");
 
-function LoaderWithHookedConsole(module) {
-  let globals = {};
-  let errors = [];
-
-  globals.console = Object.create(console, {
-    error: {
-      value: function(e) {
-        errors.push(e);
-        if (!/DEPRECATED:/.test(e)) {
-          console.error(e);
-        }
-      }
-    }
-  });
-
-  let loader = Loader(module, globals);
-
-  return {
-    loader: loader,
-    errors: errors
-  }
-}
-
-function deactivate(callback) {
-  if (pbUtils.isGlobalPBSupported) {
-    if (callback)
-      pb.once('stop', callback);
-    pb.deactivate();
-  }
-}
-exports.deactivate = deactivate;
-
-exports.pb = pb;
-exports.pbUtils = pbUtils;
-exports.LoaderWithHookedConsole = LoaderWithHookedConsole;
-
 exports.openWebpage = function openWebpage(url, enablePrivate) {
   if (xulApp.is("Fennec")) {
     let chromeWindow = getMostRecentBrowserWindow();
     let rawTab = openTab(chromeWindow, url, {
       isPrivate: enablePrivate
     });
 
     return {
--- a/addon-sdk/source/test/private-browsing/windows.js
+++ b/addon-sdk/source/test/private-browsing/windows.js
@@ -1,63 +1,49 @@
 /* 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';
 
-const { pb, pbUtils } = require('./helper');
 const { onFocus, openDialog, open } = require('sdk/window/utils');
 const { open: openPromise, close, focus, promise } = require('sdk/window/helpers');
 const { isPrivate } = require('sdk/private-browsing');
+const { getMode } = require('sdk/private-browsing/utils');
 const { browserWindows: windows } = require('sdk/windows');
 const { defer } = require('sdk/core/promise');
 const tabs = require('sdk/tabs');
 
 // test openDialog() from window/utils with private option
 // test isActive state in pwpb case
 // test isPrivate on ChromeWindow
-exports.testPerWindowPrivateBrowsingGetter = function(assert, done) {
-  let win = openDialog({
-    private: true
-  });
+exports.testPerWindowPrivateBrowsingGetter = function*(assert) {
+  let win = openDialog({ private: true });
+
+  yield promise(win, 'DOMContentLoaded');
 
-  promise(win, 'DOMContentLoaded').then(function onload() {
-    assert.equal(pbUtils.getMode(win),
-                 true, 'Newly opened window is in PB mode');
-    assert.ok(isPrivate(win), 'isPrivate(window) is true');
-    assert.equal(pb.isActive, false, 'PB mode is not active');
+  assert.equal(getMode(win), true, 'Newly opened window is in PB mode');
+  assert.ok(isPrivate(win), 'isPrivate(window) is true');
 
-    close(win).then(function() {
-      assert.equal(pb.isActive, false, 'PB mode is not active');
-      done();
-    });
-  });
+  yield close(win);
 }
 
 // test open() from window/utils with private feature
 // test isActive state in pwpb case
 // test isPrivate on ChromeWindow
-exports.testPerWindowPrivateBrowsingGetter = function(assert, done) {
+exports.testPerWindowPrivateBrowsingGetter = function*(assert) {
   let win = open('chrome://browser/content/browser.xul', {
     features: {
       private: true
     }
   });
 
-  promise(win, 'DOMContentLoaded').then(function onload() {
-    assert.equal(pbUtils.getMode(win),
-                 true, 'Newly opened window is in PB mode');
-    assert.ok(isPrivate(win), 'isPrivate(window) is true');
-    assert.equal(pb.isActive, false, 'PB mode is not active');
-
-    close(win).then(function() {
-      assert.equal(pb.isActive, false, 'PB mode is not active');
-      done();
-    });
-  });
+  yield promise(win, 'DOMContentLoaded');
+  assert.equal(getMode(win), true, 'Newly opened window is in PB mode');
+  assert.ok(isPrivate(win), 'isPrivate(window) is true');
+  yield close(win)
 }
 
 exports.testIsPrivateOnWindowOpen = function(assert, done) {
   windows.open({
     isPrivate: true,
     onOpen: function(window) {
       assert.equal(isPrivate(window), false, 'isPrivate for a window is true when it should be');
       assert.equal(isPrivate(window.tabs[0]), false, 'isPrivate for a tab is false when it should be');
--- a/addon-sdk/source/test/test-addon-installer.js
+++ b/addon-sdk/source/test/test-addon-installer.js
@@ -170,9 +170,9 @@ exports['test Disable failure'] = functi
 
 exports['test Enable failure'] = function (assert, done) {
   AddonInstaller.enable('not-an-id').then(
     () => assert.fail('Addon enable should not resolve successfully'),
     () => assert.pass('Addon correctly rejected invalid enable')
   ).then(done, assert.fail);
 };
 
-require("test").run(exports);
+require("sdk/test").run(exports);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/test-addon-manager.js
@@ -0,0 +1,14 @@
+/* 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";
+
+const { id } = require("sdk/self");
+const { getAddonByID } = require("sdk/addon/manager");
+
+exports["test getAddonByID"] = function*(assert) {
+  let addon = yield getAddonByID(id);
+  assert.equal(addon.id, id, "getAddonByID works");
+}
+
+require("sdk/test").run(exports);
--- a/addon-sdk/source/test/test-buffer.js
+++ b/addon-sdk/source/test/test-buffer.js
@@ -386,17 +386,17 @@ exports.testBufferSlice = function (asse
   assert.equal(buf.slice(-10, 10), '0123456789', 'buffer slice range correct');
   assert.equal(buf.slice(-20, 10), '0123456789', 'buffer slice range correct');
   assert.equal(buf.slice(-20, -10), '', 'buffer slice range correct');
   assert.equal(buf.slice(0, -1), '012345678', 'buffer slice range correct');
   assert.equal(buf.slice(2, -2), '234567', 'buffer slice range correct');
   assert.equal(buf.slice(0, 65536), '0123456789', 'buffer slice range correct');
   assert.equal(buf.slice(65536, 0), '', 'buffer slice range correct');
 
-  let sliceTest = true;
+  sliceTest = true;
   for (var i = 0, s = buf.toString(); i < buf.length; ++i) {
     if (buf.slice(-i) != s.slice(-i)) sliceTest = false;
     if (buf.slice(0, -i) != s.slice(0, -i)) sliceTest = false;
   }
   assert.ok(sliceTest, 'buffer.slice should be consistent');
 
   // Make sure modifying a sliced buffer, affects original and vice versa
   b.fill(0);
@@ -458,17 +458,17 @@ exports.testBufferConcat = function (ass
   let flatZero = Buffer.concat(zero);
   let flatOne = Buffer.concat(one);
   let flatLong = Buffer.concat(long);
   let flatLongLen = Buffer.concat(long, 40);
 
   assert.equal(flatZero.length, 0);
   assert.equal(flatOne.toString(), 'asdf');
   assert.equal(flatOne, one[0]);
-  assert.ok(flatLong.toString(), (new Array(10+1).join('asdf')));
+  assert.equal(flatLong.toString(), (new Array(10+1).join('asdf')));
   assert.equal(flatLongLen.toString(), (new Array(10+1).join('asdf')));
 };
 
 exports.testBufferByteLength = function (assert) {
   let str = '\u00bd + \u00bc = \u00be';
   assert.equal(Buffer.byteLength(str), 12,
     'correct byteLength of string');
 
--- a/addon-sdk/source/test/test-context-menu.js
+++ b/addon-sdk/source/test/test-context-menu.js
@@ -5,16 +5,17 @@
 
 let { Cc, Ci } = require("chrome");
 
 require("sdk/context-menu");
 
 const { Loader } = require('sdk/test/loader');
 const timer = require("sdk/timers");
 const { merge } = require("sdk/util/object");
+const { defer } = require("sdk/core/promise");
 
 // These should match the same constants in the module.
 const ITEM_CLASS = "addon-context-menu-item";
 const SEPARATOR_CLASS = "addon-context-menu-separator";
 const OVERFLOW_THRESH_DEFAULT = 10;
 const OVERFLOW_THRESH_PREF =
   "extensions.addon-sdk.context-menu.overflowThreshold";
 const OVERFLOW_MENU_CLASS = "addon-content-menu-overflow-menu";
@@ -2659,16 +2660,68 @@ exports.testItemNoData = function (asser
           });
         });
       });
     });
   });
 }
 
 
+exports.testItemNoAccessKey = function (assert, done) {
+  let test = new TestHelper(assert, done);
+  let loader = test.newLoader();
+
+  let item1 = new loader.cm.Item({ label: "item 1" });
+  let item2 = new loader.cm.Item({ label: "item 2", accesskey: null });
+  let item3 = new loader.cm.Item({ label: "item 3", accesskey: undefined });
+
+  assert.equal(item1.accesskey, undefined, "Should be no defined image");
+  assert.equal(item2.accesskey, null, "Should be no defined image");
+  assert.equal(item3.accesskey, undefined, "Should be no defined image");
+
+  test.showMenu().
+  then((popup) => test.checkMenu([item1, item2, item3], [], [])).
+  then(test.done).
+  catch(assert.fail);
+}
+
+
+// Test accesskey support.
+exports.testItemAccessKey = function (assert, done) {
+  let test = new TestHelper(assert, done);
+  let loader = test.newLoader();
+
+  let item = new loader.cm.Item({ label: "item", accesskey: "i" });
+  assert.equal(item.accesskey, "i", "Should have set the image to i");
+
+  let menu = new loader.cm.Menu({ label: "menu", accesskey: "m", items: [
+    loader.cm.Item({ label: "subitem" })
+  ]});
+  assert.equal(menu.accesskey, "m", "Should have set the accesskey to m");
+
+  test.showMenu().then((popup) => {
+    test.checkMenu([item, menu], [], []);
+
+    let accesskey = "e";
+    menu.accesskey = item.accesskey = accesskey;
+    assert.equal(item.accesskey, accesskey, "Should have set the accesskey to " + accesskey);
+    assert.equal(menu.accesskey, accesskey, "Should have set the accesskey to " + accesskey);
+    test.checkMenu([item, menu], [], []);
+
+    item.accesskey = null;
+    menu.accesskey = null;
+    assert.equal(item.accesskey, null, "Should have set the accesskey to " + accesskey);
+    assert.equal(menu.accesskey, null, "Should have set the accesskey to " + accesskey);
+    test.checkMenu([item, menu], [], []);
+  }).
+  then(test.done).
+  catch(assert.fail);
+};
+
+
 // Tests that items without an image don't attempt to show one
 exports.testItemNoImage = function (assert, done) {
   let test = new TestHelper(assert, done);
   let loader = test.newLoader();
 
   let item1 = new loader.cm.Item({ label: "item 1" });
   let item2 = new loader.cm.Item({ label: "item 2", image: null });
   let item3 = new loader.cm.Item({ label: "item 3", image: undefined });
@@ -3690,16 +3743,17 @@ function TestHelper(assert, done) {
   this.assert = assert;
   this.end = done;
   this.loaders = [];
   this.browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"].
                        getService(Ci.nsIWindowMediator).
                        getMostRecentWindow("navigator:browser");
   this.overflowThreshValue = require("sdk/preferences/service").
                              get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT);
+  this.done = this.done.bind(this);
 }
 
 TestHelper.prototype = {
   get contextMenuPopup() {
     return this.browserWindow.document.getElementById("contentAreaContextMenu");
   },
 
   get contextMenuSeparator() {
@@ -3748,16 +3802,30 @@ TestHelper.prototype = {
       this.assert.equal(elt.localName, "menuseparator",
                          "Separator DOM element should be a xul:menuseparator");
       break;
     }
 
     if (itemType === "Item" || itemType === "Menu") {
       this.assert.equal(elt.getAttribute("label"), item.label,
                             "Item should have correct title");
+
+      // validate accesskey prop
+      if (item.accesskey) {
+        this.assert.equal(elt.getAttribute("accesskey"),
+                          item.accesskey,
+                          "Item should have correct accesskey");
+      }
+      else {
+        this.assert.equal(elt.getAttribute("accesskey"),
+                          "",
+                          "Item should not have accesskey");
+      }
+
+      // validate image prop
       if (typeof(item.image) === "string") {
         this.assert.equal(elt.getAttribute("image"), item.image,
                               "Item should have correct image");
         if (itemType === "Menu")
           this.assert.ok(elt.classList.contains("menu-iconic"),
                            "Menus with images should have the correct class")
         else
           this.assert.ok(elt.classList.contains("menuitem-iconic"),
@@ -4029,21 +4097,26 @@ TestHelper.prototype = {
               get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT) :
             OVERFLOW_THRESH_DEFAULT);
   },
 
   // Opens the context menu on the current page.  If targetNode is null, the
   // menu is opened in the top-left corner.  onShowncallback is passed the
   // popup.
   showMenu: function(targetNode, onshownCallback) {
+    let { promise, resolve } = defer();
+
     function sendEvent() {
       this.delayedEventListener(this.browserWindow, "popupshowing",
         function (e) {
           let popup = e.target;
-          onshownCallback.call(this, popup);
+          if (onshownCallback) {
+            onshownCallback.call(this, popup);
+          }
+          resolve(popup);
         }, false);
 
       let rect = targetNode ?
                  targetNode.getBoundingClientRect() :
                  { left: 0, top: 0, width: 0, height: 0 };
       let contentWin = targetNode ? targetNode.ownerDocument.defaultView
                                   : this.browserWindow.content;
       contentWin.
@@ -4065,16 +4138,18 @@ TestHelper.prototype = {
 
       this.delayedEventListener(browser, "load", function () {
         this.tabBrowser.selectedTab = this.tab;
         sendEvent.call(this);
       }, true);
     }
     else
       sendEvent.call(this);
+
+    return promise;
   },
 
   hideMenu: function(onhiddenCallback) {
     this.delayedEventListener(this.browserWindow, "popuphidden", onhiddenCallback);
 
     this.contextMenuPopup.hidePopup();
   },
 
--- a/addon-sdk/source/test/test-dev-panel.js
+++ b/addon-sdk/source/test/test-dev-panel.js
@@ -10,16 +10,18 @@ module.metadata = {
 };
 
 const { Tool } = require("dev/toolbox");
 const { Panel } = require("dev/panel");
 const { Class } = require("sdk/core/heritage");
 const { openToolbox, closeToolbox, getCurrentPanel } = require("dev/utils");
 const { MessageChannel } = require("sdk/messaging");
 const { when } = require("sdk/dom/events");
+const { viewFor } = require("sdk/view/core");
+const { createView } = require("dev/panel/view");
 
 const iconURI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAKQWlDQ1BJQ0MgUHJvZmlsZQAASA2dlndUU9kWh8+9N73QEiIgJfQaegkg0jtIFQRRiUmAUAKGhCZ2RAVGFBEpVmRUwAFHhyJjRRQLg4Ji1wnyEFDGwVFEReXdjGsJ7601896a/cdZ39nnt9fZZ+9917oAUPyCBMJ0WAGANKFYFO7rwVwSE8vE9wIYEAEOWAHA4WZmBEf4RALU/L09mZmoSMaz9u4ugGS72yy/UCZz1v9/kSI3QyQGAApF1TY8fiYX5QKUU7PFGTL/BMr0lSkyhjEyFqEJoqwi48SvbPan5iu7yZiXJuShGlnOGbw0noy7UN6aJeGjjAShXJgl4GejfAdlvVRJmgDl9yjT0/icTAAwFJlfzOcmoWyJMkUUGe6J8gIACJTEObxyDov5OWieAHimZ+SKBIlJYqYR15hp5ejIZvrxs1P5YjErlMNN4Yh4TM/0tAyOMBeAr2+WRQElWW2ZaJHtrRzt7VnW5mj5v9nfHn5T/T3IevtV8Sbsz55BjJ5Z32zsrC+9FgD2JFqbHbO+lVUAtG0GQOXhrE/vIADyBQC03pzzHoZsXpLE4gwnC4vs7GxzAZ9rLivoN/ufgm/Kv4Y595nL7vtWO6YXP4EjSRUzZUXlpqemS0TMzAwOl89k/fcQ/+PAOWnNycMsnJ/AF/GF6FVR6JQJhIlou4U8gViQLmQKhH/V4X8YNicHGX6daxRodV8AfYU5ULhJB8hvPQBDIwMkbj96An3rWxAxCsi+vGitka9zjzJ6/uf6Hwtcim7hTEEiU+b2DI9kciWiLBmj34RswQISkAd0oAo0gS4wAixgDRyAM3AD3iAAhIBIEAOWAy5IAmlABLJBPtgACkEx2AF2g2pwANSBetAEToI2cAZcBFfADXALDIBHQAqGwUswAd6BaQiC8BAVokGqkBakD5lC1hAbWgh5Q0FQOBQDxUOJkBCSQPnQJqgYKoOqoUNQPfQjdBq6CF2D+qAH0CA0Bv0BfYQRmALTYQ3YALaA2bA7HAhHwsvgRHgVnAcXwNvhSrgWPg63whfhG/AALIVfwpMIQMgIA9FGWAgb8URCkFgkAREha5EipAKpRZqQDqQbuY1IkXHkAwaHoWGYGBbGGeOHWYzhYlZh1mJKMNWYY5hWTBfmNmYQM4H5gqVi1bGmWCesP3YJNhGbjS3EVmCPYFuwl7ED2GHsOxwOx8AZ4hxwfrgYXDJuNa4Etw/XjLuA68MN4SbxeLwq3hTvgg/Bc/BifCG+Cn8cfx7fjx/GvyeQCVoEa4IPIZYgJGwkVBAaCOcI/YQRwjRRgahPdCKGEHnEXGIpsY7YQbxJHCZOkxRJhiQXUiQpmbSBVElqIl0mPSa9IZPJOmRHchhZQF5PriSfIF8lD5I/UJQoJhRPShxFQtlOOUq5QHlAeUOlUg2obtRYqpi6nVpPvUR9Sn0vR5Mzl/OX48mtk6uRa5Xrl3slT5TXl3eXXy6fJ18hf0r+pvy4AlHBQMFTgaOwVqFG4bTCPYVJRZqilWKIYppiiWKD4jXFUSW8koGStxJPqUDpsNIlpSEaQtOledK4tE20Otpl2jAdRzek+9OT6cX0H+i99AllJWVb5SjlHOUa5bPKUgbCMGD4M1IZpYyTjLuMj/M05rnP48/bNq9pXv+8KZX5Km4qfJUilWaVAZWPqkxVb9UU1Z2qbapP1DBqJmphatlq+9Uuq43Pp893ns+dXzT/5PyH6rC6iXq4+mr1w+o96pMamhq+GhkaVRqXNMY1GZpumsma5ZrnNMe0aFoLtQRa5VrntV4wlZnuzFRmJbOLOaGtru2nLdE+pN2rPa1jqLNYZ6NOs84TXZIuWzdBt1y3U3dCT0svWC9fr1HvoT5Rn62fpL9Hv1t/ysDQINpgi0GbwaihiqG/YZ5ho+FjI6qRq9Eqo1qjO8Y4Y7ZxivE+41smsImdSZJJjclNU9jU3lRgus+0zwxr5mgmNKs1u8eisNxZWaxG1qA5wzzIfKN5m/krCz2LWIudFt0WXyztLFMt6ywfWSlZBVhttOqw+sPaxJprXWN9x4Zq42Ozzqbd5rWtqS3fdr/tfTuaXbDdFrtOu8/2DvYi+yb7MQc9h3iHvQ732HR2KLuEfdUR6+jhuM7xjOMHJ3snsdNJp9+dWc4pzg3OowsMF/AX1C0YctFx4bgccpEuZC6MX3hwodRV25XjWuv6zE3Xjed2xG3E3dg92f24+ysPSw+RR4vHlKeT5xrPC16Il69XkVevt5L3Yu9q76c+Oj6JPo0+E752vqt9L/hh/QL9dvrd89fw5/rX+08EOASsCegKpARGBFYHPgsyCRIFdQTDwQHBu4IfL9JfJFzUFgJC/EN2hTwJNQxdFfpzGC4sNKwm7Hm4VXh+eHcELWJFREPEu0iPyNLIR4uNFksWd0bJR8VF1UdNRXtFl0VLl1gsWbPkRoxajCCmPRYfGxV7JHZyqffS3UuH4+ziCuPuLjNclrPs2nK15anLz66QX8FZcSoeGx8d3xD/iRPCqeVMrvRfuXflBNeTu4f7kufGK+eN8V34ZfyRBJeEsoTRRJfEXYljSa5JFUnjAk9BteB1sl/ygeSplJCUoykzqdGpzWmEtPi000IlYYqwK10zPSe9L8M0ozBDuspp1e5VE6JA0ZFMKHNZZruYjv5M9UiMJJslg1kLs2qy3mdHZZ/KUcwR5vTkmuRuyx3J88n7fjVmNXd1Z752/ob8wTXuaw6thdauXNu5Tnddwbrh9b7rj20gbUjZ8MtGy41lG99uit7UUaBRsL5gaLPv5sZCuUJR4b0tzlsObMVsFWzt3WazrWrblyJe0fViy+KK4k8l3JLr31l9V/ndzPaE7b2l9qX7d+B2CHfc3em681iZYlle2dCu4F2t5czyovK3u1fsvlZhW3FgD2mPZI+0MqiyvUqvakfVp+qk6oEaj5rmvep7t+2d2sfb17/fbX/TAY0DxQc+HhQcvH/I91BrrUFtxWHc4azDz+ui6rq/Z39ff0TtSPGRz0eFR6XHwo911TvU1zeoN5Q2wo2SxrHjccdv/eD1Q3sTq+lQM6O5+AQ4ITnx4sf4H++eDDzZeYp9qukn/Z/2ttBailqh1tzWibakNml7THvf6YDTnR3OHS0/m/989Iz2mZqzymdLz5HOFZybOZ93fvJCxoXxi4kXhzpXdD66tOTSna6wrt7LgZevXvG5cqnbvfv8VZerZ645XTt9nX297Yb9jdYeu56WX+x+aem172296XCz/ZbjrY6+BX3n+l37L972un3ljv+dGwOLBvruLr57/17cPel93v3RB6kPXj/Mejj9aP1j7OOiJwpPKp6qP6391fjXZqm99Oyg12DPs4hnj4a4Qy//lfmvT8MFz6nPK0a0RupHrUfPjPmM3Xqx9MXwy4yX0+OFvyn+tveV0auffnf7vWdiycTwa9HrmT9K3qi+OfrW9m3nZOjk03dp76anit6rvj/2gf2h+2P0x5Hp7E/4T5WfjT93fAn88ngmbWbm3/eE8/syOll+AAAACXBIWXMAAAsTAAALEwEAmpwYAAADqmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgICAgICAgICAgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyI+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPnhtcC5kaWQ6M0ExNEY4NjZBNkU1MTFFMTlGMkFGQ0QyNTUyN0VDRjY8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpEZXJpdmVkRnJvbSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgIDxzdFJlZjppbnN0YW5jZUlEPnhtcC5paWQ6M0ExNEY4NjNBNkU1MTFFMTlGMkFGQ0QyNTUyN0VDRjY8L3N0UmVmOmluc3RhbmNlSUQ+CiAgICAgICAgICAgIDxzdFJlZjpkb2N1bWVudElEPnhtcC5kaWQ6M0ExNEY4NjRBNkU1MTFFMTlGMkFGQ0QyNTUyN0VDRjY8L3N0UmVmOmRvY3VtZW50SUQ+CiAgICAgICAgIDwveG1wTU06RGVyaXZlZEZyb20+CiAgICAgICAgIDx4bXBNTTpJbnN0YW5jZUlEPnhtcC5paWQ6M0ExNEY4NjVBNkU1MTFFMTlGMkFGQ0QyNTUyN0VDRjY8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+QWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2g8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+ChetDDYAAACaSURBVEgNY2AYboAR3UNnzpx5DxQTQBP/YGJiIogmBuYSq54Ji2Z0S0BKsInBtGKTwxDDZhHMAKrSdLOIqq7GZxjj+fPnBf7+/bseqMgBn0IK5A4wMzMHMtHYEpD7HEB2gOLIAcSjMXCgW2IYtYjsqBwNutGgg4fAaGKABwWpDLoG3QFSXUeG+gNMoEoJqJGWloErPjIcR54WALqPHeiJgl15AAAAAElFTkSuQmCC";
 const makeHTML = fn =>
   "data:text/html;charset=utf-8,<script>(" + fn + ")();</script>";
 
 
 const test = function(unit) {
   return function*(assert) {
@@ -228,9 +230,93 @@ exports["test communication with debugge
     text: "ping"
   }, "received message back from root");
 
   yield closeToolbox();
 
   assert.equal(panel.readyState, "destroyed", "panel is destroyed");
 });
 
+
+exports["test viewFor panel"] = test(function*(assert) {
+  const url = "data:text/html;charset=utf-8,viewFor";
+  const MyPanel = Class({
+    extends: Panel,
+    label: "view for panel",
+    tooltip: "my panel view",
+    icon: iconURI,
+    url: url
+  });
+
+  const myTool = new Tool({
+    panels: {
+      myPanel: MyPanel
+    }
+  });
+
+
+  const toolbox = yield openToolbox(MyPanel);
+  const panel = yield getCurrentPanel(toolbox);
+  assert.ok(panel instanceof MyPanel, "is instance of MyPanel");
+
+  const frame = viewFor(panel);
+
+  assert.equal(frame.nodeName.toLowerCase(), "iframe",
+               "viewFor(panel) returns associated iframe");
+
+  yield panel.loaded();
+
+  assert.equal(frame.contentDocument.URL, url, "is expected iframe");
+
+  yield closeToolbox();
+});
+
+
+exports["test createView panel"] = test(function*(assert) {
+  var frame = null;
+  var panel = null;
+
+  const url = "data:text/html;charset=utf-8,createView";
+  const id = Math.random().toString(16).substr(2);
+  const MyPanel = Class({
+    extends: Panel,
+    label: "create view",
+    tooltip: "panel creator",
+    icon: iconURI,
+    url: url
+  });
+
+  createView.define(MyPanel, (instance, document) => {
+    var view = document.createElement("iframe");
+    view.setAttribute("type", "content");
+
+    // save instances for later asserts
+    frame = view;
+    panel = instance;
+
+    return view;
+  });
+
+  const myTool = new Tool({
+    panels: {
+      myPanel: MyPanel
+    }
+  });
+
+
+  const toolbox = yield openToolbox(MyPanel);
+  const myPanel = yield getCurrentPanel(toolbox);
+
+  assert.equal(myPanel, panel,
+               "panel passed to createView is one instantiated");
+  assert.equal(viewFor(panel), frame,
+               "createView has created an iframe");
+
+  yield panel.loaded();
+
+  assert.equal(frame.contentDocument.URL, url, "is expected iframe");
+
+  yield closeToolbox();
+});
+
+
 require("test").run(exports);
+
--- a/addon-sdk/source/test/test-disposable.js
+++ b/addon-sdk/source/test/test-disposable.js
@@ -249,18 +249,17 @@ exports["test disposables are GC-able"] 
       this.fooed = false
       disposals = disposals + 1
     }
   });
 
   let foo1 = Foo(arg1, arg2)
   let foo2 = Foo(arg1, arg2)
 
-  let foo1 = null
-  let foo2 = null
+  foo1 = foo2 = null;
 
   Cu.schedulePreciseGC(function() {
     loader.unload();
     assert.equal(disposals, 0, "GC removed dispose listeners");
     done();
   });
 }
 
@@ -357,9 +356,9 @@ exports["test multiple destroy"] = funct
   foo2.destroy();
   assert.equal(disposals, 2, "but not twice");
 
   loader.unload();
 
   assert.equal(disposals, 3, "unload only disposed the remaining instance");
 }
 
-require('test').run(exports);
+require('sdk/test').run(exports);
--- a/addon-sdk/source/test/test-loader.js
+++ b/addon-sdk/source/test/test-loader.js
@@ -362,16 +362,16 @@ exports['test shared globals'] = functio
   assert.ok(Cu.getGlobalForObject(m).foo, "main is shared");
   assert.ok(Cu.getGlobalForObject(a).foo, "a is shared");
   assert.ok(!Cu.getGlobalForObject(b).foo, "b isn't shared");
 
   unload(loader);
 }
 
 exports["test require#resolve"] = function(assert) {
-  let root = require.resolve("sdk/tabs").replace(/commonjs\.path\/(.*)$/, "") + "commonjs.path/";
-  assert.ok(/^resource:\/\/extensions\.modules\.[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}-at-jetpack\.commonjs\.path\/$/.test(root), "correct resolution root");
+  let foundRoot = require.resolve("sdk/tabs").replace(/sdk\/tabs.js$/, "");
+  assert.ok(root, foundRoot, "correct resolution root");
 
-  assert.equal(root + "sdk/tabs.js", require.resolve("sdk/tabs"), "correct resolution of sdk module");
-  assert.equal(root + "toolkit/loader.js", require.resolve("toolkit/loader"), "correct resolution of sdk module");
+  assert.equal(foundRoot + "sdk/tabs.js", require.resolve("sdk/tabs"), "correct resolution of sdk module");
+  assert.equal(foundRoot + "toolkit/loader.js", require.resolve("toolkit/loader"), "correct resolution of sdk module");
 };
 
 require('test').run(exports);
--- a/addon-sdk/source/test/test-match-pattern.js
+++ b/addon-sdk/source/test/test-match-pattern.js
@@ -25,16 +25,18 @@ exports.testMatchPatternTestTrue = funct
   ok("http://example.com/*", "http://example.com/potato-salad");
   ok("http://example.com/pickles/*", "http://example.com/pickles/");
   ok("http://example.com/pickles/*", "http://example.com/pickles/lemonade");
 
   ok("http://example.com", "http://example.com");
   ok("http://example.com/ice-cream", "http://example.com/ice-cream");
 
   ok(/.*zilla.*/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753");
+  ok(/.*A.*/i, "http://A.com");
+  ok(/.*A.*/i, "http://a.com");
   ok(/https:.*zilla.*/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753");
   ok('*.sample.com', 'http://ex.sample.com/foo.html');
   ok('*.amp.le.com', 'http://ex.amp.le.com');
 
   ok('data:*', 'data:text/html;charset=utf-8,');
 };
 
 exports.testMatchPatternTestFalse = function(assert) {
@@ -104,22 +106,16 @@ exports.testMatchPatternErrors = functio
 
   assert.throws(
     function() new MatchPattern(/ /g),
     /^A RegExp match pattern cannot be set to `global` \(i\.e\. \/\/g\)\.$/,
     "MatchPattern throws on a RegExp set to `global` (i.e. //g)."
   );
 
   assert.throws(
-    function() new MatchPattern(/ /i),
-    /^A RegExp match pattern cannot be set to `ignoreCase` \(i\.e\. \/\/i\)\.$/,
-    "MatchPattern throws on a RegExp set to `ignoreCase` (i.e. //i)."
-  );
-
-  assert.throws(
     function() new MatchPattern( / /m ),
     /^A RegExp match pattern cannot be set to `multiline` \(i\.e\. \/\/m\)\.$/,
     "MatchPattern throws on a RegExp set to `multiline` (i.e. //m)."
   );
 };
 
 exports.testMatchPatternInternals = function(assert) {
   assert.equal(
--- a/addon-sdk/source/test/test-native-loader.js
+++ b/addon-sdk/source/test/test-native-loader.js
@@ -160,18 +160,17 @@ exports["test require#resolve with relat
       rootURI: rootURI,
       manifest: manifest,
       isNative: true
     });
 
     let program = main(loader);
     let fixtureRoot = program.require.resolve("./").replace(/native-addon-test\/(.*)/, "") + "native-addon-test/";
 
-    assert.ok(/^resource:\/\/[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}-at-jetpack\/addon-sdk\/tests\/fixtures\/native-addon-test\/$/.test(fixtureRoot),
-      "correct resolution root");
+    assert.equal(root + "/fixtures/native-addon-test/", fixtureRoot, "correct resolution root");
     assert.equal(program.require.resolve("test-math"), fixtureRoot + "node_modules/test-math/index.js", "works with node_modules");
     assert.equal(program.require.resolve("./newmodule"), fixtureRoot + "newmodule/lib/file.js", "works with directory mains");
     assert.equal(program.require.resolve("./dir/a"), fixtureRoot + "dir/a.js", "works with normal relative module lookups");
     assert.equal(program.require.resolve("modules/Promise.jsm"), "resource://gre/modules/Promise.jsm", "works with path lookups");
 
     // TODO bug 1050422, handle loading non JS/JSM file paths
     // assert.equal(program.require.resolve("test-assets/styles.css"), fixtureRoot + "node_modules/test-assets/styles.css",
     // "works with different file extension lookups in dependencies");
--- a/addon-sdk/source/test/test-panel.js
+++ b/addon-sdk/source/test/test-panel.js
@@ -11,21 +11,20 @@ module.metadata = {
 
 const { Cc, Ci } = require("chrome");
 const { Loader } = require('sdk/test/loader');
 const { LoaderWithHookedConsole } = require("sdk/test/loader");
 const { setTimeout } = require("sdk/timers");
 const self = require('sdk/self');
 const { open, close, focus, ready } = require('sdk/window/helpers');
 const { isPrivate } = require('sdk/private-browsing');
-const { isWindowPBSupported, isGlobalPBSupported } = require('sdk/private-browsing/utils');
+const { isWindowPBSupported } = require('sdk/private-browsing/utils');
 const { defer, all } = require('sdk/core/promise');
 const { getMostRecentBrowserWindow } = require('sdk/window/utils');
 const { getWindow } = require('sdk/panel/window');
-const { pb } = require('./private-browsing/helper');
 const { URL } = require('sdk/url');
 const { wait } = require('./event/helpers');
 
 const fixtures = require('./fixtures')
 
 const SVG_URL = fixtures.url('mofo_logo.SVG');
 
 const Isolate = fn => '(' + fn + ')()';
@@ -1001,17 +1000,17 @@ exports['test panel CSS'] = function(ass
     contentStyle: 'div { height: 100px; }',
     contentStyleFile: [fixtures.url("include-file.css"), "./border-style.css"],
     onShow: () => {
       ready(getContentWindow(panel)).then(({ window, document }) => {
         let div = document.querySelector('div');
 
       assert.equal(div.clientHeight, 100,
         "Panel contentStyle worked");
-   
+
       assert.equal(div.offsetHeight, 120,
         "Panel contentStyleFile worked");
 
       assert.equal(window.getComputedStyle(div).borderTopStyle, "dashed",
         "Panel contentStyleFile with relative path worked");
 
         loader.unload();
         done();
@@ -1039,17 +1038,17 @@ exports['test panel contentScriptFile'] 
   const getContentWindow = panel =>
     getActiveView(panel).querySelector('iframe').contentWindow;
 
   let whenMessage = defer();
   let whenShown = defer();
 
   let panel = Panel({
     contentURL: './test.html',
-    contentScriptFile: "./test-contentScriptFile.js", 
+    contentScriptFile: "./test-contentScriptFile.js",
     onMessage: (message) => {
       assert.equal(message, "msg from contentScriptFile",
         "Panel contentScriptFile with relative path worked");
 
       whenMessage.resolve();
     },
     onShow: () => {
       ready(getContentWindow(panel)).then(({ document }) => {
@@ -1150,17 +1149,17 @@ exports['test panel contextmenu validati
 
   assert.equal(panel.contextMenu, true,
     'contextMenu option is `true`');
 
   panel.contextMenu = false;
 
   assert.equal(panel.contextMenu, false,
     'contextMenu option accepts boolean values');
-  
+
   assert.throws(() =>
     Panel({contextMenu: 1}),
     /The option "contextMenu" must be one of the following types: boolean, undefined, null/,
     'contextMenu only accepts boolean or nil values');
 
   panel = Panel();
 
   assert.throws(() =>
@@ -1230,29 +1229,29 @@ exports['test panel contextmenu disabled
 
   let { sendMouseEvent } = window.QueryInterface(Ci.nsIInterfaceRequestor).
                                     getInterface(Ci.nsIDOMWindowUtils);
 
   yield ready(window);
 
   assert.equal(contextmenu.state, 'closed',
     'contextmenu must be closed');
-  
+
   sendMouseEvent('contextmenu', 20, 20, 2, 1, 0);
 
   contextmenu.addEventListener('popupshown', listener);
 
   yield wait(1000);
 
   contextmenu.removeEventListener('popupshown', listener);
 
   assert.equal(contextmenu.state, 'closed',
     'contextmenu was never open');
 
-  loader.unload();  
+  loader.unload();
 }
 
 exports["test panel addon global object"] = function*(assert) {
   const { merge } = require("sdk/util/object");
 
   let loader = Loader(module, null, null, {
     modules: {
       "sdk/self": merge({}, self, {
@@ -1269,17 +1268,17 @@ exports["test panel addon global object"
 
   panel.show();
 
   yield wait(panel, "show");
 
   panel.port.emit('addon-to-document', 'ok');
 
   yield wait(panel.port, "document-to-addon");
- 
+
   assert.pass("Received an event from the document");
 
   loader.unload();
 }
 
 if (isWindowPBSupported) {
   exports.testGetWindow = function(assert, done) {
     let activeWindow = getMostRecentBrowserWindow();
@@ -1290,33 +1289,10 @@ if (isWindowPBSupported) {
     } }).then(window => {
       assert.ok(isPrivate(window), 'window is private');
       assert.equal(getWindow(window.gBrowser), null, 'private window elements returns null');
       assert.equal(getWindow(activeWindow.gBrowser), activeWindow, 'non-private window elements returns window');
       return window;
     }).then(close).then(done).then(null, assert.fail);
   }
 }
-else if (isGlobalPBSupported) {
-  exports.testGetWindow = function(assert, done) {
-    let activeWindow = getMostRecentBrowserWindow();
 
-    assert.equal(getWindow(activeWindow.gBrowser), activeWindow, 'non-private window elements returns window');
-    pb.once('start', function() {
-      assert.ok(isPrivate(activeWindow), 'window is private');
-      assert.equal(getWindow(activeWindow.gBrowser), activeWindow, 'private window elements returns window');
-      open(null, { features: {
-        toolbar: true,
-        chrome: true
-      } }).then(window => {
-        assert.ok(isPrivate(window), 'window is private');
-        assert.equal(getWindow(window.gBrowser), window, 'private window elements returns window');
-        assert.equal(getWindow(activeWindow.gBrowser), activeWindow, 'active window elements returns window');
-
-        pb.once('stop', done);
-        pb.deactivate();
-      })
-    });
-    pb.activate();
-  }
-}
-
-require("test").run(exports);
+require("sdk/test").run(exports);
--- a/addon-sdk/source/test/test-plain-text-console.js
+++ b/addon-sdk/source/test/test-plain-text-console.js
@@ -13,16 +13,17 @@ const SDK_LOG_LEVEL_PREF = "extensions.s
 
 const HAS_ORIGINAL_ADDON_LOG_LEVEL = prefs.has(ADDON_LOG_LEVEL_PREF);
 const ORIGINAL_ADDON_LOG_LEVEL = prefs.get(ADDON_LOG_LEVEL_PREF);
 const HAS_ORIGINAL_SDK_LOG_LEVEL = prefs.has(SDK_LOG_LEVEL_PREF);
 const ORIGINAL_SDK_LOG_LEVEL = prefs.get(SDK_LOG_LEVEL_PREF);
 
 exports.testPlainTextConsole = function(assert) {
   let prints = [];
+  let tbLines;
   function print(message) {
     prints.push(message);
   }
   function lastPrint() {
     let last = prints.slice(-1)[0];
     prints = [];
     return last;
   }
@@ -75,42 +76,42 @@ exports.testPlainTextConsole = function(
   con.log("testing", { toString: function() "obj.toString()" });
   assert.equal(lastPrint(), "console.log: " + name + ": testing {}\n",
                    "PlainTextConsole.log() doesn't printify custom toString.");
 
   con.log("testing", { toString: function() { throw "fail!"; } });
   assert.equal(lastPrint(), "console.log: " + name + ": testing {}\n",
                    "PlainTextConsole.log() must stringify custom bad toString.");
 
-
   con.exception(new Error("blah"));
 
-
-  assert.equal(prints[0], "console.error: " + name + ": \n");
-  let tbLines = prints[1].split("\n");
-  assert.equal(tbLines[0], "  Message: Error: blah");
-  assert.equal(tbLines[1], "  Stack:");
-  assert.ok(prints[1].indexOf(module.uri + ":84") !== -1);
+  assert.equal(prints[0], "console.error: " + name + ": \n", "prints[0] is correct");
+  tbLines = prints[1].split("\n");
+  assert.equal(tbLines[0], "  Message: Error: blah", "tbLines[0] is correct");
+  assert.equal(tbLines[1], "  Stack:", "tbLines[1] is correct");
+  let lineNumber = prints[1].match(module.uri + ":(\\d+)")[1];
+  assert.equal(lineNumber, "84", "line number is correct")
+  assert.ok(prints[1].indexOf(module.uri + ":84") !== -1, "line number is correct");
   prints = []
 
   try {
     loadSubScript("invalid-url", {});
     assert.fail("successed in calling loadSubScript with invalid-url");
   }
   catch(e) {
     con.exception(e);
   }
-  assert.equal(prints[0], "console.error: " + name + ": \n");
-  assert.equal(prints[1], "  Error creating URI (invalid URL scheme?)\n");
+  assert.equal(prints[0], "console.error: " + name + ": \n", "prints[0] is correct");
+  assert.equal(prints[1], "  Error creating URI (invalid URL scheme?)\n", "prints[1] is correct");
   prints = [];
 
   con.trace();
-  let tbLines = prints[0].split("\n");
-  assert.equal(tbLines[0], "console.trace: " + name + ": ");
-  assert.ok(tbLines[1].indexOf("_ain-text-console.js 105") == 0);
+  tbLines = prints[0].split("\n");
+  assert.equal(tbLines[0], "console.trace: " + name + ": ", "contains correct console.trace");
+  assert.ok(tbLines[1].indexOf("_ain-text-console.js 106") == 0);
   prints = [];
 
   // Whether or not console methods should print at the various log levels,
   // structured as a hash of levels, each of which contains a hash of methods,
   // each of whose value is whether or not it should print, i.e.:
   // { [level]: { [method]: [prints?], ... }, ... }.
   let levels = {
     all:   { debug: true,  log: true,  info: true,  warn: true,  error: true  },
@@ -162,16 +163,17 @@ exports.testPlainTextConsole = function(
   assert.equal(lastPrint(), null,
                    "addon log level 'off' overrides SDK log level 'all'");
 
   restorePrefs();
 };
 
 exports.testPlainTextConsoleBoundMethods = function(assert) {
   let prints = [];
+  let tbLines;
   function print(message) {
     prints.push(message);
   }
   function lastPrint() {
     let last = prints.slice(-1)[0];
     prints = [];
     return last;
   }
@@ -202,34 +204,36 @@ exports.testPlainTextConsoleBoundMethods
   assert.equal(prints[1], "  testing\n")
   assert.equal(prints[2], "  1\n")
   assert.equal(prints[3], "Array\n    - 0 = 2\n    - 1 = 3\n    - 2 = 4\n    - length = 3\n");
   prints = [];
 
   debug('testing', 1, [2, 3, 4]);
   assert.equal(prints[0], "console.debug: " + name + ": \n",
                    "PlainTextConsole.debug() must work.");
-  assert.equal(prints[1], "  testing\n")
-  assert.equal(prints[2], "  1\n")
-  assert.equal(prints[3], "Array\n    - 0 = 2\n    - 1 = 3\n    - 2 = 4\n    - length = 3\n");
+  assert.equal(prints[1], "  testing\n", "prints[1] is correct");
+  assert.equal(prints[2], "  1\n", "prints[2] is correct");
+  assert.equal(prints[3],
+               "Array\n    - 0 = 2\n    - 1 = 3\n    - 2 = 4\n    - length = 3\n",
+               "prints[3] is correct");
   prints = [];
 
   exception(new Error("blah"));
 
-  assert.equal(prints[0], "console.error: " + name + ": \n");
-  let tbLines = prints[1].split("\n");
-  assert.equal(tbLines[0], "  Message: Error: blah");
-  assert.equal(tbLines[1], "  Stack:");
-  assert.ok(prints[1].indexOf(module.uri + ":215") !== -1);
+  assert.equal(prints[0], "console.error: " + name + ": \n", "prints[0] is correct");
+  tbLines = prints[1].split("\n");
+  assert.equal(tbLines[0], "  Message: Error: blah", "tbLines[0] is correct");
+  assert.equal(tbLines[1], "  Stack:", "tbLines[1] is correct");
+  assert.ok(prints[1].indexOf(module.uri + ":219") !== -1, "correct line number");
   prints = []
 
   trace();
-  let tbLines = prints[0].split("\n");
-  assert.equal(tbLines[0], "console.trace: " + name + ": ");
-  assert.ok(tbLines[1].indexOf("_ain-text-console.js 224") === 0);
+  tbLines = prints[0].split("\n");
+  assert.equal(tbLines[0], "console.trace: " + name + ": ", "console.trace is correct");
+  assert.ok(tbLines[1].indexOf("_ain-text-console.js 228") === 0, "correct line number");
   prints = [];
 
   restorePrefs();
 };
 
 exports.testConsoleInnerID = function(assert) {
   let Console = require("sdk/console/plain-text").PlainTextConsole;
   let { log, info, warn, error, debug, exception, trace } = new Console(function() {}, "test ID");
@@ -266,9 +270,9 @@ function restorePrefs() {
     prefs.reset(ADDON_LOG_LEVEL_PREF);
 
   if (HAS_ORIGINAL_SDK_LOG_LEVEL)
     prefs.set(SDK_LOG_LEVEL_PREF, ORIGINAL_SDK_LOG_LEVEL);
   else
     prefs.reset(SDK_LOG_LEVEL_PREF);
 }
 
-require("test").run(exports);
+require("sdk/test").run(exports);
--- a/addon-sdk/source/test/test-private-browsing.js
+++ b/addon-sdk/source/test/test-private-browsing.js
@@ -8,36 +8,25 @@ const { safeMerge } = require('sdk/util/
 const windows = require('sdk/windows').browserWindows;
 const tabs = require('sdk/tabs');
 const winUtils = require('sdk/window/utils');
 const { isWindowPrivate } = winUtils;
 const { isPrivateBrowsingSupported } = require('sdk/self');
 const { is } = require('sdk/system/xul-app');
 const { isPrivate } = require('sdk/private-browsing');
 const { LoaderWithHookedConsole } = require("sdk/test/loader");
-const { getMode, isGlobalPBSupported,
-        isWindowPBSupported, isTabPBSupported } = require('sdk/private-browsing/utils');
+const { getMode, isWindowPBSupported, isTabPBSupported } = require('sdk/private-browsing/utils');
 const { pb } = require('./private-browsing/helper');
 const prefs = require('sdk/preferences/service');
-const { set: setPref } = require("sdk/preferences/service");
-const DEPRECATE_PREF = "devtools.errorconsole.deprecation_warnings";
 
 const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
 
 const kAutoStartPref = "browser.privatebrowsing.autostart";
 
-// is global pb is enabled?
-if (isGlobalPBSupported) {
-  safeMerge(module.exports, require('./private-browsing/global'));
-
-  exports.testGlobalOnlyOnFirefox = function(assert) {
-    assert.ok(is("Firefox"), "isGlobalPBSupported is only true on Firefox");
-  }
-}
-else if (isWindowPBSupported) {
+if (isWindowPBSupported) {
   safeMerge(module.exports, require('./private-browsing/windows'));
 
   exports.testPWOnlyOnFirefox = function(assert) {
     assert.ok(is("Firefox"), "isWindowPBSupported is only true on Firefox");
   }
 }
 // only on Fennec
 else if (isTabPBSupported) {
@@ -53,36 +42,29 @@ exports.testIsPrivateDefaults = function
   assert.equal(isPrivate('test'), false, 'strings are not private');
   assert.equal(isPrivate({}), false, 'random objects are not private');
   assert.equal(isPrivate(4), false, 'numbers are not private');
   assert.equal(isPrivate(/abc/), false, 'regex are not private');
   assert.equal(isPrivate(function() {}), false, 'functions are not private');
 };
 
 exports.testWindowDefaults = function(assert) {
-  setPref(DEPRECATE_PREF, true);
   // Ensure that browserWindow still works while being deprecated
   let { loader, messages } = LoaderWithHookedConsole(module);
   let windows = loader.require("sdk/windows").browserWindows;
-  assert.equal(windows.activeWindow.isPrivateBrowsing, false,
-                   'window is not private browsing by default');
-  assert.ok(/DEPRECATED.+isPrivateBrowsing/.test(messages[0].msg),
-                     'isPrivateBrowsing is deprecated');
+  assert.equal(windows.activeWindow.isPrivateBrowsing, undefined,
+              'window.isPrivateBrowsing is undefined');
+  assert.equal(undefined, messages[0],
+               'isPrivateBrowsing is deprecated');
 
   let chromeWin = winUtils.getMostRecentBrowserWindow();
   assert.equal(getMode(chromeWin), false);
   assert.equal(isWindowPrivate(chromeWin), false);
 };
 
-// tests for the case where private browsing doesn't exist
-exports.testIsActiveDefault = function(assert) {
-  assert.equal(pb.isActive, false,
-                   'pb.isActive returns false when private browsing isn\'t supported');
-};
-
 exports.testIsPrivateBrowsingFalseDefault = function(assert) {
   assert.equal(isPrivateBrowsingSupported, false,
   	               'isPrivateBrowsingSupported property is false by default');
 };
 
 exports.testNSIPrivateBrowsingChannel = function(assert) {
   let channel = Services.io.newChannel("about:blank", null, null);
   channel.QueryInterface(Ci.nsIPrivateBrowsingChannel);
--- a/addon-sdk/source/test/test-self.js
+++ b/addon-sdk/source/test/test-self.js
@@ -1,21 +1,18 @@
 /* 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";
 
-const { Cc, Ci, Cu, Cm, components } = require("chrome");
 const xulApp = require("sdk/system/xul-app");
 const self = require("sdk/self");
 const { Loader, main, unload } = require("toolkit/loader");
 const loaderOptions = require("@loader/options");
 
-const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
-
 exports.testSelf = function(assert) {
   // Likewise, we can't assert anything about the full URL, because that
   // depends on self.id . We can only assert that it ends in the right
   // thing.
   var url = self.data.url("test-content-symbiont.js");
   assert.equal(typeof(url), "string", "self.data.url('x') returns string");
   assert.equal(/\/test-content-symbiont\.js$/.test(url), true);
 
--- a/addon-sdk/source/test/test-simple-prefs.js
+++ b/addon-sdk/source/test/test-simple-prefs.js
@@ -12,17 +12,17 @@ const { prefs: sp } = simplePrefs;
 const { defer, resolve, reject, all } = require("sdk/core/promise");
 const AddonInstaller = require("sdk/addon/installer");
 const fixtures = require("./fixtures");
 const { pathFor } = require("sdk/system");
 const file = require("sdk/io/file");
 const { install, uninstall } = require("sdk/addon/installer");
 const { open } = require('sdk/preferences/utils');
 const { toFilename } = require('sdk/url');
-const { AddonManager } = Cu.import('resource://gre/modules/AddonManager.jsm', {});
+const { getAddonByID } = require('sdk/addon/manager');
 const { ZipWriter } = require('./zip/utils');
 const { getTabForId } = require('sdk/tabs/utils');
 const { preferencesBranch, id } = require('sdk/self');
 const { Tab } = require('sdk/tabs/tab');
 require('sdk/tabs');
 
 const prefsrv = Cc['@mozilla.org/preferences-service;1'].
                     getService(Ci.nsIPrefService);
@@ -39,17 +39,17 @@ exports.testIterations = function(assert
   assert.ok("test" in sp);
   assert.ok(!sp.getPropertyDescriptor);
   assert.ok(Object.prototype.hasOwnProperty.call(sp, "test"));
   assert.equal(["test", "test.test"].toString(), prefAry.sort().toString(), "for (x in y) part 1/2 works");
   assert.equal(["test", "test.test"].toString(), Object.keys(sp).sort().toString(), "Object.keys works");
 
   delete sp["test"];
   delete sp["test.test"];
-  let prefAry = [];
+  prefAry = [];
   for (var name in sp ) {
     prefAry.push(name);
   }
   assert.equal([].toString(), prefAry.toString(), "for (x in y) part 2/2 works");
 }
 
 exports.testSetGetBool = function(assert) {
   assert.equal(sp.test, undefined, "Value should not exist");
@@ -242,109 +242,91 @@ exports.testPrefUnloadWildcardListener =
 exports.testPrefJSONStringification = function(assert) {
   var sp = require("sdk/simple-prefs").prefs;
   assert.equal(
       Object.keys(sp).join(),
       Object.keys(JSON.parse(JSON.stringify(sp))).join(),
       "JSON stringification should work.");
 };
 
-exports.testUnloadOfDynamicPrefGeneration = function(assert, done) {
+exports.testUnloadOfDynamicPrefGeneration = function*(assert) {
   let loader = Loader(module);
   let branch = prefsrv.getDefaultBranch('extensions.' + preferencesBranch);
 
   let { enable } = loader.require("sdk/preferences/native-options");
 
   let addon_id = "test-bootstrap-addon@mozilla.com";
   let xpi_path = file.join(pathFor("ProfD"), addon_id + ".xpi");
 
   // zip the add-on
   let zip = new ZipWriter(xpi_path);
   assert.pass("start creating the xpi");
-  zip.addFile("", toFilename(fixtures.url("bootstrap-addon/"))).
-  then(zip.close()).
-  then(_ => install(xpi_path)).
+  zip.addFile("", toFilename(fixtures.url("bootstrap-addon/")));
+  yield zip.close();
+
+  // insatll the add-on
+  let id = yield install(xpi_path);
+
   // get the addon
-  then(id => {
-    let { promise, resolve } = defer();
-    AddonManager.getAddonByID(id, resolve);
-    return promise;
-  }).
-  // insatll the add-on
-  then(addon => {
-    assert.pass('installed');
+  let addon = yield getAddonByID(id);
+
+  assert.pass('installed');
 
-    assert.pass('addon id: ' + addon.id);
-    addon.userDisabled = false;
-    assert.ok(!addon.userDisabled, 'the add-on is enabled');
-    assert.ok(addon.isActive, 'the add-on is enabled');
+  assert.pass('addon id: ' + addon.id);
+  addon.userDisabled = false;
+  assert.ok(!addon.userDisabled, 'the add-on is enabled');
+  assert.ok(addon.isActive, 'the add-on is enabled');
 
-    // setup dynamic prefs
-    return enable({
-      id: addon.id,
-      preferences: [{
-        "name": "test",
-        "description": "test",
-        "title": "Test",
-        "type": "string",
-        "value": "default"
-      }, {
-        "name": "test-int",
-        "description": "test",
-        "type": "integer",
-        "value": 5,
-        "title": "How Many?"
-      }]
-    });
-  }).
-  then(args => {
-    assert.pass('enabled');
-    return args;
-  }).
+  // setup dynamic prefs
+  yield enable({
+    id: addon.id,
+    preferences: [{
+      "name": "test",
+      "description": "test",
+      "title": "Test",
+      "type": "string",
+      "value": "default"
+    }, {
+      "name": "test-int",
+      "description": "test",
+      "type": "integer",
+      "value": 5,
+      "title": "How Many?"
+    }]
+  });
+
+  assert.pass('enabled');
+
   // show inline prefs
-  then(open).
-  then(args => {
-    assert.pass('opened');
-    return args;
-  }).
+  let { tabId, document } = yield open(addon);
+
+  assert.pass('opened');
   // confirm dynamic pref generation did occur
-  then(args => {
-    let results = args.document.querySelectorAll("*[data-jetpack-id=\"" +args.id + "\"]");
-    assert.ok(results.length > 0, "the prefs were setup");
-    return args;
-  }).
+  let results = document.querySelectorAll("*[data-jetpack-id=\"" + id + "\"]");
+  assert.ok(results.length > 0, "the prefs were setup");
+
   // unload dynamic prefs
-  then(args => {
-    loader.unload();
-    assert.pass('unload');
-    return args;
-  }).
+  loader.unload();
+  assert.pass('unload');
+
   // hide and show the inline prefs
-  then(({ tabId, id, document }) => {
-    let { promise, resolve } = defer();
-    let tab = Tab({ tab: getTabForId(tabId) });
+  let { promise, resolve } = defer();
+  Tab({ tab: getTabForId(tabId) }).close(resolve);
+  yield promise;
 
-    tab.close(_ => resolve({ id: id }));
-
-    return promise;
-  }).
   // reopen the add-on prefs page
-  then(open).
-  // confirm dynamic pref generation did not occur
-  then(({ id, tabId, document }) => {
-    let { promise, resolve } = defer();
-    let tab = Tab({ tab: getTabForId(tabId) });
+  ({ tabId, document }) = yield open(addon);
 
-    let results = document.querySelectorAll("*[data-jetpack-id=\"" + id + "\"]");
-    assert.equal(0, results.length, "the prefs were not setup after unload");
-
-    tab.close(_ => resolve({ id: id }));
+  // confirm dynamic pref generation did not occur
+  ({ promise, resolve }) = defer();
+  results = document.querySelectorAll("*[data-jetpack-id=\"" + id + "\"]");
+  assert.equal(0, results.length, "the prefs were not setup after unload");
+  Tab({ tab: getTabForId(tabId) }).close(resolve);
+  yield promise;
 
-    return promise;
-  }).
   // uninstall the add-on
-  then(({ id }) => uninstall(id)).
+  yield uninstall(id);
+
   // delete the pref branch
-  then(_ => branch.deleteBranch('')).
-  then(done, assert.fail);
+  branch.deleteBranch('');
 }
 
 require("sdk/test").run(exports);
--- a/addon-sdk/source/test/test-tab-utils.js
+++ b/addon-sdk/source/test/test-tab-utils.js
@@ -1,15 +1,14 @@
 'use strict';
 
 const { getTabs } = require('sdk/tabs/utils');
-const { isGlobalPBSupported, isWindowPBSupported, isTabPBSupported } = require('sdk/private-browsing/utils');
+const { isWindowPBSupported, isTabPBSupported } = require('sdk/private-browsing/utils');
 const { browserWindows } = require('sdk/windows');
 const tabs = require('sdk/tabs');
-const { pb } = require('./private-browsing/helper');
 const { isPrivate } = require('sdk/private-browsing');
 const { openTab, closeTab, getTabContentWindow, getOwnerWindow } = require('sdk/tabs/utils');
 const { open, close } = require('sdk/window/helpers');
 const { windows } = require('sdk/window/utils');
 const { getMostRecentBrowserWindow } = require('sdk/window/utils');
 const { fromIterator } = require('sdk/util/array');
 
 if (isWindowPBSupported) {
@@ -59,9 +58,9 @@ else if (isTabPBSupported) {
                  'the last tab is the opened tab');
     assert.equal(browserWindows.length, 1, 'there is only one window');
     closeTab(tab);
 
     done();
   };
 }
 
-require('test').run(exports);
+require('sdk/test').run(exports);
--- a/addon-sdk/source/test/test-tabs-common.js
+++ b/addon-sdk/source/test/test-tabs-common.js
@@ -13,16 +13,17 @@ const { setTimeout } = require('sdk/time
 const { openWebpage } = require('./private-browsing/helper');
 const { isTabPBSupported, isWindowPBSupported } = require('sdk/private-browsing/utils');
 const { getTabContentWindow } = require('sdk/tabs/utils');
 const { attach, detach } = require('sdk/content/mod');
 const { Style } = require('sdk/stylesheet/style');
 const fixtures = require('./fixtures');
 const { viewFor } = require('sdk/view/core');
 const app = require("sdk/system/xul-app");
+const { cleanUI } = require('sdk/test/utils');
 
 const URL = 'data:text/html;charset=utf-8,<html><head><title>#title#</title></head></html>';
 
 // TEST: tab count
 exports.testTabCounts = function(assert, done) {
   tabs.open({
     url: 'about:blank',
     onReady: function(tab) {
--- a/addon-sdk/source/test/test-test-utils.js
+++ b/addon-sdk/source/test/test-test-utils.js
@@ -1,16 +1,17 @@
 /* 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'
 
 const { setTimeout } = require('sdk/timers');
-const { waitUntil } = require('sdk/test/utils');
+const { waitUntil, cleanUI } = require('sdk/test/utils');
+const tabs = require('sdk/tabs');
 
 exports.testWaitUntil = function (assert, done) {
   let bool = false;
   let finished = false;
   waitUntil(() => {
     if (finished)
       assert.fail('interval should be cleared after predicate is truthy');
     return bool;
@@ -38,9 +39,34 @@ exports.testWaitUntilInterval = function
     assert.equal(counter, 1,
       'predicate should only be called once with a higher interval');
     finished = true;
     done();
   });
   setTimeout(() => { bool = true; }, 10);
 };
 
+exports.testCleanUIWithExtraTabAndWindow = function(assert, done) {
+  tabs.open({
+    url: "about:blank",
+    inNewWindow: true,
+    onOpen: () => {
+      cleanUI().then(() => {
+        assert.pass("the ui was cleaned");
+        assert.equal(tabs.length, 1, 'there is only one tab open');
+      }).then(done).catch(assert.fail);
+    }
+  });
+}
+
+exports.testCleanUIWithOnlyExtraTab = function(assert, done) {
+  tabs.open({
+    url: "about:blank",
+    onOpen: () => {
+      cleanUI().then(() => {
+        assert.pass("the ui was cleaned");
+        assert.equal(tabs.length, 1, 'there is only one tab open');
+      }).then(done).catch(assert.fail);
+    }
+  });
+}
+
 require('sdk/test').run(exports);
--- a/addon-sdk/source/test/test-ui-action-button.js
+++ b/addon-sdk/source/test/test-ui-action-button.js
@@ -378,16 +378,19 @@ exports['test button window state'] = fu
     label: 'my button',
     icon: './icon.png'
   });
 
   let mainWindow = browserWindows.activeWindow;
   let nodes = [getWidget(button.id).node];
 
   openBrowserWindow().then(focus).then(window => {
+    let node;
+    let state;
+
     nodes.push(getWidget(button.id, window).node);
 
     let { activeWindow } = browserWindows;
 
     button.state(activeWindow, {
       label: 'New label',
       icon: './new-icon.png',
       disabled: true
@@ -397,26 +400,26 @@ exports['test button window state'] = fu
 
     assert.equal(button.label, 'my button',
       'global label unchanged');
     assert.equal(button.icon, './icon.png',
       'global icon unchanged');
     assert.equal(button.disabled, false,
       'global disabled unchanged');
 
-    let state = button.state(mainWindow);
+    state = button.state(mainWindow);
 
     assert.equal(state.label, 'my button',
       'previous window label unchanged');
     assert.equal(state.icon, './icon.png',
       'previous window icon unchanged');
     assert.equal(state.disabled, false,
       'previous window disabled unchanged');
 
-    let state = button.state(activeWindow);
+    state = button.state(activeWindow);
 
     assert.equal(state.label, 'New label',
       'active window label updated');
     assert.equal(state.icon, './new-icon.png',
       'active window icon updated');
     assert.equal(state.disabled, true,
       'active disabled updated');
 
@@ -434,31 +437,31 @@ exports['test button window state'] = fu
     // delete the window state will inherits the global state again
 
     button.state(activeWindow, null);
 
     assert.equal(button.state(activeWindow).label, 'A good label',
       'active window label inherited');
 
     // check the nodes properties
-    let node = nodes[0];
-    let state = button.state(mainWindow);
+    node = nodes[0];
+    state = button.state(mainWindow);
 
     assert.equal(node.getAttribute('label'), state.label,
       'node label is correct');
     assert.equal(node.getAttribute('tooltiptext'), state.label,
       'node tooltip is correct');
 
     assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
       'node image is correct');
     assert.equal(node.hasAttribute('disabled'), state.disabled,
       'disabled is correct');
 
-    let node = nodes[1];
-    let state = button.state(activeWindow);
+    node = nodes[1];
+    state = button.state(activeWindow);
 
     assert.equal(node.getAttribute('label'), state.label,
       'node label is correct');
     assert.equal(node.getAttribute('tooltiptext'), state.label,
       'node tooltip is correct');
 
     assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
       'node image is correct');
@@ -510,33 +513,35 @@ exports['test button tab state'] = funct
       button.state(tab, {
         icon: './another-tab-icon.png',
         disabled: true
       });
 
       // check the states
 
       Cu.schedulePreciseGC(() => {
+        let state;
+
         assert.equal(button.label, 'my button',
           'global label unchanged');
         assert.equal(button.icon, './icon.png',
           'global icon unchanged');
         assert.equal(button.disabled, false,
           'global disabled unchanged');
 
-        let state = button.state(mainTab);
+        state = button.state(mainTab);
 
         assert.equal(state.label, 'Tab label',
           'previous tab label updated');
         assert.equal(state.icon, './tab-icon.png',
           'previous tab icon updated');
         assert.equal(state.disabled, false,
           'previous tab disabled unchanged');
 
-        let state = button.state(tab);
+        state = button.state(tab);
 
         assert.equal(state.label, 'Window label',
           'active tab inherited from window state');
         assert.equal(state.icon, './another-tab-icon.png',
           'active tab icon updated');
         assert.equal(state.disabled, true,
           'active disabled updated');
 
@@ -556,17 +561,17 @@ exports['test button tab state'] = funct
         // delete the window state
         button.state(activeWindow, null);
 
         assert.equal(button.state(tab).icon, './good-icon.png',
           'tab icon inherited from global');
 
         // check the node properties
 
-        let state = button.state(tabs.activeTab);
+        state = button.state(tabs.activeTab);
 
         assert.equal(node.getAttribute('label'), state.label,
           'node label is correct');
         assert.equal(node.getAttribute('tooltiptext'), state.label,
           'node tooltip is correct');
         assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
           'node image is correct');
         assert.equal(node.hasAttribute('disabled'), state.disabled,
@@ -596,55 +601,55 @@ exports['test button tab state'] = funct
 
         mainTab.activate();
       });
     }
   });
 
 };
 
-exports['test button click'] = function(assert, done) {
+exports['test button click'] = function*(assert) {
   let loader = Loader(module);
   let { ActionButton } = loader.require('sdk/ui');
   let { browserWindows } = loader.require('sdk/windows');
 
   let labels = [];
 
   let button = ActionButton({
     id: 'my-button-8',
     label: 'my button',
     icon: './icon.png',
     onClick: ({label}) => labels.push(label)
   });
 
   let mainWindow = browserWindows.activeWindow;
   let chromeWindow = getMostRecentBrowserWindow();
 
-  openBrowserWindow().then(focus).then(window => {
-    button.state(mainWindow, { label: 'nothing' });
-    button.state(mainWindow.tabs.activeTab, { label: 'foo'})
-    button.state(browserWindows.activeWindow, { label: 'bar' });
+  let window = yield openBrowserWindow().then(focus);
 
-    button.click();
+  button.state(mainWindow, { label: 'nothing' });
+  button.state(mainWindow.tabs.activeTab, { label: 'foo'})
+  button.state(browserWindows.activeWindow, { label: 'bar' });
 
-    focus(chromeWindow).then(() => {
-      button.click();
+  button.click();
 
-      assert.deepEqual(labels, ['bar', 'foo'],
-        'button click works');
+  yield focus(chromeWindow);
+
+  button.click();
 
-      close(window).
-        then(loader.unload).
-        then(done, assert.fail);
-    });
-  }).then(null, assert.fail);
+  assert.deepEqual(labels, ['bar', 'foo'],
+    'button click works');
 
+  yield close(window);
+
+  loader.unload();
 }
 
 exports['test button icon set'] = function(assert) {
+  let size;
   const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
   let loader = Loader(module);
   let { ActionButton } = loader.require('sdk/ui');
 
   // Test remote icon set
   assert.throws(
     () => ActionButton({
       id: 'my-button-10',
@@ -665,22 +670,22 @@ exports['test button icon set'] = functi
       '32': './icon32.png',
       '64': './icon64.png'
     }
   });
 
   let { node, id: widgetId } = getWidget(button.id);
   let { devicePixelRatio } = node.ownerDocument.defaultView;
 
-  let size = 16 * devicePixelRatio;
+  size = 16 * devicePixelRatio;
 
   assert.equal(node.getAttribute('image'), data.url(button.icon[size].substr(2)),
     'the icon is set properly in navbar');
 
-  let size = 32 * devicePixelRatio;
+  size = 32 * devicePixelRatio;
 
   CustomizableUI.addWidgetToArea(widgetId, CustomizableUI.AREA_PANEL);
 
   assert.equal(node.getAttribute('image'), data.url(button.icon[size].substr(2)),
     'the icon is set properly in panel');
 
   // Using `loader.unload` without move back the button to the original area
   // raises an error in the CustomizableUI. This is doesn't happen if the
--- a/addon-sdk/source/test/test-ui-frame.js
+++ b/addon-sdk/source/test/test-ui-frame.js
@@ -196,16 +196,17 @@ exports["test content to host messaging"
 
   t1.destroy();
   yield wait(t1, "detach");
 
 };
 
 
 exports["test direct messaging"] = function* (assert) {
+  let message;
   const url = "data:text/html,<script>new " + function() {
     var n = 0;
     window.addEventListener("message", (event) => {
       if (event.data === "inc")
         n = n + 1;
       if (event.data === "print")
         event.source.postMessage({ n: n }, event.origin);
     });
@@ -226,27 +227,26 @@ exports["test direct messaging"] = funct
   let messages = wait(f1, "message", 2);
   f1.postMessage("inc", f1.origin);
   f1.postMessage("print", f1.origin);
 
   const [e1, e2] = yield messages;
   assert.deepEqual(e1.data, {n: 1}, "received message from window#1");
   assert.deepEqual(e2.data, {n: 1}, "received message from window#2");
 
-  let message = wait(f1, "message");
+  message = wait(f1, "message");
   e1.source.postMessage("inc", e1.origin);
   e1.source.postMessage("print", e1.origin);
   const e3 = yield message;
   assert.deepEqual(e3.data, {n: 2}, "state changed in window#1");
 
-  let message = wait(f1, "message");
+  message = wait(f1, "message");
   e2.source.postMessage("print", e2.origin);
   yield message;
   assert.deepEqual(e2.data, {n:1}, "window#2 didn't received inc message");
 
   yield close(w2);
   t1.destroy();
 
   yield wait(t1, "detach");
-
 };
 
 require("sdk/test").run(exports);
--- a/addon-sdk/source/test/test-ui-toggle-button.js
+++ b/addon-sdk/source/test/test-ui-toggle-button.js
@@ -294,27 +294,28 @@ exports['test button global state update
     'node disabled is updated');
 
   // TODO: test validation on update
 
   loader.unload();
 }
 
 exports['test button global state set and get with state method'] = function(assert) {
+  let state;
   let loader = Loader(module);
   let { ToggleButton } = loader.require('sdk/ui');
 
   let button = ToggleButton({
     id: 'my-button-16',
     label: 'my button',
     icon: './icon.png'
   });
 
   // read the button's state
-  let state = button.state(button);
+  state = button.state(button);
 
   assert.equal(state.label, 'my button',
     'label is correct');
   assert.equal(state.icon, './icon.png',
     'icon is correct');
   assert.equal(state.disabled, false,
     'disabled is correct');
 
@@ -374,16 +375,17 @@ exports['test button global state update
     return window;
   }).
   then(close).
   then(loader.unload).
   then(done, assert.fail);
 };
 
 exports['test button window state'] = function(assert, done) {
+  let state;
   let loader = Loader(module);
   let { ToggleButton } = loader.require('sdk/ui');
   let { browserWindows } = loader.require('sdk/windows');
 
   let button = ToggleButton({
     id: 'my-button-6',
     label: 'my button',
     icon: './icon.png'
@@ -407,26 +409,26 @@ exports['test button window state'] = fu
 
     assert.equal(button.label, 'my button',
       'global label unchanged');
     assert.equal(button.icon, './icon.png',
       'global icon unchanged');
     assert.equal(button.disabled, false,
       'global disabled unchanged');
 
-    let state = button.state(mainWindow);
+    state = button.state(mainWindow);
 
     assert.equal(state.label, 'my button',
       'previous window label unchanged');
     assert.equal(state.icon, './icon.png',
       'previous window icon unchanged');
     assert.equal(state.disabled, false,
       'previous window disabled unchanged');
 
-    let state = button.state(activeWindow);
+    state = button.state(activeWindow);
 
     assert.equal(state.label, 'New label',
       'active window label updated');
     assert.equal(state.icon, './new-icon.png',
       'active window icon updated');
     assert.equal(state.disabled, true,
       'active disabled updated');
 
@@ -445,30 +447,30 @@ exports['test button window state'] = fu
 
     button.state(activeWindow, null);
 
     assert.equal(button.state(activeWindow).label, 'A good label',
       'active window label inherited');
 
     // check the nodes properties
     let node = nodes[0];
-    let state = button.state(mainWindow);
+    state = button.state(mainWindow);
 
     assert.equal(node.getAttribute('label'), state.label,
       'node label is correct');
     assert.equal(node.getAttribute('tooltiptext'), state.label,
       'node tooltip is correct');
 
     assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
       'node image is correct');
     assert.equal(node.hasAttribute('disabled'), state.disabled,
       'disabled is correct');
 
-    let node = nodes[1];
-    let state = button.state(activeWindow);
+    node = nodes[1];
+    state = button.state(activeWindow);
 
     assert.equal(node.getAttribute('label'), state.label,
       'node label is correct');
     assert.equal(node.getAttribute('tooltiptext'), state.label,
       'node tooltip is correct');
 
     assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
       'node image is correct');
@@ -520,33 +522,35 @@ exports['test button tab state'] = funct
       button.state(tab, {
         icon: './another-tab-icon.png',
         disabled: true
       });
 
       // check the states
 
       Cu.schedulePreciseGC(() => {
+        let state;
+
         assert.equal(button.label, 'my button',
           'global label unchanged');
         assert.equal(button.icon, './icon.png',
           'global icon unchanged');
         assert.equal(button.disabled, false,
           'global disabled unchanged');
 
-        let state = button.state(mainTab);
+        state = button.state(mainTab);
 
         assert.equal(state.label, 'Tab label',
           'previous tab label updated');
         assert.equal(state.icon, './tab-icon.png',
           'previous tab icon updated');
         assert.equal(state.disabled, false,
           'previous tab disabled unchanged');
 
-        let state = button.state(tab);
+        state = button.state(tab);
 
         assert.equal(state.label, 'Window label',
           'active tab inherited from window state');
         assert.equal(state.icon, './another-tab-icon.png',
           'active tab icon updated');
         assert.equal(state.disabled, true,
           'active disabled updated');
 
@@ -566,17 +570,17 @@ exports['test button tab state'] = funct
         // delete the window state
         button.state(activeWindow, null);
 
         assert.equal(button.state(tab).icon, './good-icon.png',
           'tab icon inherited from global');
 
         // check the node properties
 
-        let state = button.state(tabs.activeTab);
+        state = button.state(tabs.activeTab);
 
         assert.equal(node.getAttribute('label'), state.label,
           'node label is correct');
         assert.equal(node.getAttribute('tooltiptext'), state.label,
           'node tooltip is correct');
         assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
           'node image is correct');
         assert.equal(node.hasAttribute('disabled'), state.disabled,
@@ -679,17 +683,17 @@ exports['test button icon set'] = functi
   let { node, id: widgetId } = getWidget(button.id);
   let { devicePixelRatio } = node.ownerDocument.defaultView;
 
   let size = 16 * devicePixelRatio;
 
   assert.equal(node.getAttribute('image'), data.url(button.icon[size].substr(2)),
     'the icon is set properly in navbar');
 
-  let size = 32 * devicePixelRatio;
+  size = 32 * devicePixelRatio;
 
   CustomizableUI.addWidgetToArea(widgetId, CustomizableUI.AREA_PANEL);
 
   assert.equal(node.getAttribute('image'), data.url(button.icon[size].substr(2)),
     'the icon is set properly in panel');
 
   // Using `loader.unload` without move back the button to the original area
   // raises an error in the CustomizableUI. This is doesn't happen if the
--- a/addon-sdk/source/test/test-unit-test-finder.js
+++ b/addon-sdk/source/test/test-unit-test-finder.js
@@ -20,38 +20,38 @@ exports["test makeFilters no filter"] = 
   testMethods.forEach(m => assert.ok(testFilter(m), "using no options on method name " + m + " works"));
 }
 
 exports["test makeFilters no method filter"] = (assert) => {
   let { fileFilter, testFilter } = makeFilters({ filter: "i" });
   testFiles.forEach(f => assert.ok(fileFilter(f), "using filter 'i' on filename " + f + " works"));
   testMethods.forEach(m => assert.ok(testFilter(m), "using filter 'i' on method name " + m + " works"));
 
-  let { fileFilter, testFilter } = makeFilters({ filter: "i:" });
+  ({ fileFilter, testFilter }) = makeFilters({ filter: "i:" });
   testFiles.forEach(f => assert.ok(fileFilter(f), "using filter 'i:' on filename " + f + " works"));
   testMethods.forEach(m => assert.ok(testFilter(m), "using filter 'i:' on method name " + m + " works"));
 
-  let { fileFilter, testFilter } = makeFilters({ filter: "z:" });
+  ({ fileFilter, testFilter }) = makeFilters({ filter: "z:" });
   testFiles.forEach(f => assert.ok(!fileFilter(f), "using filter 'z:' on filename " + f + " dnw"));
   testMethods.forEach(m => assert.ok(testFilter(m), "using filter 'z:' on method name " + m + " works"));
 }
 
 exports["test makeFilters no file filter"] = (assert) => {
   let { fileFilter, testFilter } = makeFilters({ filter: ":i" });
   testFiles.forEach(f => assert.ok(fileFilter(f), "using filter ':i' on filename " + f + " works"));
   testMethods.forEach(m => assert.ok(testFilter(m), "using filter ':i' on method name " + m + " works"));
 
-  let { fileFilter, testFilter } = makeFilters({ filter: ":z" });
+  ({ fileFilter, testFilter }) = makeFilters({ filter: ":z" });
   testFiles.forEach(f => assert.ok(fileFilter(f), "using filter ':z' on filename " + f + " works"));
   testMethods.forEach(m => assert.ok(!testFilter(m), "using filter ':z' on method name " + m + " dnw"));
 }
 
 exports["test makeFilters both filters"] = (assert) => {
   let { fileFilter, testFilter } = makeFilters({ filter: "i:i" });
   testFiles.forEach(f => assert.ok(fileFilter(f), "using filter 'i:i' on filename " + f + " works"));
   testMethods.forEach(m => assert.ok(testFilter(m), "using filter 'i:i' on method name " + m + " works"));
 
-  let { fileFilter, testFilter } = makeFilters({ filter: "z:z" });
+  ({ fileFilter, testFilter }) = makeFilters({ filter: "z:z" });
   testFiles.forEach(f => assert.ok(!fileFilter(f), "using filter 'z:z' on filename " + f + " dnw"));
   testMethods.forEach(m => assert.ok(!testFilter(m), "using filter 'z:z' on method name " + m + " dnw"));
 }
 
 require("sdk/test").run(exports);
--- a/addon-sdk/source/test/test-widget.js
+++ b/addon-sdk/source/test/test-widget.js
@@ -87,18 +87,18 @@ exports.testConstructor = function(asser
   AddonsMgrListener.onUninstalled();
   w.destroy();
   assert.pass("Multiple destroys do not cause an error");
   assert.equal(widgetCount(), widgetStartCount, "panel has correct number of child elements after destroy");
 
   // Test automatic widget destroy on unload
   let { loader } = LoaderWithHookedConsole(module);
   let widgetsFromLoader = loader.require("sdk/widget");
-  let widgetStartCount = widgetCount();
-  let w = widgetsFromLoader.Widget({ id: "destroy-on-unload", label: "foo", content: "bar" });
+  widgetStartCount = widgetCount();
+  w = widgetsFromLoader.Widget({ id: "destroy-on-unload", label: "foo", content: "bar" });
   assert.equal(widgetCount(), widgetStartCount + 1, "widget has been correctly added");
   loader.unload();
   assert.equal(widgetCount(), widgetStartCount, "widget has been destroyed on module unload");
 
   // Test nothing
   assert.throws(
     function() widgets.Widget({}),
     /^The widget must have a non-empty label property\.$/,
@@ -157,18 +157,18 @@ exports.testConstructor = function(asser
   // Test duplicate label, different ID
   let w1 = widgets.Widget({id: "id1", label: "foo", content: "bar"});
   let w2 = widgets.Widget({id: "id2", label: "foo", content: "bar"});
   w1.destroy();
   w2.destroy();
 
   // Test position restore on create/destroy/create
   // Create 3 ordered widgets
-  let w1 = widgets.Widget({id: "position-first", label:"first", content: "bar"});
-  let w2 = widgets.Widget({id: "position-second", label:"second", content: "bar"});
+  w1 = widgets.Widget({id: "position-first", label:"first", content: "bar"});
+  w2 = widgets.Widget({id: "position-second", label:"second", content: "bar"});
   let w3 = widgets.Widget({id: "position-third", label:"third", content: "bar"});
   // Remove the middle widget
   assert.equal(widgetNode(1).getAttribute("label"), "second", "second widget is the second widget inserted");
   w2.destroy();
   assert.equal(widgetNode(1).getAttribute("label"), "third", "second widget is removed, so second widget is now the third one");
   w2 = widgets.Widget({id: "position-second", label:"second", content: "bar"});
   assert.equal(widgetNode(1).getAttribute("label"), "second", "second widget is created again, at the same location");
   // Cleanup this testcase
deleted file mode 100644
--- a/addon-sdk/source/test/test-window-utils-global-private-browsing.js
+++ /dev/null
@@ -1,152 +0,0 @@
-/* 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';
-
-const windowUtils = require('sdk/deprecated/window-utils');
-const { isWindowPBSupported, isGlobalPBSupported } = require('sdk/private-browsing/utils');
-const { getFrames, getWindowTitle, onFocus, isWindowPrivate, windows, isBrowser } = require('sdk/window/utils');
-const { open, close, focus } = require('sdk/window/helpers');
-const { isPrivate } = require('sdk/private-browsing');
-const { pb } = require('./private-browsing/helper');
-const { fromIterator: toArray } = require('sdk/util/array');
-
-function makeEmptyBrowserWindow(options) {
-  options = options || {};
-  return open('chrome://browser/content/browser.xul', {
-    features: {
-      chrome: true,
-      private: !!options.private,
-      toolbar: true
-    }
-  });
-}
-
-exports.testShowPanelAndWidgetOnPrivateWindow = function(assert, done) {
-  var myPrivateWindow;
-  var finished = false;
-  var privateWindow;
-  var privateWindowClosed = false;
-  var { Panel } = require('sdk/panel');
-  var { Widget } = require('sdk/widget');
-
-  pb.once('start', function() {
-    assert.pass('private browsing mode started');
-
-    // make a new private window
-    makeEmptyBrowserWindow().then(function(window) {
-      myPrivateWindow = window;
-
-      let wt = windowUtils.WindowTracker({
-        onTrack: function(window) {
-          if (!isBrowser(window) || window !== myPrivateWindow) return;
-
-          assert.ok(isWindowPrivate(window), 'window is private onTrack!');
-          let panel = Panel({
-            onShow: function() {
-              assert.ok(this.isShowing, 'the panel is showing on the private window');
-
-              let count = 0;
-              let widget = Widget({
-                id: "testShowPanelAndWidgetOnPrivateWindow-id",
-                label: "My Hello Widget",
-                content: "Hello!",
-                onAttach: function(mod) {
-                  count++;
-                  if (count == 2) {
-                    panel.destroy();
-                    widget.destroy();
-                    close(window);
-                  }
-                }
-              });
-            }
-          }).show(null, window.gBrowser);
-        },
-        onUntrack: function(window) {
-          if (window === myPrivateWindow) {
-            wt.unload();
-
-            pb.once('stop', function() {
-              assert.pass('private browsing mode end');
-              done();
-            });
-
-            pb.deactivate();
-          }
-        }
-      });
-
-      assert.equal(isWindowPrivate(window), true, 'the opened window is private');
-      assert.equal(isPrivate(window), true, 'the opened window is private');
-      assert.ok(getFrames(window).length > 1, 'there are frames for private window');
-      assert.equal(getWindowTitle(window), window.document.title,
-                   'getWindowTitle works');
-    });
-  });
-  pb.activate();
-};
-
-exports.testWindowTrackerDoesNotIgnorePrivateWindows = function(assert, done) {
-  var myPrivateWindow;
-  var count = 0;
-
-  let wt = windowUtils.WindowTracker({
-    onTrack: function(window) {
-      if (!isBrowser(window) || !isWindowPrivate(window)) return;
-      assert.ok(isWindowPrivate(window), 'window is private onTrack!');
-      if (++count == 1)
-        close(window);
-    },
-    onUntrack: function(window) {
-      if (count == 1 && isWindowPrivate(window)) {
-        wt.unload();
-
-        pb.once('stop', function() {
-          assert.pass('private browsing mode end');
-          done();
-        });
-        pb.deactivate();
-      }
-    }
-  });
-
-  pb.once('start', function() {
-    assert.pass('private browsing mode started');
-    makeEmptyBrowserWindow();
-  });
-  pb.activate();
-}
-
-exports.testWindowIteratorDoesNotIgnorePrivateWindows = function(assert, done) {
-  pb.once('start', function() {
-    // make a new private window
-    makeEmptyBrowserWindow().then(function(window) {
-      assert.ok(isWindowPrivate(window), "window is private");
-      assert.equal(isPrivate(window), true, 'the opened window is private');
-      assert.ok(toArray(windowUtils.windowIterator()).indexOf(window) > -1,
-                "window is in windowIterator()");
-      assert.ok(windows(null, { includePrivate: true }).indexOf(window) > -1,
-                "window is in windows()");
-
-      close(window).then(function() {
-        pb.once('stop', function() {
-          done();
-        });
-        pb.deactivate();
-      });
-    });
-  });
-  pb.activate();
-};
-
-if (!isGlobalPBSupported) {
-  module.exports = {
-    "test Unsupported Test": function UnsupportedTest (assert) {
-        assert.pass(
-          "Skipping global private browsing tests");
-    }
-  }
-}
-
-require("test").run(exports);