Bug 1114752 - Uplift Add-on SDK to Firefox a=me
☠☠ backed out by a868809113e1 ☠ ☠
authorErik Vold <evold@mozilla.com>
Sat, 10 Jan 2015 02:01:01 -0800
changeset 249078 2815bfd2ccb58c382e0207f451d989a973e44e57
parent 249040 a31444b37e976d88f8c4b3a516fcfa64428de9cb
child 249079 d606591cf149d7452176fa49b3621b3ecfaa1c42
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersme
bugs1114752
milestone37.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 1114752 - Uplift Add-on SDK to Firefox a=me
addon-sdk/moz.build
addon-sdk/mozbuild.template
addon-sdk/source/.gitignore
addon-sdk/source/.hgignore
addon-sdk/source/.jpmignore
addon-sdk/source/.travis.yml
addon-sdk/source/README
addon-sdk/source/README.md
addon-sdk/source/bin/jpm-test.js
addon-sdk/source/bin/node-scripts/test.addons.js
addon-sdk/source/bin/node-scripts/test.examples.js
addon-sdk/source/bin/node-scripts/test.modules.js
addon-sdk/source/bin/node-scripts/utils.js
addon-sdk/source/bootstrap.js
addon-sdk/source/examples/actor-repl/test/test-main.js
addon-sdk/source/examples/annotator/package.json
addon-sdk/source/examples/annotator/tests/test-main.js
addon-sdk/source/examples/debug-client/test/test-main.js
addon-sdk/source/examples/library-detector/package.json
addon-sdk/source/examples/library-detector/test/test-main.js
addon-sdk/source/examples/toolbar-api/package.json
addon-sdk/source/examples/toolbar-api/test/test-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/utils.js
addon-sdk/source/lib/framescript/context-menu.js
addon-sdk/source/lib/framescript/manager.js
addon-sdk/source/lib/framescript/util.js
addon-sdk/source/lib/index.js
addon-sdk/source/lib/sdk/addon/bootstrap.js
addon-sdk/source/lib/sdk/clipboard.js
addon-sdk/source/lib/sdk/content/worker-parent.js
addon-sdk/source/lib/sdk/content/worker.js
addon-sdk/source/lib/sdk/context-menu.js
addon-sdk/source/lib/sdk/context-menu/context.js
addon-sdk/source/lib/sdk/context-menu/core.js
addon-sdk/source/lib/sdk/context-menu/readers.js
addon-sdk/source/lib/sdk/context-menu@2.js
addon-sdk/source/lib/sdk/core/disposable.js
addon-sdk/source/lib/sdk/deprecated/cortex.js
addon-sdk/source/lib/sdk/deprecated/events.js
addon-sdk/source/lib/sdk/deprecated/events/assembler.js
addon-sdk/source/lib/sdk/deprecated/sync-worker.js
addon-sdk/source/lib/sdk/deprecated/unit-test.js
addon-sdk/source/lib/sdk/io/stream.js
addon-sdk/source/lib/sdk/keyboard/observer.js
addon-sdk/source/lib/sdk/l10n/locale.js
addon-sdk/source/lib/sdk/lang/type.js
addon-sdk/source/lib/sdk/loader/cuddlefish.js
addon-sdk/source/lib/sdk/page-mod.js
addon-sdk/source/lib/sdk/page-worker.js
addon-sdk/source/lib/sdk/panel.js
addon-sdk/source/lib/sdk/panel/utils.js
addon-sdk/source/lib/sdk/panel/window.js
addon-sdk/source/lib/sdk/places/bookmarks.js
addon-sdk/source/lib/sdk/places/events.js
addon-sdk/source/lib/sdk/places/favicon.js
addon-sdk/source/lib/sdk/places/history.js
addon-sdk/source/lib/sdk/places/host/host-bookmarks.js
addon-sdk/source/lib/sdk/places/host/host-query.js
addon-sdk/source/lib/sdk/places/host/host-tags.js
addon-sdk/source/lib/sdk/places/utils.js
addon-sdk/source/lib/sdk/preferences/native-options.js
addon-sdk/source/lib/sdk/preferences/service.js
addon-sdk/source/lib/sdk/preferences/utils.js
addon-sdk/source/lib/sdk/self.js
addon-sdk/source/lib/sdk/system/child_process.js
addon-sdk/source/lib/sdk/system/xul-app.js
addon-sdk/source/lib/sdk/system/xul-app.jsm
addon-sdk/source/lib/sdk/tabs/observer.js
addon-sdk/source/lib/sdk/tabs/worker.js
addon-sdk/source/lib/sdk/test.js
addon-sdk/source/lib/sdk/test/assert.js
addon-sdk/source/lib/sdk/test/harness.js
addon-sdk/source/lib/sdk/test/utils.js
addon-sdk/source/lib/sdk/ui/button/view/events.js
addon-sdk/source/lib/sdk/ui/component.js
addon-sdk/source/lib/sdk/ui/sidebar.js
addon-sdk/source/lib/sdk/ui/state.js
addon-sdk/source/lib/sdk/ui/state/events.js
addon-sdk/source/lib/sdk/uri/resource.js
addon-sdk/source/lib/sdk/util/bond.js
addon-sdk/source/lib/sdk/util/registry.js
addon-sdk/source/lib/sdk/util/sequence.js
addon-sdk/source/lib/sdk/window/utils.js
addon-sdk/source/lib/sdk/windows/firefox.js
addon-sdk/source/lib/sdk/windows/loader.js
addon-sdk/source/lib/sdk/windows/observer.js
addon-sdk/source/lib/sdk/windows/tabs-fennec.js
addon-sdk/source/lib/toolkit/loader.js
addon-sdk/source/lib/toolkit/require.js
addon-sdk/source/modules/system/Startup.js
addon-sdk/source/modules/system/XulApp.js
addon-sdk/source/modules/system/moz.build
addon-sdk/source/package.json
addon-sdk/source/python-lib/cuddlefish/manifest.py
addon-sdk/source/python-lib/cuddlefish/packaging.py
addon-sdk/source/python-lib/cuddlefish/prefs.py
addon-sdk/source/python-lib/cuddlefish/runner.py
addon-sdk/source/test/addons/addon-manager/lib/main.js
addon-sdk/source/test/addons/addon-manager/lib/test-main.js
addon-sdk/source/test/addons/addon-manager/main.js
addon-sdk/source/test/addons/addon-manager/package.json
addon-sdk/source/test/addons/author-email/package.json
addon-sdk/source/test/addons/child_process/package.json
addon-sdk/source/test/addons/chrome/main.js
addon-sdk/source/test/addons/chrome/package.json
addon-sdk/source/test/addons/content-permissions/package.json
addon-sdk/source/test/addons/contributors/package.json
addon-sdk/source/test/addons/curly-id/package.json
addon-sdk/source/test/addons/developers/package.json
addon-sdk/source/test/addons/e10s-content/data/test-contentScriptFile.js
addon-sdk/source/test/addons/e10s-content/data/test-page-worker.html
addon-sdk/source/test/addons/e10s-content/data/test-page-worker.js
addon-sdk/source/test/addons/e10s-content/data/test.html
addon-sdk/source/test/addons/e10s-content/lib/fixtures.js
addon-sdk/source/test/addons/e10s-content/lib/httpd.js
addon-sdk/source/test/addons/e10s-content/lib/main.js
addon-sdk/source/test/addons/e10s-content/lib/test-content-script.js
addon-sdk/source/test/addons/e10s-content/lib/test-content-worker.js
addon-sdk/source/test/addons/e10s-content/lib/test-page-worker.js
addon-sdk/source/test/addons/e10s-content/package.json
addon-sdk/source/test/addons/e10s-tabs/lib/main.js
addon-sdk/source/test/addons/e10s-tabs/lib/test-tab-utils.js
addon-sdk/source/test/addons/e10s-tabs/lib/test-tab.js
addon-sdk/source/test/addons/e10s-tabs/package.json
addon-sdk/source/test/addons/e10s/lib/main.js
addon-sdk/source/test/addons/e10s/package.json
addon-sdk/source/test/addons/jetpack-addon.ini
addon-sdk/source/test/addons/l10n-manifest/locale/en-US.properties
addon-sdk/source/test/addons/l10n-manifest/main.js
addon-sdk/source/test/addons/l10n-manifest/package.json
addon-sdk/source/test/addons/l10n-properties/package.json
addon-sdk/source/test/addons/l10n/package.json
addon-sdk/source/test/addons/layout-change/lib/main.js
addon-sdk/source/test/addons/layout-change/lib/test-cuddlefish-loader.js
addon-sdk/source/test/addons/layout-change/lib/test-toolkit-loader.js
addon-sdk/source/test/addons/layout-change/main.js
addon-sdk/source/test/addons/layout-change/package.json
addon-sdk/source/test/addons/main/package.json
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/name-in-numbers-plus/index.js
addon-sdk/source/test/addons/name-in-numbers-plus/package.json
addon-sdk/source/test/addons/name-in-numbers/index.js
addon-sdk/source/test/addons/name-in-numbers/package.json
addon-sdk/source/test/addons/packaging/main.js
addon-sdk/source/test/addons/packaging/package.json
addon-sdk/source/test/addons/packed/package.json
addon-sdk/source/test/addons/page-mod-debugger-post/package.json
addon-sdk/source/test/addons/page-mod-debugger-pre/package.json
addon-sdk/source/test/addons/places/favicon-helpers.js
addon-sdk/source/test/addons/places/httpd.js
addon-sdk/source/test/addons/places/lib/favicon-helpers.js
addon-sdk/source/test/addons/places/lib/httpd.js
addon-sdk/source/test/addons/places/lib/main.js
addon-sdk/source/test/addons/places/lib/places-helper.js
addon-sdk/source/test/addons/places/lib/test-places-bookmarks.js
addon-sdk/source/test/addons/places/lib/test-places-events.js
addon-sdk/source/test/addons/places/lib/test-places-favicon.js
addon-sdk/source/test/addons/places/lib/test-places-history.js
addon-sdk/source/test/addons/places/lib/test-places-host.js
addon-sdk/source/test/addons/places/lib/test-places-utils.js
addon-sdk/source/test/addons/places/main.js
addon-sdk/source/test/addons/places/package.json
addon-sdk/source/test/addons/places/places-helper.js
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/places/tests/test-places-favicon.js
addon-sdk/source/test/addons/places/tests/test-places-history.js
addon-sdk/source/test/addons/places/tests/test-places-host.js
addon-sdk/source/test/addons/places/tests/test-places-utils.js
addon-sdk/source/test/addons/predefined-id-with-at/package.json
addon-sdk/source/test/addons/preferences-branch/package.json
addon-sdk/source/test/addons/private-browsing-supported/package.json
addon-sdk/source/test/addons/private-browsing-supported/test-page-mod.js
addon-sdk/source/test/addons/private-browsing-supported/test-private-browsing.js
addon-sdk/source/test/addons/private-browsing-supported/test-windows.js
addon-sdk/source/test/addons/require/main.js
addon-sdk/source/test/addons/require/package.json
addon-sdk/source/test/addons/self/main.js
addon-sdk/source/test/addons/self/package.json
addon-sdk/source/test/addons/simple-prefs/package.json
addon-sdk/source/test/addons/standard-id/package.json
addon-sdk/source/test/addons/symbiont/package.json
addon-sdk/source/test/addons/tab-close-on-startup/package.json
addon-sdk/source/test/addons/toolkit-require-reload/main.js
addon-sdk/source/test/addons/toolkit-require-reload/package.json
addon-sdk/source/test/addons/translators/package.json
addon-sdk/source/test/addons/unpacked/package.json
addon-sdk/source/test/addons/unsafe-content-script/package.json
addon-sdk/source/test/context-menu/framescript.js
addon-sdk/source/test/context-menu/test-helper.js
addon-sdk/source/test/context-menu/util.js
addon-sdk/source/test/fixtures.js
addon-sdk/source/test/fixtures/Firefox.jpg
addon-sdk/source/test/fixtures/addon-sdk/data/test-page-worker.html
addon-sdk/source/test/fixtures/addon-sdk/data/test-page-worker.js
addon-sdk/source/test/fixtures/addon/bootstrap.js
addon-sdk/source/test/fixtures/preferences/simple-prefs/package.json
addon-sdk/source/test/fixtures/test-page-worker.html
addon-sdk/source/test/fixtures/test-page-worker.js
addon-sdk/source/test/framescript-manager/frame-script.js
addon-sdk/source/test/framescript-manager/pong.js
addon-sdk/source/test/framescript-util/frame-script.js
addon-sdk/source/test/jetpack-package.ini
addon-sdk/source/test/pagemod-test-helpers.js
addon-sdk/source/test/preferences/common.json
addon-sdk/source/test/preferences/e10s-off.json
addon-sdk/source/test/preferences/e10s-on.json
addon-sdk/source/test/preferences/firefox.json
addon-sdk/source/test/preferences/no-connections.json
addon-sdk/source/test/preferences/test-e10s-preferences.js
addon-sdk/source/test/preferences/test-preferences.js
addon-sdk/source/test/preferences/test.json
addon-sdk/source/test/tabs/test-fennec-tabs.js
addon-sdk/source/test/tabs/test-firefox-tabs.js
addon-sdk/source/test/test-addon-bootstrap.js
addon-sdk/source/test/test-addon-installer.js
addon-sdk/source/test/test-api-utils.js
addon-sdk/source/test/test-array.js
addon-sdk/source/test/test-bond.js
addon-sdk/source/test/test-browser-events.js
addon-sdk/source/test/test-child_process.js
addon-sdk/source/test/test-clipboard.js
addon-sdk/source/test/test-content-script.js
addon-sdk/source/test/test-content-symbiont.js
addon-sdk/source/test/test-content-sync-worker.js
addon-sdk/source/test/test-content-worker-parent.js
addon-sdk/source/test/test-content-worker.js
addon-sdk/source/test/test-context-menu.js
addon-sdk/source/test/test-context-menu@2.js
addon-sdk/source/test/test-cuddlefish.js
addon-sdk/source/test/test-deprecate.js
addon-sdk/source/test/test-dev-panel.js
addon-sdk/source/test/test-environment.js
addon-sdk/source/test/test-event-utils.js
addon-sdk/source/test/test-frame-utils.js
addon-sdk/source/test/test-framescript-manager.js
addon-sdk/source/test/test-framescript-util.js
addon-sdk/source/test/test-functional.js
addon-sdk/source/test/test-globals.js
addon-sdk/source/test/test-heritage.js
addon-sdk/source/test/test-hidden-frame.js
addon-sdk/source/test/test-host-events.js
addon-sdk/source/test/test-hotkeys.js
addon-sdk/source/test/test-indexed-db.js
addon-sdk/source/test/test-keyboard-observer.js
addon-sdk/source/test/test-keyboard-utils.js
addon-sdk/source/test/test-l10n-locale.js
addon-sdk/source/test/test-lang-type.js
addon-sdk/source/test/test-libxul.js
addon-sdk/source/test/test-light-traits.js
addon-sdk/source/test/test-loader.js
addon-sdk/source/test/test-module.js
addon-sdk/source/test/test-namespace.js
addon-sdk/source/test/test-native-loader.js
addon-sdk/source/test/test-native-options.js
addon-sdk/source/test/test-net-url.js
addon-sdk/source/test/test-node-os.js
addon-sdk/source/test/test-page-mod.js
addon-sdk/source/test/test-page-worker.js
addon-sdk/source/test/test-panel.js
addon-sdk/source/test/test-passwords-utils.js
addon-sdk/source/test/test-passwords.js
addon-sdk/source/test/test-path.js
addon-sdk/source/test/test-preferences-service.js
addon-sdk/source/test/test-private-browsing.js
addon-sdk/source/test/test-promise.js
addon-sdk/source/test/test-querystring.js
addon-sdk/source/test/test-registry.js
addon-sdk/source/test/test-require.js
addon-sdk/source/test/test-rules.js
addon-sdk/source/test/test-sandbox.js
addon-sdk/source/test/test-selection.js
addon-sdk/source/test/test-sequence.js
addon-sdk/source/test/test-shared-require.js
addon-sdk/source/test/test-simple-prefs.js
addon-sdk/source/test/test-system-events.js
addon-sdk/source/test/test-system-runtime.js
addon-sdk/source/test/test-system.js
addon-sdk/source/test/test-tab-observer.js
addon-sdk/source/test/test-tab.js
addon-sdk/source/test/test-test-addon-file.js
addon-sdk/source/test/test-test-assert.js
addon-sdk/source/test/test-test-loader.js
addon-sdk/source/test/test-test-memory.js
addon-sdk/source/test/test-test-utils-async.js
addon-sdk/source/test/test-test-utils-generator.js
addon-sdk/source/test/test-test-utils-sync.js
addon-sdk/source/test/test-timer.js
addon-sdk/source/test/test-traits.js
addon-sdk/source/test/test-type.js
addon-sdk/source/test/test-ui-action-button.js
addon-sdk/source/test/test-ui-frame.js
addon-sdk/source/test/test-ui-sidebar.js
addon-sdk/source/test/test-ui-toggle-button.js
addon-sdk/source/test/test-ui-toolbar.js
addon-sdk/source/test/test-unit-test.js
addon-sdk/source/test/test-unsupported-skip.js
addon-sdk/source/test/test-uri-resource.js
addon-sdk/source/test/test-uuid.js
addon-sdk/source/test/test-weak-set.js
addon-sdk/source/test/test-widget.js
addon-sdk/source/test/test-window-loader.js
addon-sdk/source/test/test-window-utils.js
addon-sdk/source/test/test-window-utils2.js
addon-sdk/source/test/test-windows.js
addon-sdk/source/test/test-xhr.js
addon-sdk/source/test/test-xpcom.js
addon-sdk/source/test/test-xul-app.js
addon-sdk/source/test/util.js
addon-sdk/source/test/windows/test-firefox-windows.js
--- a/addon-sdk/moz.build
+++ b/addon-sdk/moz.build
@@ -4,29 +4,26 @@
 # 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/.
 
-HAS_MISC_RULE = True
-
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 JETPACK_PACKAGE_MANIFESTS += ['source/test/jetpack-package.ini']
 JETPACK_ADDON_MANIFESTS += ['source/test/addons/jetpack-addon.ini']
 
 EXTRA_JS_MODULES.sdk += [
     'source/app-extension/bootstrap.js',
 ]
 
 EXTRA_JS_MODULES.sdk.system += [
-    'source/modules/system/Startup.js',
-    'source/modules/system/XulApp.js',
+    'source/modules/system/Startup.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',
     ]
 
@@ -34,32 +31,32 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk
         'source/lib/sdk/deprecated/api-utils.js',
         'source/lib/sdk/deprecated/cortex.js',
         'source/lib/sdk/deprecated/errors.js',
         'source/lib/sdk/deprecated/events.js',
         'source/lib/sdk/deprecated/light-traits.js',
         'source/lib/sdk/deprecated/list.js',
         'source/lib/sdk/deprecated/memory.js',
         'source/lib/sdk/deprecated/symbiont.js',
+        'source/lib/sdk/deprecated/sync-worker.js',
         'source/lib/sdk/deprecated/traits-worker.js',
         'source/lib/sdk/deprecated/traits.js',
         'source/lib/sdk/deprecated/unit-test-finder.js',
         'source/lib/sdk/deprecated/unit-test.js',
         'source/lib/sdk/deprecated/window-utils.js',
     ]
 
     EXTRA_JS_MODULES.commonjs.sdk.frame += [
         'source/lib/sdk/frame/hidden-frame.js',
         'source/lib/sdk/frame/utils.js',
     ]
 
     EXTRA_JS_MODULES.commonjs.sdk.panel += [
         'source/lib/sdk/panel/events.js',
         'source/lib/sdk/panel/utils.js',
-        'source/lib/sdk/panel/window.js',
     ]
 
     EXTRA_JS_MODULES.commonjs.sdk.places += [
         'source/lib/sdk/places/bookmarks.js',
         'source/lib/sdk/places/contract.js',
         'source/lib/sdk/places/events.js',
         'source/lib/sdk/places/favicon.js',
         'source/lib/sdk/places/history.js',
@@ -94,16 +91,17 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk
         'source/lib/sdk/test/memory.js',
         'source/lib/sdk/test/options.js',
         'source/lib/sdk/test/runner.js',
         'source/lib/sdk/test/tmp-file.js',
         'source/lib/sdk/test/utils.js',
     ]
 
     EXTRA_JS_MODULES.commonjs.sdk.ui += [
+        'source/lib/sdk/ui/component.js',
         'source/lib/sdk/ui/frame.js',
         'source/lib/sdk/ui/id.js',
         'source/lib/sdk/ui/sidebar.js',
         'source/lib/sdk/ui/state.js',
         'source/lib/sdk/ui/toolbar.js',
     ]
 
     EXTRA_JS_MODULES.commonjs.sdk.ui.button += [
@@ -128,23 +126,23 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk
         'source/lib/sdk/window/namespace.js',
         'source/lib/sdk/window/utils.js',
     ]
 
     EXTRA_JS_MODULES.commonjs.sdk.windows += [
         'source/lib/sdk/windows/dom.js',
         'source/lib/sdk/windows/fennec.js',
         'source/lib/sdk/windows/firefox.js',
-        'source/lib/sdk/windows/loader.js',
         'source/lib/sdk/windows/observer.js',
         'source/lib/sdk/windows/tabs-fennec.js',
         'source/lib/sdk/windows/tabs-firefox.js',
     ]
 
 EXTRA_JS_MODULES.commonjs += [
+    'source/lib/index.js',
     'source/lib/test.js',
 ]
 
 EXTRA_JS_MODULES.commonjs.dev += [
     'source/lib/dev/debuggee.js',
     'source/lib/dev/frame-script.js',
     'source/lib/dev/panel.js',
     'source/lib/dev/ports.js',
@@ -168,34 +166,38 @@ EXTRA_JS_MODULES.commonjs.diffpatcher.te
     'source/lib/diffpatcher/test/common.js',
     'source/lib/diffpatcher/test/diff.js',
     'source/lib/diffpatcher/test/index.js',
     'source/lib/diffpatcher/test/patch.js',
     'source/lib/diffpatcher/test/tap.js',
 ]
 
 EXTRA_JS_MODULES.commonjs.framescript += [
+    'source/lib/framescript/context-menu.js',
     'source/lib/framescript/contextmenu-events.js',
     'source/lib/framescript/FrameScriptManager.jsm',
     'source/lib/framescript/LoaderHelper.jsm',
+    'source/lib/framescript/manager.js',
     'source/lib/framescript/tab-events.js',
+    'source/lib/framescript/util.js',
 ]
 
 EXTRA_JS_MODULES.commonjs.method += [
     'source/lib/method/core.js',
 ]
 
 EXTRA_JS_MODULES.commonjs.node += [
     'source/lib/node/os.js',
 ]
 
 EXTRA_JS_MODULES.commonjs.sdk += [
     'source/lib/sdk/base64.js',
     'source/lib/sdk/clipboard.js',
     'source/lib/sdk/context-menu.js',
+    'source/lib/sdk/context-menu@2.js',
     'source/lib/sdk/hotkeys.js',
     'source/lib/sdk/indexed-db.js',
     'source/lib/sdk/l10n.js',
     'source/lib/sdk/messaging.js',
     'source/lib/sdk/notifications.js',
     'source/lib/sdk/page-mod.js',
     'source/lib/sdk/page-worker.js',
     'source/lib/sdk/panel.js',
@@ -213,16 +215,17 @@ EXTRA_JS_MODULES.commonjs.sdk += [
     'source/lib/sdk/timers.js',
     'source/lib/sdk/ui.js',
     'source/lib/sdk/url.js',
     'source/lib/sdk/widget.js',
     'source/lib/sdk/windows.js',
 ]
 
 EXTRA_JS_MODULES.commonjs.sdk.addon += [
+    'source/lib/sdk/addon/bootstrap.js',
     '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',
 ]
 
@@ -241,20 +244,25 @@ EXTRA_JS_MODULES.commonjs.sdk.content +=
     'source/lib/sdk/content/context-menu.js',
     'source/lib/sdk/content/events.js',
     'source/lib/sdk/content/loader.js',
     'source/lib/sdk/content/mod.js',
     'source/lib/sdk/content/sandbox.js',
     'source/lib/sdk/content/thumbnail.js',
     'source/lib/sdk/content/utils.js',
     'source/lib/sdk/content/worker-child.js',
-    'source/lib/sdk/content/worker-parent.js',
     'source/lib/sdk/content/worker.js',
 ]
 
+EXTRA_JS_MODULES.commonjs.sdk['context-menu'] += [
+    'source/lib/sdk/context-menu/context.js',
+    'source/lib/sdk/context-menu/core.js',
+    'source/lib/sdk/context-menu/readers.js',
+]
+
 EXTRA_JS_MODULES.commonjs.sdk.core += [
     'source/lib/sdk/core/disposable.js',
     'source/lib/sdk/core/heritage.js',
     'source/lib/sdk/core/namespace.js',
     'source/lib/sdk/core/observer.js',
     'source/lib/sdk/core/promise.js',
     'source/lib/sdk/core/reference.js',
 ]
@@ -390,16 +398,17 @@ EXTRA_JS_MODULES.commonjs.sdk.system += 
     'source/lib/sdk/system/child_process.js',
     'source/lib/sdk/system/environment.js',
     'source/lib/sdk/system/events.js',
     'source/lib/sdk/system/globals.js',
     'source/lib/sdk/system/process.js',
     'source/lib/sdk/system/runtime.js',
     'source/lib/sdk/system/unload.js',
     'source/lib/sdk/system/xul-app.js',
+    'source/lib/sdk/system/xul-app.jsm',
 ]
 
 EXTRA_JS_MODULES.commonjs.sdk.system.child_process += [
     'source/lib/sdk/system/child_process/subprocess.js',
     'source/lib/sdk/system/child_process/subprocess_worker_unix.js',
     'source/lib/sdk/system/child_process/subprocess_worker_win.js',
 ]
 
@@ -421,30 +430,34 @@ EXTRA_JS_MODULES.commonjs.sdk.ui.state +
     'source/lib/sdk/ui/state/events.js',
 ]
 
 EXTRA_JS_MODULES.commonjs.sdk.ui.toolbar += [
     'source/lib/sdk/ui/toolbar/model.js',
     'source/lib/sdk/ui/toolbar/view.js',
 ]
 
+EXTRA_JS_MODULES.commonjs.sdk.uri += [
+    'source/lib/sdk/uri/resource.js',
+]
+
 EXTRA_JS_MODULES.commonjs.sdk.url += [
     'source/lib/sdk/url/utils.js',
 ]
 
 EXTRA_JS_MODULES.commonjs.sdk.util += [
     'source/lib/sdk/util/array.js',
+    'source/lib/sdk/util/bond.js',
     'source/lib/sdk/util/collection.js',
     'source/lib/sdk/util/contract.js',
     'source/lib/sdk/util/deprecate.js',
     'source/lib/sdk/util/dispatcher.js',
     'source/lib/sdk/util/list.js',
     'source/lib/sdk/util/match-pattern.js',
     'source/lib/sdk/util/object.js',
-    'source/lib/sdk/util/registry.js',
     'source/lib/sdk/util/rules.js',
     'source/lib/sdk/util/sequence.js',
     'source/lib/sdk/util/uuid.js',
 ]
 
 EXTRA_JS_MODULES.commonjs.sdk.view += [
     'source/lib/sdk/view/core.js',
 ]
--- a/addon-sdk/mozbuild.template
+++ b/addon-sdk/mozbuild.template
@@ -8,11 +8,10 @@ BROWSER_CHROME_MANIFESTS += ['test/brows
 JETPACK_PACKAGE_MANIFESTS += ['source/test/jetpack-package.ini']
 JETPACK_ADDON_MANIFESTS += ['source/test/addons/jetpack-addon.ini']
 
 EXTRA_JS_MODULES.sdk += [
     'source/app-extension/bootstrap.js',
 ]
 
 EXTRA_JS_MODULES.sdk.system += [
-    'source/modules/system/Startup.js',
     'source/modules/system/XulApp.js',
 ]
--- a/addon-sdk/source/.gitignore
+++ b/addon-sdk/source/.gitignore
@@ -3,18 +3,18 @@ python-lib/cuddlefish/app-extension/comp
 testdocs.tgz
 jetpack-sdk-docs.tgz
 .test_tmp/
 doc/dev-guide/
 doc/index.html
 doc/modules/
 doc/status.md5
 packages/*
+node_modules
 
 # Python
 *.pyc
 
 # OSX
 *.DS_Store
 
 # Windows
 *Thumbs.db
-
--- a/addon-sdk/source/.hgignore
+++ b/addon-sdk/source/.hgignore
@@ -1,14 +1,15 @@
 syntax: glob
 local.json
 python-lib/cuddlefish/app-extension/components/jetpack.xpt
 testdocs.tgz
 jetpack-sdk-docs.tgz
 .test_tmp
 jetpack-sdk-docs
+node_modules
 
 # These should really be in a global .hgignore, but such a thing
 # seems ridiculously confusing to set up, so we'll include some
 # common intermediate files here.
 *.pyc
 *~
 *.DS_Store
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/.jpmignore
@@ -0,0 +1,17 @@
+local.json
+mapping.json
+CONTRIBUTING.md
+@addon-sdk.xpi
+.*
+app-extension/
+bin/
+modules/
+node_modules/
+examples/
+
+# Python
+python-lib/
+*.pyc
+
+# Windows
+*Thumbs.db
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/.travis.yml
@@ -0,0 +1,23 @@
+sudo: false
+language: node_js
+node_js:
+  - "0.10"
+
+notifications:
+  irc: "irc.mozilla.org#jetpack"
+
+before_install:
+  - "export DISPLAY=:99.0"
+  - "sh -e /etc/init.d/xvfb start"
+  - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 -extension RANDR"
+
+before_script:
+  - npm install mozilla-download -g
+  - npm install jpm -g
+  - cd ..
+  - mozilla-download --branch nightly -c prerelease --host ftp.mozilla.org firefox
+  - export JPM_FIREFOX_BINARY=$TRAVIS_BUILD_DIR/../firefox/firefox
+  - cd $TRAVIS_BUILD_DIR
+
+script:
+  - npm test
deleted file mode 100644
--- a/addon-sdk/source/README
+++ /dev/null
@@ -1,41 +0,0 @@
-Add-on SDK README
-==================
-
-Before proceeding, please make sure you've installed Python 2.5,
-2.6, or 2.7 (if it's not already on your system):
-
-  http://python.org/download/
-
-Note that Python 3 is not supported.
-
-For Windows users, MozillaBuild (https://wiki.mozilla.org/MozillaBuild)
-will install the correct version of Python and the MSYS package, which
-will make it easier to work with the SDK.
-
-To get started, first enter the same directory that this README file
-is in (the SDK's root directory) using a shell program. On Unix systems
-or on Windows with MSYS, you can execute the following command:
-
-  source bin/activate
-
-Windows users using cmd.exe should instead run:
-
-  bin\activate.bat
-
-Then go to https://developer.mozilla.org/en-US/Add-ons/SDK/
-to browse the SDK documentation.
-
-If you get an error when running cfx or have any other problems getting
-started, see the "Troubleshooting" guide at:
-https://developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/Troubleshooting
-
-Bugs
--------
-
-* file a bug: https://bugzilla.mozilla.org/enter_bug.cgi?product=Add-on%20SDK
-
-
-Style Guidelines
---------------------
-
-* https://github.com/mozilla/addon-sdk/wiki/Coding-style-guide
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/README.md
@@ -0,0 +1,31 @@
+# Mozilla Add-on SDK [![Build Status](https://travis-ci.org/mozilla/addon-sdk.png)](https://travis-ci.org/mozilla/addon-sdk)
+
+Using the Add-on SDK you can create Firefox add-ons using standard Web technologies: JavaScript, HTML, and CSS. The SDK includes JavaScript APIs which you can use to create add-ons, and tools for creating, running, testing, and packaging add-ons.
+
+If you find a problem, please [report the bug here](https://bugzilla.mozilla.org/enter_bug.cgi?product=Add-on%20SDK).
+
+## Developing Add-ons
+
+These resources should provide some help:
+
+* [Add-on SDK Documentation](https://developer.mozilla.org/en-US/Add-ons/SDK)
+* [Community Developed Modules](https://github.com/mozilla/addon-sdk/wiki/Community-developed-modules)
+* [Jetpack FAQ](https://wiki.mozilla.org/Jetpack/FAQ)
+* [StackOverflow Questions](http://stackoverflow.com/questions/tagged/firefox-addon-sdk)
+* [Mailing List](https://wiki.mozilla.org/Jetpack#Mailing_list)
+* #jetpack on irc.mozilla.org
+
+## Contributing Code
+
+Please read these two guides if you wish to contribute some patches to the addon-sdk:
+
+* [Contribute Guide](https://github.com/mozilla/addon-sdk/wiki/Contribute)
+* [Style Guide](https://github.com/mozilla/addon-sdk/wiki/Coding-style-guide)
+
+## Issues
+
+We use [bugzilla](https://bugzilla.mozilla.org/) as our issue tracker, here are some useful links:
+
+* [File a bug](https://bugzilla.mozilla.org/enter_bug.cgi?product=Add-on%20SDK)
+* [Open bugs](https://bugzilla.mozilla.org/buglist.cgi?bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&columnlist=bug_severity%2Cpriority%2Cassigned_to%2Cbug_status%2Ctarget_milestone%2Cresolution%2Cshort_desc%2Cchangeddate&product=Add-on%20SDK&query_format=advanced&order=priority)
+* [Good first bugs](https://bugzilla.mozilla.org/buglist.cgi?status_whiteboard=[good+first+bug]&&resolution=---&product=Add-on+SDK)
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/bin/jpm-test.js
@@ -0,0 +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";
+
+var BLACKLIST = [];
+var readParam = require("./node-scripts/utils").readParam;
+var path = require("path");
+var Mocha = require("mocha");
+var mocha = new Mocha({
+  ui: "bdd",
+  reporter: "spec",
+  timeout: 900000
+});
+
+var type = readParam("type");
+
+[
+  (!type || type == "modules") && require.resolve("../bin/node-scripts/test.modules"),
+  (!type || type == "addons") && require.resolve("../bin/node-scripts/test.addons"),
+  (!type || type == "examples") && require.resolve("../bin/node-scripts/test.examples"),
+].sort().forEach(function(filepath) {
+  filepath && mocha.addFile(filepath);
+})
+
+mocha.run(function (failures) {
+  process.exit(failures);
+});
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/bin/node-scripts/test.addons.js
@@ -0,0 +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";
+
+var utils = require("./utils");
+var path = require("path");
+var fs = require("fs");
+var jpm = utils.run;
+var readParam = utils.readParam;
+
+var addonsPath = path.join(__dirname, "..", "..", "test", "addons");
+
+var binary = process.env.JPM_FIREFOX_BINARY || "nightly";
+var filterPattern = readParam("filter");
+
+describe("jpm test sdk addons", function () {
+  fs.readdirSync(addonsPath)
+  .filter(fileFilter.bind(null, addonsPath))
+  .forEach(function (file) {
+    it(file, function (done) {
+      var addonPath = path.join(addonsPath, file);
+      process.chdir(addonPath);
+
+      var options = { cwd: addonPath, env: { JPM_FIREFOX_BINARY: binary }};
+      if (process.env.DISPLAY) {
+        options.env.DISPLAY = process.env.DISPLAY;
+      }
+      if (/^e10s/.test(file)) {
+        options.e10s = true;
+      }
+
+      jpm("run", options).then(done).catch(done);
+    });
+  });
+});
+
+function fileFilter(root, file) {
+  var matcher = filterPattern && new RegExp(filterPattern);
+  if (/^(l10n|simple-prefs|page-mod-debugger)/.test(file)) {
+    return false;
+  }
+  if (matcher && !matcher.test(file)) {
+    return false;
+  }
+  var stat = fs.statSync(path.join(root, file))
+  return (stat && stat.isDirectory());
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/bin/node-scripts/test.examples.js
@@ -0,0 +1,45 @@
+/* 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 utils = require("./utils");
+var path = require("path");
+var fs = require("fs");
+var jpm = utils.run;
+var readParam = utils.readParam;
+
+var examplesPath = path.join(__dirname, "..", "..", "examples");
+
+var binary = process.env.JPM_FIREFOX_BINARY || "nightly";
+var filterPattern = readParam("filter");
+
+describe("jpm test sdk examples", function () {
+  fs.readdirSync(examplesPath)
+  .filter(fileFilter.bind(null, examplesPath))
+  .forEach(function (file) {
+    it(file, function (done) {
+      var addonPath = path.join(examplesPath, file);
+      process.chdir(addonPath);
+
+      var options = { cwd: addonPath, env: { JPM_FIREFOX_BINARY: binary }};
+      if (process.env.DISPLAY) {
+        options.env.DISPLAY = process.env.DISPLAY;
+      }
+
+      jpm("test", options).then(done);
+    });
+  });
+});
+
+function fileFilter(root, file) {
+  var matcher = filterPattern && new RegExp(filterPattern);
+  if (/^(reading-data)/.test(file)) {
+    return false;
+  }
+  if (matcher && !matcher.test(file)) {
+    return false;
+  }
+  var stat = fs.statSync(path.join(root, file))
+  return (stat && stat.isDirectory());
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/bin/node-scripts/test.modules.js
@@ -0,0 +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";
+
+var utils = require("./utils");
+var readParam = utils.readParam;
+var path = require("path");
+var fs = require("fs");
+var jpm = utils.run;
+var sdk = path.join(__dirname, "..", "..");
+var binary = process.env.JPM_FIREFOX_BINARY || "nightly";
+
+var filterPattern = readParam("filter");
+
+describe("jpm test sdk modules", function () {
+  it("SDK Modules", function (done) {
+    process.chdir(sdk);
+
+    var options = { cwd: sdk, env: { JPM_FIREFOX_BINARY: binary } };
+    if (process.env.DISPLAY) {
+      options.env.DISPLAY = process.env.DISPLAY;
+    }
+    options.filter = filterPattern;
+
+    jpm("test", options, process).then(done);
+  });
+});
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/bin/node-scripts/utils.js
@@ -0,0 +1,70 @@
+/* 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 _ = require("lodash");
+var path = require("path");
+var child_process = require("child_process");
+var jpm = require.resolve("../../node_modules/jpm/bin/jpm");
+var Promise = require("promise");
+var chai = require("chai");
+var expect = chai.expect;
+var assert = chai.assert;
+var DEFAULT_PROCESS = process;
+
+var sdk = path.join(__dirname, "..", "..");
+var prefsPath = path.join(sdk, "test", "preferences", "test-preferences.js");
+var e10sPrefsPath = path.join(sdk, "test", "preferences", "test-e10s-preferences.js");
+
+function spawn (cmd, options) {
+  options = options || {};
+  var env = _.extend({}, options.env, process.env);
+  var e10s = options.e10s || false;
+
+  return child_process.spawn("node", [
+    jpm, cmd, "-v",
+    "--prefs", e10s ? e10sPrefsPath : prefsPath,
+    "-o", sdk,
+    "-f", options.filter || ""
+  ], {
+    cwd: options.cwd || tmpOutputDir,
+    env: env
+  });
+}
+exports.spawn = spawn;
+
+function run (cmd, options, p) {
+  return new Promise(function(resolve) {
+    var output = [];
+    var proc = spawn(cmd, options);
+    proc.stderr.pipe(process.stderr);
+    proc.stdout.on("data", function (data) {
+      output.push(data);
+    });
+    if (p) {
+      proc.stdout.pipe(p.stdout);
+    }
+    proc.on("close", function(code) {
+      var out = output.join("");
+      var noTests = /No tests were run/.test(out);
+      var hasSuccess = /All tests passed!/.test(out);
+      var hasFailure = /There were test failures\.\.\./.test(out);
+      if (noTests || hasFailure || !hasSuccess || code != 0) {
+        DEFAULT_PROCESS.stdout.write(out);
+      }
+      expect(code).to.equal(hasFailure ? 1 : 0);
+      expect(hasFailure).to.equal(false);
+      expect(hasSuccess).to.equal(true);
+      expect(noTests).to.equal(false);
+      resolve();
+    });
+  });
+}
+exports.run = run;
+
+function readParam(name) {
+  var index = process.argv.indexOf("--" + name)
+  return index >= 0 && process.argv[index + 1]
+}
+exports.readParam = readParam;
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/bootstrap.js
@@ -0,0 +1,13 @@
+/* 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";
+
+// Note that this file is temporary workaroud until JPM is smart enough
+// to cover it on it's own.
+
+const { utils: Cu } = Components;
+const rootURI = __SCRIPT_URI_SPEC__.replace("bootstrap.js", "");
+const { require } = Cu.import(`${rootURI}/lib/toolkit/require.js`, {});
+const { Bootstrap } = require(`${rootURI}/lib/sdk/addon/bootstrap.js`);
+const { startup, shutdown, install, uninstall } = new Bootstrap(rootURI);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/examples/actor-repl/test/test-main.js
@@ -0,0 +1,10 @@
+/* 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";
+
+exports.testMain = function(assert) {
+  assert.pass("TODO: Write some tests.");
+};
+
+require("sdk/test").run(exports);
--- a/addon-sdk/source/examples/annotator/package.json
+++ b/addon-sdk/source/examples/annotator/package.json
@@ -1,9 +1,11 @@
 {
-    "license": "MPL 2.0",
-    "name": "annotator",
-    "contributors": [],
-    "author": "Will Bamberg",
-    "keywords": [],
-    "id": "anonid0-annotator",
-    "description": "Add notes to Web pages"
+  "license": "MPL 2.0",
+  "name": "annotator",
+  "contributors": [],
+  "author": "Will Bamberg",
+  "keywords": [],
+  "version": "0.1.1",
+  "id": "anonid0-annotator@jetpack",
+  "description": "Add notes to Web pages",
+  "main": "./lib/main.js"
 }
--- a/addon-sdk/source/examples/annotator/tests/test-main.js
+++ b/addon-sdk/source/examples/annotator/tests/test-main.js
@@ -1,7 +1,10 @@
 /* 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";
 
-exports.testMain = function(test) {
-  test.pass("TODO: Write some tests.");
+exports.testMain = function(assert) {
+  assert.pass("TODO: Write some tests.");
 };
+
+require("sdk/test").run(exports);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/examples/debug-client/test/test-main.js
@@ -0,0 +1,10 @@
+/* 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";
+
+exports.testMain = function(assert) {
+  assert.pass("TODO: Write some tests.");
+};
+
+require("sdk/test").run(exports);
--- a/addon-sdk/source/examples/library-detector/package.json
+++ b/addon-sdk/source/examples/library-detector/package.json
@@ -1,9 +1,10 @@
 {
-    "name": "library-detector-sdk",
-    "license": "MPL 2.0",
-    "author": "",
-    "version": "0.1",
-    "title": "library-detector-sdk",
-    "id": "jid1-R4rSVNkBANnvGQ",
-    "description": "a basic add-on"
+  "name": "library-detector-sdk",
+  "license": "MPL 2.0",
+  "author": "",
+  "version": "0.1.1",
+  "title": "library-detector-sdk",
+  "id": "jid1-R4rSVNkBANnvGQ@jetpack",
+  "description": "a basic add-on",
+  "main": "./lib/main.js"
 }
--- a/addon-sdk/source/examples/library-detector/test/test-main.js
+++ b/addon-sdk/source/examples/library-detector/test/test-main.js
@@ -1,7 +1,10 @@
 /* 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";
 
-exports.testMain = function(test) {
-  test.pass("TODO: Write some tests.");
+exports.testMain = function(assert) {
+  assert.pass("TODO: Write some tests.");
 };
+
+require("sdk/test").run(exports);
--- a/addon-sdk/source/examples/toolbar-api/package.json
+++ b/addon-sdk/source/examples/toolbar-api/package.json
@@ -1,12 +1,12 @@
 {
   "name": "toolbar-api",
   "title": "Toolbar API",
   "main": "./lib/main.js",
   "description": "a toolbar api example",
   "author": "",
   "license": "MPL 2.0",
-  "version": "0.1",
+  "version": "0.1.1",
   "engines": {
     "firefox": ">=27.0 <=30.0"
   }
 }
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/examples/toolbar-api/test/test-main.js
@@ -0,0 +1,10 @@
+/* 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";
+
+exports.testMain = function(assert) {
+  assert.pass("TODO: Write some tests.");
+};
+
+require("sdk/test").run(exports);
--- a/addon-sdk/source/examples/ui-button-apis/package.json
+++ b/addon-sdk/source/examples/ui-button-apis/package.json
@@ -1,9 +1,10 @@
 {
   "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"
+  "version": "0.1.1",
+  "main": "./lib/main.js"
 }
--- a/addon-sdk/source/examples/ui-button-apis/tests/test-main.js
+++ b/addon-sdk/source/examples/ui-button-apis/tests/test-main.js
@@ -1,14 +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";
 
-var { actionButton, toggleButton, icon } = require("main");
+try {
+  // CFX use case..
+  var { actionButton, toggleButton, icon } = require("main");
+}
+catch (e) {
+  // JPM use case..
+  let mainURI = "../lib/main";
+  var { actionButton, toggleButton, icon } = require(mainURI);
+}
 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");
 }
 
--- a/addon-sdk/source/lib/dev/utils.js
+++ b/addon-sdk/source/lib/dev/utils.js
@@ -11,28 +11,30 @@ const { devtools } = Cu.import("resource
 const { getActiveTab } = require("../sdk/tabs/utils");
 const { getMostRecentBrowserWindow } = require("../sdk/window/utils");
 
 const targetFor = target => {
   target = target || getActiveTab(getMostRecentBrowserWindow());
   return devtools.TargetFactory.forTab(target);
 };
 
+const getId = id => ((id.prototype && id.prototype.id) || id.id || id);
+
 const getCurrentPanel = toolbox => toolbox.getCurrentPanel();
 exports.getCurrentPanel = getCurrentPanel;
 
 const openToolbox = (id, tab) => {
-  id = id.prototype.id || id.id || id;
+  id = getId(id);
   return gDevTools.showToolbox(targetFor(tab), id);
 };
 exports.openToolbox = openToolbox;
 
 const closeToolbox = tab => gDevTools.closeToolbox(targetFor(tab));
 exports.closeToolbox = closeToolbox;
 
 const getToolbox = tab => gDevTools.getToolbox(targetFor(tab));
 exports.getToolbox = getToolbox;
 
 const openToolboxPanel = (id, tab) => {
-  id = id.prototype.id || id.id || id;
+  id = getId(id);
   return gDevTools.showToolbox(targetFor(tab), id).then(getCurrentPanel);
 };
 exports.openToolboxPanel = openToolboxPanel;
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/framescript/context-menu.js
@@ -0,0 +1,212 @@
+"use strict";
+
+const { query, constant, cache } = require("sdk/lang/functional");
+const { pairs, each, map, object } = require("sdk/util/sequence");
+const { nodeToMessageManager } = require("./util");
+
+// Decorator function that takes `f` function and returns one that attempts
+// to run `f` with given arguments. In case of exception error is logged
+// and `fallback` is returned instead.
+const Try = (fn, fallback=null) => (...args) => {
+  try {
+    return fn(...args);
+  } catch(error) {
+    console.error(error);
+    return fallback;
+  }
+};
+
+// Decorator funciton that takes `f` function and returns one that returns
+// JSON cloned result of whatever `f` returns for given arguments.
+const JSONReturn = f => (...args) => JSON.parse(JSON.stringify(f(...args)));
+
+const Null = constant(null);
+
+// Table of readers mapped to field names they're going to be reading.
+const readers = Object.create(null);
+// Read function takes "contextmenu" event target `node` and returns table of
+// read field names mapped to appropriate values. Read uses above defined read
+// table to read data for all registered readers.
+const read = node =>
+  object(...map(([id, read]) => [id, read(node, id)], pairs(readers)));
+
+// Table of built-in readers, each takes a descriptor and returns a reader:
+// descriptor -> node -> JSON
+const parsers = Object.create(null)
+// Function takes a descriptor of the remotely defined reader and parsese it
+// to construct a local reader that's going to read out data from context menu
+// target.
+const parse = descriptor => {
+  const parser = parsers[descriptor.category];
+  if (!parser) {
+    console.error("Unknown reader descriptor was received", descriptor, `"${descriptor.category}"`);
+    return Null
+  }
+  return Try(parser(descriptor));
+}
+
+// TODO: Test how chrome's mediaType behaves to try and match it's behavior.
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const SVG_NS = "http://www.w3.org/2000/svg";
+
+// Firefox always creates a HTMLVideoElement when loading an ogg file
+// directly. If the media is actually audio, be smarter and provide a
+// context menu with audio operations.
+// Source: https://github.com/mozilla/gecko-dev/blob/28c2fca3753c5371643843fc2f2f205146b083b7/browser/base/content/nsContextMenu.js#L632-L637
+const isVideoLoadingAudio = node =>
+  node.readyState >= node.HAVE_METADATA &&
+    (node.videoWidth == 0 || node.videoHeight == 0)
+
+const isVideo = node =>
+  node instanceof node.ownerDocument.defaultView.HTMLVideoElement &&
+  !isVideoLoadingAudio(node);
+
+const isAudio = node => {
+  const {HTMLVideoElement, HTMLAudioElement} = node.ownerDocument.defaultView;
+  return node instanceof HTMLAudioElement ? true :
+         node instanceof HTMLVideoElement ? isVideoLoadingAudio(node) :
+         false;
+};
+
+const isImage = ({namespaceURI, localName}) =>
+  namespaceURI === HTML_NS && localName === "img" ? true :
+  namespaceURI === XUL_NS && localName === "image" ? true :
+  namespaceURI === SVG_NS && localName === "image" ? true :
+  false;
+
+parsers["reader/MediaType()"] = constant(node =>
+  isImage(node) ? "image" :
+  isAudio(node) ? "audio" :
+  isVideo(node) ? "video" :
+  null);
+
+
+const readLink = node =>
+  node.namespaceURI === HTML_NS && node.localName === "a" ? node.href :
+  readLink(node.parentNode);
+
+parsers["reader/LinkURL()"] = constant(node =>
+  node.matches("a, a *") ? readLink(node) : null);
+
+// Reader that reads out `true` if "contextmenu" `event.target` matches
+// `descriptor.selector` and `false` if it does not.
+parsers["reader/SelectorMatch()"] = ({selector}) =>
+  node => node.matches(selector);
+
+// Accessing `selectionStart` and `selectionEnd` properties on non
+// editable input nodes throw exceptions, there for we need this util
+// function to guard us against them.
+const getInputSelection = node => {
+  try {
+    if ("selectionStart" in node && "selectionEnd" in node) {
+      const {selectionStart, selectionEnd} = node;
+      return {selectionStart, selectionEnd}
+    }
+  }
+  catch(_) {}
+
+  return null;
+}
+
+// Selection reader does not really cares about descriptor so it is
+// a constant function returning selection reader. Selection reader
+// returns string of the selected text or `null` if there is no selection.
+parsers["reader/Selection()"] = constant(node => {
+  const selection = node.ownerDocument.getSelection();
+  if (!selection.isCollapsed) {
+    return selection.toString();
+  }
+  // If target node is editable (text, input, textarea, etc..) document does
+  // not really handles selections there. There for we fallback to checking
+  // `selectionStart` `selectionEnd` properties and if they are present we
+  // extract selections manually from the `node.value`.
+  else {
+    const selection = getInputSelection(node);
+    const isSelected = selection &&
+                       Number.isInteger(selection.selectionStart) &&
+                       Number.isInteger(selection.selectionEnd) &&
+                       selection.selectionStart !== selection.selectionEnd;
+    return  isSelected ? node.value.substring(selection.selectionStart,
+                                              selection.selectionEnd) :
+            null;
+  }
+});
+
+// Query reader just reads out properties from the node, so we just use `query`
+// utility function.
+parsers["reader/Query()"] = ({path}) => JSONReturn(query(path));
+// Attribute reader just reads attribute of the event target node.
+parsers["reader/Attribute()"] = ({name}) => node => node.getAttribute(name);
+
+// Extractor reader defines generates a reader out of serialized function, who's
+// return value is JSON cloned. Note: We do know source will evaluate to function
+// as that's what we serialized on the other end, it's also ok if generated function
+// is going to throw as registered readers are wrapped in try catch to avoid breakting
+// unrelated readers.
+parsers["reader/Extractor()"] = ({source}) =>
+  JSONReturn(new Function("return (" + source + ")")());
+
+// If the context-menu target node or any of its ancestors is one of these,
+// Firefox uses a tailored context menu, and so the page context doesn't apply.
+// There for `reader/isPage()` will read `false` in that case otherwise it's going
+// to read `true`.
+const nonPageElements = ["a", "applet", "area", "button", "canvas", "object",
+                         "embed", "img", "input", "map", "video", "audio", "menu",
+                         "option", "select", "textarea", "[contenteditable=true]"];
+const nonPageSelector = nonPageElements.
+                          concat(nonPageElements.map(tag => `${tag} *`)).
+                          join(", ");
+
+// Note: isPageContext implementation could have actually used SelectorMatch reader,
+// but old implementation was also checked for collapsed selection there for to keep
+// the behavior same we end up implementing a new reader.
+parsers["reader/isPage()"] = constant(node =>
+  node.ownerDocument.defaultView.getSelection().isCollapsed &&
+  !node.matches(nonPageSelector));
+
+// Reads `true` if node is in an iframe otherwise returns true.
+parsers["reader/isFrame()"] = constant(node =>
+  !!node.ownerDocument.defaultView.frameElement);
+
+parsers["reader/isEditable()"] = constant(node => {
+  const selection = getInputSelection(node);
+  return selection ? !node.readOnly && !node.disabled : node.isContentEditable;
+});
+
+
+// TODO: Add some reader to read out tab id.
+
+const onReadersUpdate = message => {
+  each(([id, descriptor]) => {
+    if (descriptor) {
+      readers[id] = parse(descriptor);
+    }
+    else {
+      delete readers[id];
+    }
+  }, pairs(message.data));
+};
+exports.onReadersUpdate = onReadersUpdate;
+
+
+const onContextMenu = event => {
+  if (!event.defaultPrevented) {
+    const manager = nodeToMessageManager(event.target);
+    manager.sendSyncMessage("sdk/context-menu/read", read(event.target), readers);
+  }
+};
+exports.onContextMenu = onContextMenu;
+
+
+const onContentFrame = (frame) => {
+  // Listen for contextmenu events in on this frame.
+  frame.addEventListener("contextmenu", onContextMenu);
+  // Listen to registered reader changes and update registry.
+  frame.addMessageListener("sdk/context-menu/readers", onReadersUpdate);
+
+  // Request table of readers (if this is loaded in a new process some table
+  // changes may be missed, this is way to sync up).
+  frame.sendAsyncMessage("sdk/context-menu/readers?");
+};
+exports.onContentFrame = onContentFrame;
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/framescript/manager.js
@@ -0,0 +1,26 @@
+/* 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 mime = "application/javascript";
+const requireURI = module.uri.replace("framescript/manager.js",
+                                      "toolkit/require.js");
+
+const requireLoadURI = `data:${mime},this["Components"].utils.import("${requireURI}")`
+
+// Loads module with given `id` into given `messageManager` via shared module loader. If `init`
+// string is passed, will call module export with that name and pass frame script environment
+// of the `messageManager` into it. Since module will load only once per process (which is
+// once for chrome proces & second for content process) it is useful to have an init function
+// to setup event listeners on each content frame.
+const loadModule = (messageManager, id, allowDelayed, init) => {
+  const moduleLoadURI = `${requireLoadURI}.require("${id}")`
+  const uri = init ? `${moduleLoadURI}.${init}(this)` : moduleLoadURI;
+  messageManager.loadFrameScript(uri, allowDelayed);
+};
+exports.loadModule = loadModule;
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/framescript/util.js
@@ -0,0 +1,25 @@
+/* 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 { Ci } = require("chrome");
+
+const windowToMessageManager = window =>
+  window.
+    QueryInterface(Ci.nsIInterfaceRequestor).
+    getInterface(Ci.nsIDocShell).
+    sameTypeRootTreeItem.
+    QueryInterface(Ci.nsIDocShell).
+    QueryInterface(Ci.nsIInterfaceRequestor).
+    getInterface(Ci.nsIContentFrameMessageManager);
+exports.windowToMessageManager = windowToMessageManager;
+
+const nodeToMessageManager = node =>
+  windowToMessageManager(node.ownerDocument.defaultView);
+exports.nodeToMessageManager = nodeToMessageManager;
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/index.js
@@ -0,0 +1,3 @@
+/* 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/. */
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/addon/bootstrap.js
@@ -0,0 +1,160 @@
+/* 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 { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
+const { Task: { spawn } } = require("resource://gre/modules/Task.jsm");
+const { readURI } = require("sdk/net/url");
+const { mount, unmount } = require("sdk/uri/resource");
+const { setTimeout } = require("sdk/timers");
+const { Loader, Require, Module, main, unload } = require("toolkit/loader");
+const prefs = require("sdk/preferences/service");
+
+// load below now, so that it can be used by sdk/addon/runner
+// see bug https://bugzilla.mozilla.org/show_bug.cgi?id=1042239
+const Startup = Cu.import("resource://gre/modules/sdk/system/Startup.js", {});
+
+const REASON = [ "unknown", "startup", "shutdown", "enable", "disable",
+                 "install", "uninstall", "upgrade", "downgrade" ];
+
+const UUID_PATTERN = /^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/;
+// Takes add-on ID and normalizes it to a domain name so that add-on
+// can be mapped to resource://domain/
+const readDomain = id =>
+  // If only `@` character is the first one, than just substract it,
+  // otherwise fallback to legacy normalization code path. Note: `.`
+  // is valid character for resource substitutaiton & we intend to
+  // make add-on URIs intuitive, so it's best to just stick to an
+  // add-on author typed input.
+  id.lastIndexOf("@") === 0 ? id.substr(1).toLowerCase() :
+  id.toLowerCase().
+     replace(/@/g, "-at-").
+     replace(/\./g, "-dot-").
+     replace(UUID_PATTERN, "$1");
+
+const readPaths = id => {
+  const base = `extensions.modules.${id}.path.`;
+  const domain = readDomain(id);
+  return prefs.keys(base).reduce((paths, key) => {
+    const value = prefs.get(key);
+    const name = key.replace(base, "");
+    const path = name.split(".").join("/");
+    const prefix = path.length ? `${path}/` : path;
+    const uri = value.endsWith("/") ? value : `${value}/`;
+    const root = `extensions.modules.${domain}.commonjs.path.${name}`;
+
+    mount(root, uri);
+
+    paths[prefix] = `resource://${root}/`;
+    return paths;
+  }, {});
+};
+
+const Bootstrap = function(mountURI) {
+  this.mountURI = mountURI;
+  this.install = this.install.bind(this);
+  this.uninstall = this.uninstall.bind(this);
+  this.startup = this.startup.bind(this);
+  this.shutdown = this.shutdown.bind(this);
+};
+Bootstrap.prototype = {
+  constructor: Bootstrap,
+  mount(domain, rootURI) {
+    mount(domain, rootURI);
+    this.domain = domain;
+  },
+  unmount() {
+    if (this.domain) {
+      unmount(this.domain);
+      this.domain = null;
+    }
+  },
+  install(addon, reason) {
+  },
+  uninstall(addon, reason) {
+    const {id} = addon;
+
+    prefs.reset(`extensions.${id}.sdk.domain`);
+    prefs.reset(`extensions.${id}.sdk.version`);
+    prefs.reset(`extensions.${id}.sdk.rootURI`);
+    prefs.reset(`extensions.${id}.sdk.baseURI`);
+    prefs.reset(`extensions.${id}.sdk.load.reason`);
+
+  },
+  startup(addon, reasonCode) {
+    const { id, version, resourceURI: {spec: addonURI} } = addon;
+    const rootURI = this.mountURI || addonURI;
+    const reason = REASON[reasonCode];
+
+    spawn(function*() {
+      const metadata = JSON.parse(yield readURI(`${rootURI}package.json`));
+      const domain = readDomain(id);
+      const baseURI = `resource://${domain}/`;
+
+      this.mount(domain, rootURI);
+
+      prefs.set(`extensions.${id}.sdk.domain`, domain);
+      prefs.set(`extensions.${id}.sdk.version`, version);
+      prefs.set(`extensions.${id}.sdk.rootURI`, rootURI);
+      prefs.set(`extensions.${id}.sdk.baseURI`, baseURI);
+      prefs.set(`extensions.${id}.sdk.load.reason`, reason);
+
+      const command = prefs.get(`extensions.${id}.sdk.load.command`);
+
+      const loader = Loader({
+        id,
+        isNative: true,
+        checkCompatibility: true,
+        prefixURI: baseURI,
+        rootURI: baseURI,
+        name: metadata.name,
+        paths: Object.assign({
+          "": "resource://gre/modules/commonjs/",
+          "devtools/": "resource://gre/modules/devtools/",
+          "./": baseURI
+        }, readPaths(id)),
+        manifest: metadata,
+        metadata: metadata,
+        modules: {
+          "@test/options": {}
+        },
+        noQuit: prefs.get(`extensions.${id}.sdk.test.no-quit`, false)
+      });
+      this.loader = loader;
+
+      const module = Module("package.json", `${baseURI}package.json`);
+      const require = Require(loader, module);
+      const main = command === "test" ? "sdk/test/runner" : null;
+      const prefsURI = `${baseURI}defaults/preferences/prefs.js`;
+
+      const { startup } = require("sdk/addon/runner");
+      startup(reason, {loader, main, prefsURI});
+    }.bind(this)).catch(error => {
+      console.error(`Failed to start ${id} addon`, error);
+      throw error;
+    });
+  },
+  shutdown(addon, code) {
+    const { loader, domain } = this;
+
+    this.unmount();
+    this.unload(REASON[code]);
+  },
+  unload(reason) {
+    const {loader} = this;
+    if (loader) {
+      this.loader = null;
+      unload(loader, reason);
+      setTimeout(() => {
+        for (let uri of Object.keys(loader.sandboxes)) {
+          Cu.nukeSandbox(loader.sandboxes[uri]);
+          delete loader.sandboxes[uri];
+          delete loader.modules[uri];
+        }
+      }, 1000);
+    }
+  }
+};
+exports.Bootstrap = Bootstrap;
--- a/addon-sdk/source/lib/sdk/clipboard.js
+++ b/addon-sdk/source/lib/sdk/clipboard.js
@@ -4,17 +4,18 @@
 
 "use strict";
 
 module.metadata = {
   "stability": "stable",
   "engines": {
     // TODO Fennec Support 789757
     "Firefox": "*",
-    "SeaMonkey": "*"
+    "SeaMonkey": "*",
+    "Thunderbird": "*"
   }
 };
 
 const { Cc, Ci } = require("chrome");
 const { DataURL } = require("./url");
 const errors = require("./deprecated/errors");
 const apiUtils = require("./deprecated/api-utils");
 /*
@@ -119,36 +120,34 @@ exports.set = function(aData, aDataType)
                     "(couldn't create a Transferable object).");
   // Bug 769440: Starting with FF16, transferable have to be inited
   if ("init" in xferable)
     xferable.init(null);
 
   switch (flavor) {
     case "text/html":
       // add text/html flavor
-      let (str = Cc["@mozilla.org/supports-string;1"].
-                 createInstance(Ci.nsISupportsString))
-      {
-        str.data = options.data;
-        xferable.addDataFlavor(flavor);
-        xferable.setTransferData(flavor, str, str.data.length * 2);
-      }
+      let str = Cc["@mozilla.org/supports-string;1"].
+                 createInstance(Ci.nsISupportsString);
+
+      str.data = options.data;
+      xferable.addDataFlavor(flavor);
+      xferable.setTransferData(flavor, str, str.data.length * 2);
 
       // add a text/unicode flavor (html converted to plain text)
-      let (str = Cc["@mozilla.org/supports-string;1"].
-                 createInstance(Ci.nsISupportsString),
-           converter = Cc["@mozilla.org/feed-textconstruct;1"].
-                       createInstance(Ci.nsIFeedTextConstruct))
-      {
-        converter.type = "html";
-        converter.text = options.data;
-        str.data = converter.plainText();
-        xferable.addDataFlavor("text/unicode");
-        xferable.setTransferData("text/unicode", str, str.data.length * 2);
-      }
+      str = Cc["@mozilla.org/supports-string;1"].
+               createInstance(Ci.nsISupportsString);
+      let converter = Cc["@mozilla.org/feed-textconstruct;1"].
+                     createInstance(Ci.nsIFeedTextConstruct);
+
+      converter.type = "html";
+      converter.text = options.data;
+      str.data = converter.plainText();
+      xferable.addDataFlavor("text/unicode");
+      xferable.setTransferData("text/unicode", str, str.data.length * 2);
       break;
 
     // Set images to the clipboard is not straightforward, to have an idea how
     // it works on platform side, see:
     // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsCopySupport.cpp?rev=7857c5bff017#530
     case "image/png":
       let image = options.data;
 
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/content/worker-parent.js
+++ /dev/null
@@ -1,184 +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";
-
-module.metadata = {
-  "stability": "unstable"
-};
-
-const { emit } = require('../event/core');
-const { omit } = require('../util/object');
-const { Class } = require('../core/heritage');
-const { method } = require('../lang/functional');
-const { getInnerId } = require('../window/utils');
-const { EventTarget } = require('../event/target');
-const { when, ensure } = require('../system/unload');
-const { getTabForWindow } = require('../tabs/helpers');
-const { getTabForContentWindow, getBrowserForTab } = require('../tabs/utils');
-const { isPrivate } = require('../private-browsing/utils');
-const { getFrameElement } = require('../window/utils');
-const { attach, detach, destroy } = require('./utils');
-const { on: observe } = require('../system/events');
-const { uuid } = require('../util/uuid');
-const { Ci, Cc } = require('chrome');
-
-const ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"].
-  getService(Ci.nsIMessageBroadcaster);
-
-// null-out cycles in .modules to make @loader/options JSONable
-const ADDON = omit(require('@loader/options'), ['modules', 'globals']);
-
-const workers = new WeakMap();
-let modelFor = (worker) => workers.get(worker);
-
-const ERR_DESTROYED = "Couldn't find the worker to receive this message. " +
-  "The script may not be initialized yet, or may already have been unloaded.";
-
-const ERR_FROZEN = "The page is currently hidden and can no longer be used " +
-                   "until it is visible again.";
-
-// a handle for communication between content script and addon code
-const Worker = Class({
-  implements: [EventTarget],
-  initialize(options = {}) {
-
-    let model = {
-      inited: false,
-      earlyEvents: [],        // fired before worker was inited
-      frozen: true,           // document is in BFcache, let it go
-      options,
-    };
-    workers.set(this, model);
-
-    ensure(this, 'destroy');
-    this.on('detach', this.detach);
-    EventTarget.prototype.initialize.call(this, options);
-
-    this.receive = this.receive.bind(this);
-
-    model.observe = ({ subject }) => {
-      let id = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
-      if (model.window && getInnerId(model.window) === id)
-        this.detach();
-    }
-
-    observe('inner-window-destroyed', model.observe);
-
-    this.port = EventTarget();
-    this.port.emit = this.send.bind(this, 'event');
-    this.postMessage = this.send.bind(this, 'message');
-
-    if ('window' in options)
-      attach(this, options.window);
-  },
-  // messages
-  receive({ data: { id, args }}) {
-    let model = modelFor(this);
-    if (id !== model.id || !model.childWorker)
-      return;
-    if (args[0] === 'event')
-      emit(this.port, ...args.slice(1))
-    else
-      emit(this, ...args);
-  },
-  send(...args) {
-    let model = modelFor(this);
-    if (!model.inited) {
-      model.earlyEvents.push(args);
-      return;
-    }
-    if (!model.childWorker && args[0] !== 'detach')
-      throw new Error(ERR_DESTROYED);
-    if (model.frozen && args[0] !== 'detach')
-      throw new Error(ERR_FROZEN);
-    try {
-      model.manager.sendAsyncMessage('sdk/worker/message', { id: model.id, args });
-    } catch (e) {
-      //
-    }
-  },
-  // properties
-  get url() {
-    let { window } = modelFor(this);
-    return window && window.document.location.href;
-  },
-  get contentURL() {
-    let { window } = modelFor(this);
-    return window && window.document.URL;
-  },
-  get tab() {
-    let { window } = modelFor(this);
-    return window && getTabForWindow(window);
-  },
-  toString: () => '[object Worker]',
-  // methods
-  attach: method(attach),
-  detach: method(detach),
-  destroy: method(destroy),
-})
-exports.Worker = Worker;
-
-attach.define(Worker, function(worker, window) {
-  let model = modelFor(worker);
-
-  model.window = window;
-  model.options.window = getInnerId(window);
-  model.id = model.options.id = String(uuid());
-
-  let tab = getTabForContentWindow(window);
-  if (tab) {
-    model.manager = getBrowserForTab(tab).messageManager;
-  } else {
-    model.manager = getFrameElement(window.top).frameLoader.messageManager;
-  }
-
-  model.manager.addMessageListener('sdk/worker/event', worker.receive);
-  model.manager.addMessageListener('sdk/worker/attach', attach);
-
-  model.manager.sendAsyncMessage('sdk/worker/create', {
-    options: model.options,
-    addon: ADDON
-  });
-
-  function attach({ data }) {
-    if (data.id !== model.id)
-      return;
-    model.manager.removeMessageListener('sdk/worker/attach', attach);
-    model.childWorker = true;
-
-    worker.on('pageshow', () => model.frozen = false);
-    worker.on('pagehide', () => model.frozen = true);
-
-    model.inited = true;
-    model.frozen = false;
-
-    model.earlyEvents.forEach(args => worker.send(...args));
-    emit(worker, 'attach', window);
-  }
-})
-
-// unload and release the child worker, release window reference
-detach.define(Worker, function(worker, reason) {
-  let model = modelFor(worker);
-  worker.send('detach', reason);
-  if (!model.childWorker)
-    return;
-
-  model.childWorker = null;
-  model.earlyEvents = [];
-  model.window = null;
-  emit(worker, 'detach');
-  model.manager.removeMessageListener('sdk/worker/event', this.receive);
-})
-
-isPrivate.define(Worker, ({ tab }) => isPrivate(tab));
-
-// unlod worker, release references
-destroy.define(Worker, function(worker, reason) {
-  detach(worker, reason);
-  modelFor(worker).inited = true;
-})
-
-// unload Loaders used for creating WorkerChild instances in each process
-when(() => ppmm.broadcastAsyncMessage('sdk/loader/unload', { data: ADDON }));
--- a/addon-sdk/source/lib/sdk/content/worker.js
+++ b/addon-sdk/source/lib/sdk/content/worker.js
@@ -2,285 +2,183 @@
  * 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 { emit } = require('../event/core');
+const { omit } = require('../util/object');
 const { Class } = require('../core/heritage');
-const { EventTarget } = require('../event/target');
-const { on, off, emit, setListeners } = require('../event/core');
-const {
-  attach, detach, destroy
-} = require('./utils');
 const { method } = require('../lang/functional');
-const { Ci, Cu, Cc } = require('chrome');
-const unload = require('../system/unload');
-const events = require('../system/events');
-const { getInnerId } = require("../window/utils");
-const { WorkerSandbox } = require('./sandbox');
+const { getInnerId } = require('../window/utils');
+const { EventTarget } = require('../event/target');
+const { when, ensure } = require('../system/unload');
 const { getTabForWindow } = require('../tabs/helpers');
+const { getTabForContentWindow, getBrowserForTab } = require('../tabs/utils');
 const { isPrivate } = require('../private-browsing/utils');
+const { getFrameElement } = require('../window/utils');
+const { attach, detach, destroy } = require('./utils');
+const { on: observe } = require('../system/events');
+const { uuid } = require('../util/uuid');
+const { Ci, Cc } = require('chrome');
 
-// A weak map of workers to hold private attributes that
-// should not be exposed
+const ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"].
+  getService(Ci.nsIMessageBroadcaster);
+
+// null-out cycles in .modules to make @loader/options JSONable
+const ADDON = omit(require('@loader/options'), ['modules', 'globals']);
+
 const workers = new WeakMap();
-
 let modelFor = (worker) => workers.get(worker);
 
-const ERR_DESTROYED =
-  "Couldn't find the worker to receive this message. " +
+const ERR_DESTROYED = "Couldn't find the worker to receive this message. " +
   "The script may not be initialized yet, or may already have been unloaded.";
 
 const ERR_FROZEN = "The page is currently hidden and can no longer be used " +
                    "until it is visible again.";
 
-/**
- * Message-passing facility for communication between code running
- * in the content and add-on process.
- * @see https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/content_worker
- */
+// a handle for communication between content script and addon code
 const Worker = Class({
   implements: [EventTarget],
-  initialize: function WorkerConstructor (options) {
-    // Save model in weak map to not expose properties
-    let model = createModel();
+  initialize(options = {}) {
+
+    let model = {
+      inited: false,
+      earlyEvents: [],        // fired before worker was inited
+      frozen: true,           // document is in BFcache, let it go
+      options,
+    };
     workers.set(this, model);
 
-    options = options || {};
+    ensure(this, 'destroy');
+    this.on('detach', this.detach);
+    EventTarget.prototype.initialize.call(this, options);
 
-    if ('contentScriptFile' in options)
-      this.contentScriptFile = options.contentScriptFile;
-    if ('contentScriptOptions' in options)
-      this.contentScriptOptions = options.contentScriptOptions;
-    if ('contentScript' in options)
-      this.contentScript = options.contentScript;
-    if ('injectInDocument' in options)
-      this.injectInDocument = !!options.injectInDocument;
+    this.receive = this.receive.bind(this);
 
-    setListeners(this, options);
-
-    unload.ensure(this, "destroy");
+    model.observe = ({ subject }) => {
+      let id = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
+      if (model.window && getInnerId(model.window) === id)
+        this.detach();
+    }
 
-    // Ensure that worker.port is initialized for contentWorker to be able
-    // to send events during worker initialization.
-    this.port = createPort(this);
+    observe('inner-window-destroyed', model.observe);
 
-    model.documentUnload = documentUnload.bind(this);
-    model.pageShow = pageShow.bind(this);
-    model.pageHide = pageHide.bind(this);
+    this.port = EventTarget();
+    this.port.emit = this.send.bind(this, 'event');
+    this.postMessage = this.send.bind(this, 'message');
 
     if ('window' in options)
       attach(this, options.window);
   },
-
-  /**
-   * Sends a message to the worker's global scope. Method takes single
-   * argument, which represents data to be sent to the worker. The data may
-   * be any primitive type value or `JSON`. Call of this method asynchronously
-   * emits `message` event with data value in the global scope of this
-   * symbiont.
-   *
-   * `message` event listeners can be set either by calling
-   * `self.on` with a first argument string `"message"` or by
-   * implementing `onMessage` function in the global scope of this worker.
-   * @param {Number|String|JSON} data
-   */
-  postMessage: function (...data) {
+  // messages
+  receive({ data: { id, args }}) {
     let model = modelFor(this);
-    let args = ['message'].concat(data);
+    if (id !== model.id || !model.childWorker)
+      return;
+    if (args[0] === 'event')
+      emit(this.port, ...args.slice(1))
+    else
+      emit(this, ...args);
+  },
+  send(...args) {
+    let model = modelFor(this);
     if (!model.inited) {
       model.earlyEvents.push(args);
       return;
     }
-    processMessage.apply(null, [this].concat(args));
-  },
-
-  get url () {
-    let model = modelFor(this);
-    // model.window will be null after detach
-    return model.window ? model.window.document.location.href : null;
-  },
-
-  get contentURL () {
-    let model = modelFor(this);
-    return model.window ? model.window.document.URL : null;
+    if (!model.childWorker && args[0] !== 'detach')
+      throw new Error(ERR_DESTROYED);
+    if (model.frozen && args[0] !== 'detach')
+      throw new Error(ERR_FROZEN);
+    try {
+      model.manager.sendAsyncMessage('sdk/worker/message', { id: model.id, args });
+    } catch (e) {
+      //
+    }
   },
-
-  get tab () {
-    let model = modelFor(this);
-    // model.window will be null after detach
-    if (model.window)
-      return getTabForWindow(model.window);
-    return null;
+  // properties
+  get url() {
+    let { window } = modelFor(this);
+    return window && window.document.location.href;
   },
-
-  // Implemented to provide some of the previous features of exposing sandbox
-  // so that Worker can be extended
-  getSandbox: function () {
-    return modelFor(this).contentWorker;
+  get contentURL() {
+    let { window } = modelFor(this);
+    return window && window.document.URL;
   },
-
-  toString: function () { return '[object Worker]'; },
+  get tab() {
+    let { window } = modelFor(this);
+    return window && getTabForWindow(window);
+  },
+  toString: () => '[object Worker]',
+  // methods
   attach: method(attach),
   detach: method(detach),
-  destroy: method(destroy)
-});
+  destroy: method(destroy),
+})
 exports.Worker = Worker;
 
-attach.define(Worker, function (worker, window) {
-  let model = modelFor(worker);
-  model.window = window;
-  // Track document unload to destroy this worker.
-  // We can't watch for unload event on page's window object as it
-  // prevents bfcache from working:
-  // https://developer.mozilla.org/En/Working_with_BFCache
-  model.windowID = getInnerId(model.window);
-  events.on("inner-window-destroyed", model.documentUnload);
-
-  // will set model.contentWorker pointing to the private API:
-  model.contentWorker = WorkerSandbox(worker, model.window);
-
-  // Listen to pagehide event in order to freeze the content script
-  // while the document is frozen in bfcache:
-  model.window.addEventListener("pageshow", model.pageShow, true);
-  model.window.addEventListener("pagehide", model.pageHide, true);
-
-  // Mainly enable worker.port.emit to send event to the content worker
-  model.inited = true;
-  model.frozen = false;
-
-  // Fire off `attach` event
-  emit(worker, 'attach', window);
-
-  // Process all events and messages that were fired before the
-  // worker was initialized.
-  model.earlyEvents.forEach(args => processMessage.apply(null, [worker].concat(args)));
-});
-
-/**
- * Remove all internal references to the attached document
- * Tells _port to unload itself and removes all the references from itself.
- */
-detach.define(Worker, function (worker, reason) {
+attach.define(Worker, function(worker, window) {
   let model = modelFor(worker);
 
-  // maybe unloaded before content side is created
-  if (model.contentWorker) {
-    model.contentWorker.destroy(reason);
+  model.window = window;
+  model.options.window = getInnerId(window);
+  model.id = model.options.id = String(uuid());
+
+  let tab = getTabForContentWindow(window);
+  if (tab) {
+    model.manager = getBrowserForTab(tab).messageManager;
+  } else {
+    model.manager = getFrameElement(window.top).frameLoader.messageManager;
   }
 
-  model.contentWorker = null;
-  if (model.window) {
-    model.window.removeEventListener("pageshow", model.pageShow, true);
-    model.window.removeEventListener("pagehide", model.pageHide, true);
+  model.manager.addMessageListener('sdk/worker/event', worker.receive);
+  model.manager.addMessageListener('sdk/worker/attach', attach);
+
+  model.manager.sendAsyncMessage('sdk/worker/create', {
+    options: model.options,
+    addon: ADDON
+  });
+
+  function attach({ data }) {
+    if (data.id !== model.id)
+      return;
+    model.manager.removeMessageListener('sdk/worker/attach', attach);
+    model.childWorker = true;
+
+    worker.on('pageshow', () => model.frozen = false);
+    worker.on('pagehide', () => model.frozen = true);
+
+    model.inited = true;
+    model.frozen = false;
+
+    model.earlyEvents.forEach(args => worker.send(...args));
+    emit(worker, 'attach', window);
   }
+})
+
+// unload and release the child worker, release window reference
+detach.define(Worker, function(worker, reason) {
+  let model = modelFor(worker);
+  worker.send('detach', reason);
+  if (!model.childWorker)
+    return;
+
+  model.childWorker = null;
+  model.earlyEvents = [];
   model.window = null;
-  // This method may be called multiple times,
-  // avoid dispatching `detach` event more than once
-  if (model.windowID) {
-    model.windowID = null;
-    events.off("inner-window-destroyed", model.documentUnload);
-    model.earlyEvents.length = 0;
-    emit(worker, 'detach');
-  }
-  model.inited = false;
-});
+  emit(worker, 'detach');
+  model.manager.removeMessageListener('sdk/worker/event', this.receive);
+})
 
 isPrivate.define(Worker, ({ tab }) => isPrivate(tab));
 
-/**
- * Tells content worker to unload itself and
- * removes all the references from itself.
- */
-destroy.define(Worker, function (worker, reason) {
+// unlod worker, release references
+destroy.define(Worker, function(worker, reason) {
   detach(worker, reason);
   modelFor(worker).inited = true;
-  // Specifying no type or listener removes all listeners
-  // from target
-  off(worker);
-  off(worker.port);
-});
-
-/**
- * Events fired by workers
- */
-function documentUnload ({ subject, data }) {
-  let model = modelFor(this);
-  let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
-  if (innerWinID != model.windowID) return false;
-  detach(this);
-  return true;
-}
-
-function pageShow () {
-  let model = modelFor(this);
-  model.contentWorker.emitSync('pageshow');
-  emit(this, 'pageshow');
-  model.frozen = false;
-}
-
-function pageHide () {
-  let model = modelFor(this);
-  model.contentWorker.emitSync('pagehide');
-  emit(this, 'pagehide');
-  model.frozen = true;
-}
-
-/**
- * Fired from postMessage and emitEventToContent, or from the earlyMessage
- * queue when fired before the content is loaded. Sends arguments to
- * contentWorker if able
- */
+})
 
-function processMessage (worker, ...args) {
-  let model = modelFor(worker) || {};
-  if (!model.contentWorker)
-    throw new Error(ERR_DESTROYED);
-  if (model.frozen)
-    throw new Error(ERR_FROZEN);
-  model.contentWorker.emit.apply(null, args);
-}
-
-function createModel () {
-  return {
-    // List of messages fired before worker is initialized
-    earlyEvents: [],
-    // Is worker connected to the content worker sandbox ?
-    inited: false,
-    // Is worker being frozen? i.e related document is frozen in bfcache.
-    // Content script should not be reachable if frozen.
-    frozen: true,
-    /**
-     * Reference to the content side of the worker.
-     * @type {WorkerGlobalScope}
-     */
-    contentWorker: null,
-    /**
-     * Reference to the window that is accessible from
-     * the content scripts.
-     * @type {Object}
-     */
-    window: null
-  };
-}
-
-function createPort (worker) {
-  let port = EventTarget();
-  port.emit = emitEventToContent.bind(null, worker);
-  return port;
-}
-
-/**
- * Emit a custom event to the content script,
- * i.e. emit this event on `self.port`
- */
-function emitEventToContent (worker, ...eventArgs) {
-  let model = modelFor(worker);
-  let args = ['event'].concat(eventArgs);
-  if (!model.inited) {
-    model.earlyEvents.push(args);
-    return;
-  }
-  processMessage.apply(null, [worker].concat(args));
-}
+// unload Loaders used for creating WorkerChild instances in each process
+when(() => ppmm.broadcastAsyncMessage('sdk/loader/unload', { data: ADDON }));
--- a/addon-sdk/source/lib/sdk/context-menu.js
+++ b/addon-sdk/source/lib/sdk/context-menu.js
@@ -16,17 +16,16 @@ const { Class, mix } = require("./core/h
 const { addCollectionProperty } = require("./util/collection");
 const { ns } = require("./core/namespace");
 const { validateOptions, getTypeOf } = require("./deprecated/api-utils");
 const { URL, isValidURI } = require("./url");
 const { WindowTracker, browserWindowIterator } = require("./deprecated/window-utils");
 const { isBrowser, getInnerId } = require("./window/utils");
 const { Ci, Cc, Cu } = require("chrome");
 const { MatchPattern } = require("./util/match-pattern");
-const { Worker } = require("./content/worker");
 const { EventTarget } = require("./event/target");
 const { emit } = require('./event/core');
 const { when } = require('./system/unload');
 const { contract: loaderContract } = require('./content/loader');
 const { omit } = require('./util/object');
 const self = require('./self')
 
 // null-out cycles in .modules to make @loader/options JSONable
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/context-menu/context.js
@@ -0,0 +1,143 @@
+const { Class } = require("../core/heritage");
+const { extend } = require("../util/object");
+const { MatchPattern } = require("../util/match-pattern");
+const readers = require("./readers");
+
+// Context class is required to implement a single `isCurrent(target)` method
+// that must return boolean value indicating weather given target matches a
+// context or not. Most context implementations below will have an associated
+// reader that way context implementation can setup a reader to extract necessary
+// information to make decision if target is matching a context.
+const Context = Class({
+  isRequired: false,
+  isCurrent(target) {
+    throw Error("Context class must implement isCurrent(target) method");
+  },
+  get required() {
+    Object.defineProperty(this, "required", {
+      value: Object.assign(Object.create(Object.getPrototypeOf(this)),
+                           this,
+                           {isRequired: true})
+    });
+    return this.required;
+  }
+});
+Context.required = function(...params) {
+  return Object.assign(new this(...params), {isRequired: true});
+};
+exports.Context = Context;
+
+
+// Next few context implementations use an associated reader to extract info
+// from the context target and story it to a private symbol associtaed with
+// a context implementation. That way name collisions are avoided while required
+// information is still carried along.
+const isPage = Symbol("context/page?")
+const PageContext = Class({
+  extends: Context,
+  read: {[isPage]: new readers.isPage()},
+  isCurrent: target => target[isPage]
+});
+exports.Page = PageContext;
+
+const isFrame = Symbol("context/frame?");
+const FrameContext = Class({
+  extends: Context,
+  read: {[isFrame]: new readers.isFrame()},
+  isCurrent: target => target[isFrame]
+});
+exports.Frame = FrameContext;
+
+const selection = Symbol("context/selection")
+const SelectionContext = Class({
+  read: {[selection]: new readers.Selection()},
+  isCurrent: target => !!target[selection]
+});
+exports.Selection = SelectionContext;
+
+const link = Symbol("context/link");
+const LinkContext = Class({
+  extends: Context,
+  read: {[link]: new readers.LinkURL()},
+  isCurrent: target => !!target[link]
+});
+exports.Link = LinkContext;
+
+const isEditable = Symbol("context/editable?")
+const EditableContext = Class({
+  extends: Context,
+  read: {[isEditable]: new readers.isEditable()},
+  isCurrent: target => target[isEditable]
+});
+exports.Editable = EditableContext;
+
+
+const mediaType = Symbol("context/mediaType")
+
+const ImageContext = Class({
+  extends: Context,
+  read: {[mediaType]: new readers.MediaType()},
+  isCurrent: target => target[mediaType] === "image"
+});
+exports.Image = ImageContext;
+
+
+const VideoContext = Class({
+  extends: Context,
+  read: {[mediaType]: new readers.MediaType()},
+  isCurrent: target => target[mediaType] === "video"
+});
+exports.Video = VideoContext;
+
+
+const AudioContext = Class({
+  extends: Context,
+  read: {[mediaType]: new readers.MediaType()},
+  isCurrent: target => target[mediaType] === "audio"
+});
+exports.Audio = AudioContext;
+
+const isSelectorMatch = Symbol("context/selector/mathches?")
+const SelectorContext = Class({
+  extends: Context,
+  initialize(selector) {
+    this.selector = selector;
+    // Each instance of selector context will need to store read
+    // data into different field, so that case with multilpe selector
+    // contexts won't cause a conflicts.
+    this[isSelectorMatch] = Symbol(selector);
+    this.read = {[this[isSelectorMatch]]: new readers.SelectorMatch(selector)};
+  },
+  isCurrent(target) {
+    return target[this[isSelectorMatch]];
+  }
+});
+exports.Selector = SelectorContext;
+
+const url = Symbol("context/url");
+const URLContext = Class({
+  extends: Context,
+  initialize(pattern) {
+    this.pattern = new MatchPattern(pattern);
+  },
+  read: {[url]: new readers.PageURL()},
+  isCurrent(target) {
+    return this.pattern.test(target[url]);
+  }
+});
+exports.URL = URLContext;
+
+var PredicateContext = Class({
+  extends: Context,
+  initialize(isMatch) {
+    if (typeof(isMatch) !== "function") {
+      throw TypeError("Predicate context mus be passed a function");
+    }
+
+    this.isMatch = isMatch
+  },
+  isCurrent(target) {
+    return this.isMatch(target);
+  }
+});
+exports.Predicate = PredicateContext;
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/context-menu/core.js
@@ -0,0 +1,382 @@
+"use strict";
+
+
+const Contexts = require("./context");
+const Readers = require("./readers");
+const Component = require("../ui/component");
+const { Class } = require("../core/heritage");
+const { map, filter, object, reduce, keys, symbols,
+        pairs, values, each, some, isEvery, count } = require("../util/sequence");
+const { loadModule } = require("framescript/manager");
+const { Cu, Cc, Ci } = require("chrome");
+const prefs = require("sdk/preferences/service");
+
+const globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"]
+                              .getService(Ci.nsIMessageListenerManager);
+const preferencesService = Cc["@mozilla.org/preferences-service;1"].
+                            getService(Ci.nsIPrefService).
+                            getBranch(null);
+
+
+const readTable = Symbol("context-menu/read-table");
+const nameTable = Symbol("context-menu/name-table");
+const onContext = Symbol("context-menu/on-context");
+const isMatching = Symbol("context-menu/matching-handler?");
+
+exports.onContext = onContext;
+exports.readTable = readTable;
+exports.nameTable = nameTable;
+
+
+const propagateOnContext = (item, data) =>
+  each(child => child[onContext](data), item.state.children);
+
+const isContextMatch = item => !item[isMatching] || item[isMatching]();
+
+// For whatever reason addWeakMessageListener does not seems to work as our
+// instance seems to dropped even though it's alive. This is simple workaround
+// to avoid dead object excetptions.
+const WeakMessageListener = function(receiver, handler="receiveMessage") {
+  this.receiver = receiver
+  this.handler = handler
+};
+WeakMessageListener.prototype = {
+  constructor: WeakMessageListener,
+  receiveMessage(message) {
+    if (Cu.isDeadWrapper(this.receiver)) {
+      message.target.messageManager.removeMessageListener(message.name, this);
+    }
+    else {
+      this.receiver[this.handler](message);
+    }
+  }
+};
+
+const OVERFLOW_THRESH = "extensions.addon-sdk.context-menu.overflowThreshold";
+const onMessage = Symbol("context-menu/message-listener");
+const onPreferceChange = Symbol("context-menu/preference-change");
+const ContextMenuExtension = Class({
+  extends: Component,
+  initialize: Component,
+  setup() {
+    const messageListener = new WeakMessageListener(this, onMessage);
+    loadModule(globalMessageManager, "framescript/context-menu", true, "onContentFrame");
+    globalMessageManager.addMessageListener("sdk/context-menu/read", messageListener);
+    globalMessageManager.addMessageListener("sdk/context-menu/readers?", messageListener);
+
+    preferencesService.addObserver(OVERFLOW_THRESH, this, false);
+  },
+  observe(_, __, name) {
+    if (name === OVERFLOW_THRESH) {
+      const overflowThreshold = prefs.get(OVERFLOW_THRESH, 10);
+      this[Component.patch]({overflowThreshold});
+    }
+  },
+  [onMessage]({name, data, target}) {
+    if (name === "sdk/context-menu/read")
+      this[onContext]({target, data});
+    if (name === "sdk/context-menu/readers?")
+      target.messageManager.sendAsyncMessage("sdk/context-menu/readers",
+                                             JSON.parse(JSON.stringify(this.state.readers)));
+  },
+  [Component.initial](options={}, children) {
+    const element = options.element || null;
+    const target = options.target || null;
+    const readers = Object.create(null);
+    const users = Object.create(null);
+    const registry = new WeakSet();
+    const overflowThreshold = prefs.get(OVERFLOW_THRESH, 10);
+
+    return { target, children: [], readers, users, element,
+             registry, overflowThreshold };
+  },
+  [Component.isUpdated](before, after) {
+    // Update only if target changed, since there is no point in re-rendering
+    // when children are. Also new items added won't be in sync with a latest
+    // context target so we should really just render before drawing context
+    // menu.
+    return before.target !== after.target;
+  },
+  [Component.render]({element, children, overflowThreshold}) {
+    if (!element) return null;
+
+    const items = children.filter(isContextMatch);
+    const body = items.length === 0 ? items :
+                 items.length < overflowThreshold ? [new Separator(),
+                                                     ...items] :
+                 [{tagName: "menu",
+                   className: "sdk-context-menu-overflow-menu",
+                   label: "Add-ons",
+                   accesskey: "A",
+                   children: [{tagName: "menupopup",
+                               children: items}]}];
+    return {
+      element: element,
+      tagName: "menugroup",
+      style: "-moz-box-orient: vertical;",
+      className: "sdk-context-menu-extension",
+      children: body
+    }
+  },
+  // Adds / remove child to it's own list.
+  add(item) {
+    this[Component.patch]({children: this.state.children.concat(item)});
+  },
+  remove(item) {
+    this[Component.patch]({
+      children: this.state.children.filter(x => x !== item)
+    });
+  },
+  register(item) {
+    const { users, registry } = this.state;
+    if (registry.has(item)) return;
+    registry.add(item);
+
+    // Each (ContextHandler) item has a readTable that is a
+    // map of keys to readers extracting them from the content.
+    // During the registraction we update intrnal record of unique
+    // readers and users per reader. Most context will have a reader
+    // shared across all instances there for map of users per reader
+    // is stored separately from the reader so that removing reader
+    // will occur only when no users remain.
+    const table = item[readTable];
+    // Context readers store data in private symbols so we need to
+    // collect both table keys and private symbols.
+    const names = [...keys(table), ...symbols(table)];
+    const readers = map(name => table[name], names);
+    // Create delta for registered readers that will be merged into
+    // internal readers table.
+    const added = filter(x => !users[x.id], readers);
+    const delta = object(...map(x => [x.id, x], added));
+
+    const update = reduce((update, reader) => {
+      const n = update[reader.id] || 0;
+      update[reader.id] = n + 1;
+      return update;
+    }, Object.assign({}, users), readers);
+
+    // Patch current state with a changes that registered item caused.
+    this[Component.patch]({users: update,
+                           readers: Object.assign(this.state.readers, delta)});
+
+    if (count(added)) {
+      globalMessageManager.broadcastAsyncMessage("sdk/context-menu/readers",
+                                                 JSON.parse(JSON.stringify(delta)));
+    }
+  },
+  unregister(item) {
+    const { users, registry } = this.state;
+    if (!registry.has(item)) return;
+    registry.delete(item);
+
+    const table = item[readTable];
+    const names = [...keys(table), ...symbols(table)];
+    const readers = map(name => table[name], names);
+    const update = reduce((update, reader) => {
+      update[reader.id] = update[reader.id] - 1;
+      return update;
+    }, Object.assign({}, users), readers);
+    const removed = filter(id => !update[id], keys(update));
+    const delta = object(...map(x => [x, null], removed));
+
+    this[Component.patch]({users: update,
+                           readers: Object.assign(this.state.readers, delta)});
+
+    if (count(removed)) {
+      globalMessageManager.broadcastAsyncMessage("sdk/context-menu/readers",
+                                                 JSON.parse(JSON.stringify(delta)));
+    }
+  },
+
+  [onContext]({data, target}) {
+    propagateOnContext(this, data);
+    const document = target.ownerDocument;
+    const element = document.getElementById("contentAreaContextMenu");
+
+    this[Component.patch]({target: data, element: element});
+  }
+});this,
+exports.ContextMenuExtension = ContextMenuExtension;
+
+// Takes an item options and
+const makeReadTable = ({context, read}) => {
+  // Result of this function is a tuple of all readers &
+  // name, reader id pairs.
+
+  // Filter down to contexts that have a reader associated.
+  const contexts = filter(context => context.read, context);
+  // Merge all contexts read maps to a single hash, note that there should be
+  // no name collisions as context implementations expect to use private
+  // symbols for storing it's read data.
+  return Object.assign({}, ...map(({read}) => read, contexts), read);
+}
+
+const readTarget = (nameTable, data) =>
+  object(...map(([name, id]) => [name, data[id]], nameTable))
+
+const ContextHandler = Class({
+  extends: Component,
+  initialize: Component,
+  get context() {
+    return this.state.options.context;
+  },
+  get read() {
+    return this.state.options.read;
+  },
+  [Component.initial](options) {
+    return {
+      table: makeReadTable(options),
+      requiredContext: filter(context => context.isRequired, options.context),
+      optionalContext: filter(context => !context.isRequired, options.context)
+    }
+  },
+  [isMatching]() {
+    const {target, requiredContext, optionalContext} = this.state;
+    return isEvery(context => context.isCurrent(target), requiredContext) &&
+            (count(optionalContext) === 0 ||
+             some(context => context.isCurrent(target), optionalContext));
+  },
+  setup() {
+    const table = makeReadTable(this.state.options);
+    this[readTable] = table;
+    this[nameTable] = [...map(symbol => [symbol, table[symbol].id], symbols(table)),
+                       ...map(name => [name, table[name].id], keys(table))];
+
+
+    contextMenu.register(this);
+
+    each(child => contextMenu.remove(child), this.state.children);
+    contextMenu.add(this);
+  },
+  dispose() {
+    contextMenu.remove(this);
+
+    each(child => contextMenu.unregister(child), this.state.children);
+    contextMenu.unregister(this);
+  },
+  // Internal `Symbol("onContext")` method is invoked when "contextmenu" event
+  // occurs in content process. Context handles with children delegate to each
+  // child and patch it's internal state to reflect new contextmenu target.
+  [onContext](data) {
+    propagateOnContext(this, data);
+    this[Component.patch]({target: readTarget(this[nameTable], data)});
+  }
+});
+const isContextHandler = item => item instanceof ContextHandler;
+
+exports.ContextHandler = ContextHandler;
+
+const Menu = Class({
+  extends: ContextHandler,
+  [isMatching]() {
+    return ContextHandler.prototype[isMatching].call(this) &&
+           this.state.children.filter(isContextHandler)
+                              .some(isContextMatch);
+  },
+  [Component.render]({children, options}) {
+    const items = children.filter(isContextMatch);
+    return {tagName: "menu",
+            className: "sdk-context-menu menu-iconic",
+            label: options.label,
+            accesskey: options.accesskey,
+            image: options.icon,
+            children: [{tagName: "menupopup",
+                        children: items}]};
+  }
+});
+exports.Menu = Menu;
+
+const onCommand = Symbol("context-menu/item/onCommand");
+const Item = Class({
+  extends: ContextHandler,
+  get onClick() {
+    return this.state.options.onClick;
+  },
+  [Component.render]({options}) {
+    const {label, icon, accesskey} = options;
+    return {tagName: "menuitem",
+            className: "sdk-context-menu-item menuitem-iconic",
+            label,
+            accesskey,
+            image: icon,
+            oncommand: this};
+  },
+  handleEvent(event) {
+    if (this.onClick)
+      this.onClick(this.state.target);
+  }
+});
+exports.Item = Item;
+
+var Separator = Class({
+  extends: Component,
+  initialize: Component,
+  [Component.render]() {
+    return {tagName: "menuseparator",
+            className: "sdk-context-menu-separator"}
+  },
+  [onContext]() {
+
+  }
+});
+exports.Separator = Separator;
+
+exports.Contexts = Contexts;
+exports.Readers = Readers;
+
+const createElement = (vnode, {document}) => {
+   const node = vnode.namespace ?
+              document.createElementNS(vnode.namespace, vnode.tagName) :
+              document.createElement(vnode.tagName);
+
+   node.setAttribute("data-component-path", vnode[Component.path]);
+
+   each(([key, value]) => {
+     if (key === "tagName") {
+       return;
+     }
+     if (key === "children") {
+       return;
+     }
+
+     if (key.startsWith("on")) {
+       node.addEventListener(key.substr(2), value)
+       return;
+     }
+
+     if (typeof(value) !== "object" &&
+         typeof(value) !== "function" &&
+         value !== void(0) &&
+         value !== null)
+    {
+       if (key === "className") {
+         node[key] = value;
+       }
+       else {
+         node.setAttribute(key, value);
+       }
+       return;
+     }
+   }, pairs(vnode));
+
+  each(child => node.appendChild(createElement(child, {document})), vnode.children);
+  return node;
+};
+
+const htmlWriter = tree => {
+  if (tree !== null) {
+    const root = tree.element;
+    const node = createElement(tree, {document: root.ownerDocument});
+    const before = root.querySelector("[data-component-path='/']");
+    if (before) {
+      root.replaceChild(node, before);
+    } else {
+      root.appendChild(node);
+    }
+  }
+};
+
+
+const contextMenu = ContextMenuExtension();
+exports.contextMenu = contextMenu;
+Component.mount(contextMenu, htmlWriter);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/context-menu/readers.js
@@ -0,0 +1,109 @@
+const { Class } = require("../core/heritage");
+const { extend } = require("../util/object");
+const { memoize, method, identity } = require("../lang/functional");
+
+const serializeCategory = ({type}) => ({ category: `reader/${type}()` });
+
+const Reader = Class({
+  initialize() {
+    this.id = `reader/${this.type}()`
+  },
+  toJSON() {
+    return serializeCategory(this);
+  }
+});
+
+
+const MediaTypeReader = Class({ extends: Reader, type: "MediaType" });
+exports.MediaType = MediaTypeReader;
+
+const LinkURLReader = Class({ extends: Reader, type: "LinkURL" });
+exports.LinkURL = LinkURLReader;
+
+const SelectionReader = Class({ extends: Reader, type: "Selection" });
+exports.Selection = SelectionReader;
+
+const isPageReader = Class({ extends: Reader, type: "isPage" });
+exports.isPage = isPageReader;
+
+const isFrameReader = Class({ extends: Reader, type: "isFrame" });
+exports.isFrame = isFrameReader;
+
+const isEditable = Class({ extends: Reader, type: "isEditable"});
+exports.isEditable = isEditable;
+
+
+
+const ParameterizedReader = Class({
+  extends: Reader,
+  readParameter: function(value) {
+    return value;
+  },
+  toJSON: function() {
+    var json = serializeCategory(this);
+    json[this.parameter] = this[this.parameter];
+    return json;
+  },
+  initialize(...params) {
+    if (params.length) {
+      this[this.parameter] = this.readParameter(...params);
+    }
+    this.id = `reader/${this.type}(${JSON.stringify(this[this.parameter])})`;
+  }
+});
+exports.ParameterizedReader = ParameterizedReader;
+
+
+const QueryReader = Class({
+  extends: ParameterizedReader,
+  type: "Query",
+  parameter: "path"
+});
+exports.Query = QueryReader;
+
+
+const AttributeReader = Class({
+  extends: ParameterizedReader,
+  type: "Attribute",
+  parameter: "name"
+});
+exports.Attribute = AttributeReader;
+
+const SrcURLReader = Class({
+  extends: AttributeReader,
+  name: "src",
+});
+exports.SrcURL = SrcURLReader;
+
+const PageURLReader = Class({
+  extends: QueryReader,
+  path: "ownerDocument.URL",
+});
+exports.PageURL = PageURLReader;
+
+const SelectorMatchReader = Class({
+  extends: ParameterizedReader,
+  type: "SelectorMatch",
+  parameter: "selector"
+});
+exports.SelectorMatch = SelectorMatchReader;
+
+const extractors = new WeakMap();
+extractors.id = 0;
+
+
+var Extractor = Class({
+  extends: ParameterizedReader,
+  type: "Extractor",
+  parameter: "source",
+  initialize: function(f) {
+    this[this.parameter] = String(f);
+    if (!extractors.has(f)) {
+      extractors.id = extractors.id + 1;
+      extractors.set(f, extractors.id);
+    }
+
+    this.id = `reader/${this.type}.for(${extractors.get(f)})`
+  }
+});
+exports.Extractor = Extractor;
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/context-menu@2.js
@@ -0,0 +1,29 @@
+"use strict";
+
+const shared = require("toolkit/require");
+const { Item, Separator, Menu, Contexts, Readers } = shared.require("sdk/context-menu/core");
+const { setupDisposable, disposeDisposable, Disposable } = require("sdk/core/disposable")
+const { Class } = require("sdk/core/heritage")
+
+const makeDisposable = Type => Class({
+  extends: Type,
+  implements: [Disposable],
+  initialize: Type.prototype.initialize,
+  setup(...params) {
+    Type.prototype.setup.call(this, ...params);
+    setupDisposable(this);
+  },
+  dispose(...params) {
+    disposeDisposable(this);
+    Type.prototype.dispose.call(this, ...params);
+  }
+});
+
+exports.Separator = Separator;
+exports.Contexts = Contexts;
+exports.Readers = Readers;
+
+// Subclass Item & Menu shared classes so their items
+// will be unloaded when add-on is unloaded.
+exports.Item = makeDisposable(Item);
+exports.Menu = makeDisposable(Menu);
--- a/addon-sdk/source/lib/sdk/core/disposable.js
+++ b/addon-sdk/source/lib/sdk/core/disposable.js
@@ -47,21 +47,23 @@ const setup = method("disposable/setup")
 exports.setup = setup;
 setup.define(Object, (object, ...args) => object.setup(...args));
 
 
 // Set's up disposable instance.
 const setupDisposable = disposable => {
   subscribe(disposable, addonUnloadTopic, isWeak(disposable));
 };
+exports.setupDisposable = setupDisposable;
 
 // Tears down disposable instance.
 const disposeDisposable = disposable => {
   unsubscribe(disposable, addonUnloadTopic);
 };
+exports.disposeDisposable = disposeDisposable;
 
 // Base type that takes care of disposing it's instances on add-on unload.
 // Also makes sure to remove unload listener if it's already being disposed.
 const Disposable = Class({
   implements: [Observer],
   initialize: function(...args) {
     // First setup instance before initializing it's disposal. If instance
     // fails to initialize then there is no instance to be disposed at the
@@ -124,9 +126,8 @@ downgrade.define(Disposable, dispose);
 upgrade.define(Disposable, dispose);
 uninstall.define(Disposable, dispose);
 
 // If application is shut down no dispose is invoked as undo-ing
 // changes made by instance is likely to just waste of resources &
 // increase shutdown time. Although specefic components may choose
 // to implement shutdown handler that does something better.
 shutdown.define(Disposable, disposable => {});
-
--- a/addon-sdk/source/lib/sdk/deprecated/cortex.js
+++ b/addon-sdk/source/lib/sdk/deprecated/cortex.js
@@ -1,12 +1,11 @@
 /* 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": "deprecated"
 };
 
 const getOwnIdentifiers = x => [...Object.getOwnPropertyNames(x),
                                 ...Object.getOwnPropertySymbols(x)];
--- a/addon-sdk/source/lib/sdk/deprecated/events.js
+++ b/addon-sdk/source/lib/sdk/deprecated/events.js
@@ -1,12 +1,11 @@
 /* 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": "deprecated"
 };
 
 const ERROR_TYPE = 'error',
       UNCAUGHT_ERROR = 'An error event was dispatched for which there was'
--- a/addon-sdk/source/lib/sdk/deprecated/events/assembler.js
+++ b/addon-sdk/source/lib/sdk/deprecated/events/assembler.js
@@ -1,39 +1,43 @@
 /* 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("../light-traits");
+const { Class } = require("../../core/heritage");
 const { removeListener, on } = require("../../dom/events");
 
 /**
  * Trait may be used for building objects / composing traits that wish to handle
  * multiple dom events from multiple event targets in one place. Event targets
  * can be added / removed by calling `observe / ignore` methods. Composer should
  * provide array of event types it wishes to handle as property
  * `supportedEventsTypes` and function for handling all those events as
  * `handleEvent` property.
  */
-exports.DOMEventAssembler = Trait({
+exports.DOMEventAssembler = Class({
   /**
    * Function that is supposed to handle all the supported events (that are
    * present in the `supportedEventsTypes`) from all the observed
    * `eventTargets`.
    * @param {Event} event
    *    Event being dispatched.
    */
-  handleEvent: Trait.required,
+  handleEvent() {
+    throw new TypeError("Instance of DOMEventAssembler must implement `handleEvent` method");
+  },
   /**
    * Array of supported event names.
    * @type {String[]}
    */
-  supportedEventsTypes: Trait.required,
+  get supportedEventsTypes() {
+    throw new TypeError("Instance of DOMEventAssembler must implement `handleEvent` field");
+  },
   /**
    * Adds `eventTarget` to the list of observed `eventTarget`s. Listeners for
    * supported events will be registered on the given `eventTarget`.
    * @param {EventTarget} eventTarget
    */
   observe: function observe(eventTarget) {
     this.supportedEventsTypes.forEach(function(eventType) {
       on(eventTarget, eventType, this);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/deprecated/sync-worker.js
@@ -0,0 +1,297 @@
+/* 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/. */
+
+/**
+ *
+ * `deprecated/sync-worker` was previously `content/worker`, that was
+ * incompatible with e10s. we are in the process of switching to the new
+ * asynchronous `Worker`, which behaves slightly differently in some edge
+ * cases, so we are keeping this one around for a short period.
+ * try to switch to the new one as soon as possible..
+ *
+ */
+
+"use strict";
+
+module.metadata = {
+  "stability": "unstable"
+};
+
+const { Class } = require('../core/heritage');
+const { EventTarget } = require('../event/target');
+const { on, off, emit, setListeners } = require('../event/core');
+const {
+  attach, detach, destroy
+} = require('../content/utils');
+const { method } = require('../lang/functional');
+const { Ci, Cu, Cc } = require('chrome');
+const unload = require('../system/unload');
+const events = require('../system/events');
+const { getInnerId } = require("../window/utils");
+const { WorkerSandbox } = require('../content/sandbox');
+const { getTabForWindow } = require('../tabs/helpers');
+const { isPrivate } = require('../private-browsing/utils');
+
+// A weak map of workers to hold private attributes that
+// should not be exposed
+const workers = new WeakMap();
+
+let modelFor = (worker) => workers.get(worker);
+
+const ERR_DESTROYED =
+  "Couldn't find the worker to receive this message. " +
+  "The script may not be initialized yet, or may already have been unloaded.";
+
+const ERR_FROZEN = "The page is currently hidden and can no longer be used " +
+                   "until it is visible again.";
+
+/**
+ * Message-passing facility for communication between code running
+ * in the content and add-on process.
+ * @see https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/content_worker
+ */
+const Worker = Class({
+  implements: [EventTarget],
+  initialize: function WorkerConstructor (options) {
+    // Save model in weak map to not expose properties
+    let model = createModel();
+    workers.set(this, model);
+
+    options = options || {};
+
+    if ('contentScriptFile' in options)
+      this.contentScriptFile = options.contentScriptFile;
+    if ('contentScriptOptions' in options)
+      this.contentScriptOptions = options.contentScriptOptions;
+    if ('contentScript' in options)
+      this.contentScript = options.contentScript;
+    if ('injectInDocument' in options)
+      this.injectInDocument = !!options.injectInDocument;
+
+    setListeners(this, options);
+
+    unload.ensure(this, "destroy");
+
+    // Ensure that worker.port is initialized for contentWorker to be able
+    // to send events during worker initialization.
+    this.port = createPort(this);
+
+    model.documentUnload = documentUnload.bind(this);
+    model.pageShow = pageShow.bind(this);
+    model.pageHide = pageHide.bind(this);
+
+    if ('window' in options)
+      attach(this, options.window);
+  },
+
+  /**
+   * Sends a message to the worker's global scope. Method takes single
+   * argument, which represents data to be sent to the worker. The data may
+   * be any primitive type value or `JSON`. Call of this method asynchronously
+   * emits `message` event with data value in the global scope of this
+   * symbiont.
+   *
+   * `message` event listeners can be set either by calling
+   * `self.on` with a first argument string `"message"` or by
+   * implementing `onMessage` function in the global scope of this worker.
+   * @param {Number|String|JSON} data
+   */
+  postMessage: function (...data) {
+    let model = modelFor(this);
+    let args = ['message'].concat(data);
+    if (!model.inited) {
+      model.earlyEvents.push(args);
+      return;
+    }
+    processMessage.apply(null, [this].concat(args));
+  },
+
+  get url () {
+    let model = modelFor(this);
+    // model.window will be null after detach
+    return model.window ? model.window.document.location.href : null;
+  },
+
+  get contentURL () {
+    let model = modelFor(this);
+    return model.window ? model.window.document.URL : null;
+  },
+
+  get tab () {
+    let model = modelFor(this);
+    // model.window will be null after detach
+    if (model.window)
+      return getTabForWindow(model.window);
+    return null;
+  },
+
+  // Implemented to provide some of the previous features of exposing sandbox
+  // so that Worker can be extended
+  getSandbox: function () {
+    return modelFor(this).contentWorker;
+  },
+
+  toString: function () { return '[object Worker]'; },
+  attach: method(attach),
+  detach: method(detach),
+  destroy: method(destroy)
+});
+exports.Worker = Worker;
+
+attach.define(Worker, function (worker, window) {
+  let model = modelFor(worker);
+  model.window = window;
+  // Track document unload to destroy this worker.
+  // We can't watch for unload event on page's window object as it
+  // prevents bfcache from working:
+  // https://developer.mozilla.org/En/Working_with_BFCache
+  model.windowID = getInnerId(model.window);
+  events.on("inner-window-destroyed", model.documentUnload);
+
+  // will set model.contentWorker pointing to the private API:
+  model.contentWorker = WorkerSandbox(worker, model.window);
+
+  // Listen to pagehide event in order to freeze the content script
+  // while the document is frozen in bfcache:
+  model.window.addEventListener("pageshow", model.pageShow, true);
+  model.window.addEventListener("pagehide", model.pageHide, true);
+
+  // Mainly enable worker.port.emit to send event to the content worker
+  model.inited = true;
+  model.frozen = false;
+
+  // Fire off `attach` event
+  emit(worker, 'attach', window);
+
+  // Process all events and messages that were fired before the
+  // worker was initialized.
+  model.earlyEvents.forEach(args => processMessage.apply(null, [worker].concat(args)));
+});
+
+/**
+ * Remove all internal references to the attached document
+ * Tells _port to unload itself and removes all the references from itself.
+ */
+detach.define(Worker, function (worker, reason) {
+  let model = modelFor(worker);
+
+  // maybe unloaded before content side is created
+  if (model.contentWorker) {
+    model.contentWorker.destroy(reason);
+  }
+
+  model.contentWorker = null;
+  if (model.window) {
+    model.window.removeEventListener("pageshow", model.pageShow, true);
+    model.window.removeEventListener("pagehide", model.pageHide, true);
+  }
+  model.window = null;
+  // This method may be called multiple times,
+  // avoid dispatching `detach` event more than once
+  if (model.windowID) {
+    model.windowID = null;
+    events.off("inner-window-destroyed", model.documentUnload);
+    model.earlyEvents.length = 0;
+    emit(worker, 'detach');
+  }
+  model.inited = false;
+});
+
+isPrivate.define(Worker, ({ tab }) => isPrivate(tab));
+
+/**
+ * Tells content worker to unload itself and
+ * removes all the references from itself.
+ */
+destroy.define(Worker, function (worker, reason) {
+  detach(worker, reason);
+  modelFor(worker).inited = true;
+  // Specifying no type or listener removes all listeners
+  // from target
+  off(worker);
+  off(worker.port);
+});
+
+/**
+ * Events fired by workers
+ */
+function documentUnload ({ subject, data }) {
+  let model = modelFor(this);
+  let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
+  if (innerWinID != model.windowID) return false;
+  detach(this);
+  return true;
+}
+
+function pageShow () {
+  let model = modelFor(this);
+  model.contentWorker.emitSync('pageshow');
+  emit(this, 'pageshow');
+  model.frozen = false;
+}
+
+function pageHide () {
+  let model = modelFor(this);
+  model.contentWorker.emitSync('pagehide');
+  emit(this, 'pagehide');
+  model.frozen = true;
+}
+
+/**
+ * Fired from postMessage and emitEventToContent, or from the earlyMessage
+ * queue when fired before the content is loaded. Sends arguments to
+ * contentWorker if able
+ */
+
+function processMessage (worker, ...args) {
+  let model = modelFor(worker) || {};
+  if (!model.contentWorker)
+    throw new Error(ERR_DESTROYED);
+  if (model.frozen)
+    throw new Error(ERR_FROZEN);
+  model.contentWorker.emit.apply(null, args);
+}
+
+function createModel () {
+  return {
+    // List of messages fired before worker is initialized
+    earlyEvents: [],
+    // Is worker connected to the content worker sandbox ?
+    inited: false,
+    // Is worker being frozen? i.e related document is frozen in bfcache.
+    // Content script should not be reachable if frozen.
+    frozen: true,
+    /**
+     * Reference to the content side of the worker.
+     * @type {WorkerGlobalScope}
+     */
+    contentWorker: null,
+    /**
+     * Reference to the window that is accessible from
+     * the content scripts.
+     * @type {Object}
+     */
+    window: null
+  };
+}
+
+function createPort (worker) {
+  let port = EventTarget();
+  port.emit = emitEventToContent.bind(null, worker);
+  return port;
+}
+
+/**
+ * Emit a custom event to the content script,
+ * i.e. emit this event on `self.port`
+ */
+function emitEventToContent (worker, ...eventArgs) {
+  let model = modelFor(worker);
+  let args = ['event'].concat(eventArgs);
+  if (!model.inited) {
+    model.earlyEvents.push(args);
+    return;
+  }
+  processMessage.apply(null, [worker].concat(args));
+}
--- a/addon-sdk/source/lib/sdk/deprecated/unit-test.js
+++ b/addon-sdk/source/lib/sdk/deprecated/unit-test.js
@@ -5,17 +5,17 @@
 
 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 { getTabs, closeTab, getURI, getTabId, getSelectedTab } = require("../tabs/utils");
 const { windows, isBrowser, getMostRecentBrowserWindow } = require("../window/utils");
 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({
@@ -30,20 +30,26 @@ const findAndRunTests = function findAnd
       stopOnError: options.stopOnError,
       onDone: options.onDone
     });
   });
 };
 exports.findAndRunTests = findAndRunTests;
 
 let runnerWindows = new WeakMap();
+let runnerTabs = new WeakMap();
 
 const TestRunner = function TestRunner(options) {
   options = options || {};
-  runnerWindows.set(this, getInnerId(getMostRecentBrowserWindow()));
+
+  // remember the id's for the open window and tab
+  let window = getMostRecentBrowserWindow();
+  runnerWindows.set(this, getInnerId(window));
+  runnerTabs.set(this, getTabId(getSelectedTab(window)));
+
   this.fs = options.fs;
   this.console = options.console || console;
   memory.track(this);
   this.passed = 0;
   this.failed = 0;
   this.testRunSummary = [];
   this.expectFailNesting = 0;
   this.done = TestRunner.prototype.done.bind(this);
@@ -313,25 +319,36 @@ TestRunner.prototype = {
       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 newTabID = getTabId(getSelectedTab(wins[0]));
+      let oldTabID = runnerTabs.get(this);
       let hasMoreTabsOpen = browserWins.length && tabs.length != 1;
-      if (hasMoreTabsOpen)
+      let failure = false;
+
+      if (wins.length != 1 || getInnerId(wins[0]) !== runnerWindows.get(this)) {
+        failure = true;
+        this.fail("Should not be any unexpected windows open");
+      }
+      else if (hasMoreTabsOpen) {
+        failure = true;
         this.fail("Should not be any unexpected tabs open");
+      }
+      else if (oldTabID != newTabID) {
+        failure = true;
+        runnerTabs.set(this, newTabID);
+        this.fail("Should not be any new tabs left open, old id: " + oldTabID + " new id: " + newTabID);
+      }
 
-      if (hasMoreTabsOpen || wins.length != 1) {
+      if (failure) {
         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);
@@ -351,17 +368,17 @@ TestRunner.prototype = {
       });
 
       if (this.onDone !== null) {
         let onDone = this.onDone;
         this.onDone = null;
         timer.setTimeout(_ => onDone(this));
       }
     }).
-    catch(e => console.exception(e));
+    catch(console.exception);
   },
 
   // 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/io/stream.js
+++ b/addon-sdk/source/lib/sdk/io/stream.js
@@ -187,18 +187,20 @@ const InputStream = Class({
   },
   pause: function pause() {
     this.paused = true;
     nsIInputStreamPump(this).suspend();
     emit(this, "paused");
   },
   resume: function resume() {
     this.paused = false;
-    nsIInputStreamPump(this).resume();
-    emit(this, "resume");
+    if (nsIInputStreamPump(this).isPending()) {
+        nsIInputStreamPump(this).resume();
+        emit(this, "resume");
+    }
   },
   close: function close() {
     this.readable = false;
     nsIInputStreamPump(this).cancel(Cr.NS_OK);
     nsIBinaryInputStream(this).close();
     nsIAsyncInputStream(this).close();
   },
   destroy: function destroy() {
--- a/addon-sdk/source/lib/sdk/keyboard/observer.js
+++ b/addon-sdk/source/lib/sdk/keyboard/observer.js
@@ -3,55 +3,55 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 module.metadata = {
   "stability": "unstable"
 };
 
-const { Trait } = require("../deprecated/light-traits");
-const { EventEmitterTrait: EventEmitter } = require("../deprecated/events");
+const { Class } = require("../core/heritage");
+const { EventTarget } = require("../event/target");
+const { emit } = require("../event/core");
 const { DOMEventAssembler } = require("../deprecated/events/assembler");
 const { browserWindowIterator } = require('../deprecated/window-utils');
 const { isBrowser } = require('../window/utils');
 const { observer: windowObserver } = require("../windows/observer");
 
 // Event emitter objects used to register listeners and emit events on them
 // when they occur.
-const observer = Trait.compose(DOMEventAssembler, EventEmitter).create({
-  /**
-   * Method is implemented by `EventEmitter` and is used just for emitting
-   * events on registered listeners.
-   */
-  _emit: Trait.required,
+const Observer = Class({
+  implements: [DOMEventAssembler, EventTarget],
+  initialize() {
+    // Adding each opened window to a list of observed windows.
+    windowObserver.on("open", window => {
+      if (isBrowser(window))
+        this.observe(window);
+    });
+
+    // Removing each closed window form the list of observed windows.
+    windowObserver.on("close", window => {
+      if (isBrowser(window))
+        this.ignore(window);
+    });
+
+    // Making observer aware of already opened windows.
+    for (let window of browserWindowIterator()) {
+      this.observe(window);
+    }
+  },
   /**
    * Events that are supported and emitted by the module.
    */
   supportedEventsTypes: [ "keydown", "keyup", "keypress" ],
   /**
    * Function handles all the supported events on all the windows that are
    * observed. Method is used to proxy events to the listeners registered on
    * this event emitter.
    * @param {Event} event
    *    Keyboard event being emitted.
    */
-  handleEvent: function handleEvent(event) {
-    this._emit(event.type, event, event.target.ownerDocument.defaultView);
+  handleEvent(event) {
+    emit(this, event.type, event, event.target.ownerDocument.defaultView);
   }
 });
 
-// Adding each opened window to a list of observed windows.
-windowObserver.on("open", function onOpen(window) {
-  if (isBrowser(window))
-    observer.observe(window);
-});
-// Removing each closed window form the list of observed windows.
-windowObserver.on("close", function onClose(window) {
-  if (isBrowser(window))
-    observer.ignore(window);
-});
-
-// Making observer aware of already opened windows.
-for (let window of browserWindowIterator())
-  observer.observe(window);
-
-exports.observer = observer;
+exports.observer = new Observer();
--- a/addon-sdk/source/lib/sdk/l10n/locale.js
+++ b/addon-sdk/source/lib/sdk/l10n/locale.js
@@ -45,17 +45,18 @@ function getPreferedLocales(caseSensitve
   // `general.useragent.locale` is a special 'localized' value, like:
   // "chrome://global/locale/intl.properties"
   let browserUiLocale = prefs.getLocalized(PREF_SELECTED_LOCALE, "") ||
                         prefs.get(PREF_SELECTED_LOCALE, "");
   if (browserUiLocale)
     addLocale(browserUiLocale);
 
   // Third priority is the list of locales used for web content
-  let contentLocales = prefs.get(PREF_ACCEPT_LANGUAGES, "");
+  let contentLocales = prefs.getLocalized(PREF_ACCEPT_LANGUAGES, "") ||
+                       prefs.get(PREF_ACCEPT_LANGUAGES, "");
   if (contentLocales) {
     // This list is a string of locales seperated by commas.
     // There is spaces after commas, so strip each item
     for (let locale of contentLocales.split(","))
       addLocale(locale.replace(/(^\s+)|(\s+$)/g, ""));
   }
 
   // Finally, we ensure that en-US is the final fallback if it wasn't added
--- a/addon-sdk/source/lib/sdk/lang/type.js
+++ b/addon-sdk/source/lib/sdk/lang/type.js
@@ -103,16 +103,28 @@ exports.isFunction = isFunction;
  *    isObject(null) // false
  */
 function isObject(value) {
     return typeof value === "object" && value !== null;
 }
 exports.isObject = isObject;
 
 /**
+ * Detect whether a value is a generator.
+ *
+ * @param aValue
+ *        The value to identify.
+ * @return A boolean indicating whether the value is a generator.
+ */
+function isGenerator(aValue) {
+  return !!(aValue && aValue.isGenerator && aValue.isGenerator());
+}
+exports.isGenerator = isGenerator;
+
+/**
  * Returns true if `value` is an Array.
  * @examples
  *    isArray([1, 2, 3])  // true
  *    isArray({ 0: 'foo', length: 1 }) // false
  */
 var isArray = Array.isArray || function isArray(value) {
   Object.prototype.toString.call(value) === "[object Array]";
 }
--- a/addon-sdk/source/lib/sdk/loader/cuddlefish.js
+++ b/addon-sdk/source/lib/sdk/loader/cuddlefish.js
@@ -1,102 +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';
+"use strict";
 
 module.metadata = {
   "stability": "unstable"
 };
 
 // This module is manually loaded by bootstrap.js in a sandbox and immediatly
 // put in module cache so that it is never loaded in any other way.
 
 /* Workarounds to include dependencies in the manifest
 require('chrome')                  // Otherwise CFX will complain about Components
 require('toolkit/loader')          // Otherwise CFX will stip out loader.js
 require('sdk/addon/runner')        // Otherwise CFX will stip out addon/runner.js
-require('sdk/system/xul-app')      // Otherwise CFX will stip out sdk/system/xul-app
 */
 
 const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
 
 // `loadSandbox` is exposed by bootstrap.js
 const loaderURI = module.uri.replace("sdk/loader/cuddlefish.js",
                                      "toolkit/loader.js");
 const xulappURI = module.uri.replace("loader/cuddlefish.js",
-                                     "system/xul-app.js");
+                                     "system/xul-app.jsm");
 // We need to keep a reference to the sandbox in order to unload it in
 // bootstrap.js
 
 const loaderSandbox = loadSandbox(loaderURI);
 const loaderModule = loaderSandbox.exports;
 
-const xulappSandbox = loadSandbox(xulappURI);
-const xulappModule = xulappSandbox.exports;
+const { incompatibility } = Cu.import(xulappURI, {}).XulApp;
 
 const { override, load } = loaderModule;
 
-/**
- * Ensure the current application satisfied the requirements specified in the
- * module given. If not, an exception related to the incompatibility is
- * returned; `null` otherwise.
- *
- * @param {Object} module
- *  The module to check
- * @returns {Error}
- */
-function incompatibility(module) {
-  let { metadata, id } = module;
-
-  // if metadata or engines are not specified we assume compatibility is not
-  // an issue.
-  if (!metadata || !("engines" in metadata))
-    return null;
-
-  let { engines } = metadata;
-
-  if (engines === null || typeof(engines) !== "object")
-    return new Error("Malformed engines' property in metadata");
-
-  let applications = Object.keys(engines);
-
-  let versionRange;
-  applications.forEach(function(name) {
-    if (xulappModule.is(name)) {
-      versionRange = engines[name];
-      // Continue iteration. We want to ensure the module doesn't
-      // contain a typo in the applications' name or some unknown
-      // application - `is` function throws an exception in that case.
-    }
-  });
-
-  if (typeof(versionRange) === "string") {
-    if (xulappModule.satisfiesVersion(versionRange))
-      return null;
-
-    return new Error("Unsupported Application version: The module " + id +
-            " currently supports only version " + versionRange + " of " +
-            xulappModule.name + ".");
-  }
-
-  return new Error("Unsupported Application: The module " + id +
-            " currently supports only " + applications.join(", ") + ".")
-}
-
 function CuddlefishLoader(options) {
   let { manifest } = options;
 
   options = override(options, {
     // Put `api-utils/loader` and `api-utils/cuddlefish` loaded as JSM to module
     // cache to avoid subsequent loads via `require`.
     modules: override({
       'toolkit/loader': loaderModule,
-      'sdk/loader/cuddlefish': exports,
-      'sdk/system/xul-app': xulappModule
+      'sdk/loader/cuddlefish': exports
     }, options.modules),
     resolve: function resolve(id, requirer) {
       let entry = requirer && requirer in manifest && manifest[requirer];
       let uri = null;
 
       // If manifest entry for this requirement is present we follow manifest.
       // Note: Standard library modules like 'panel' will be present in
       // manifest unless they were moved to platform.
--- a/addon-sdk/source/lib/sdk/page-mod.js
+++ b/addon-sdk/source/lib/sdk/page-mod.js
@@ -9,17 +9,17 @@ module.metadata = {
 
 const observers = require('./system/events');
 const { contract: loaderContract } = require('./content/loader');
 const { contract } = require('./util/contract');
 const { getAttachEventType, WorkerHost } = require('./content/utils');
 const { Class } = require('./core/heritage');
 const { Disposable } = require('./core/disposable');
 const { WeakReference } = require('./core/reference');
-const { Worker } = require('./content/worker-parent');
+const { Worker } = require('./content/worker');
 const { EventTarget } = require('./event/target');
 const { on, emit, once, setListeners } = require('./event/core');
 const { on: domOn, removeListener: domOff } = require('./dom/events');
 const { isRegExp, isUndefined } = require('./lang/type');
 const { merge } = require('./util/object');
 const { windowIterator } = require('./deprecated/window-utils');
 const { isBrowser, getFrames } = require('./window/utils');
 const { getTabs, getTabContentWindow, getTabForContentWindow,
@@ -184,16 +184,20 @@ function onContentWindow({ subject: docu
       onContent(pagemod, window);
   }
 }
 
 function applyOnExistingDocuments (mod) {
   getTabs().forEach(tab => {
     // Fake a newly created document
     let window = getTabContentWindow(tab);
+    // on startup with e10s, contentWindow might not exist yet,
+    // in which case we will get notified by "document-element-inserted".
+    if (!window || !window.frames)
+      return;
     let uri = getTabURI(tab);
     if (has(mod.attachTo, "top") && modMatchesURI(mod, uri))
       onContent(mod, window);
     if (has(mod.attachTo, "frame"))
       getFrames(window).
         filter(iframe => modMatchesURI(mod, iframe.location.href)).
         forEach(frame => onContent(mod, frame));
   });
@@ -211,17 +215,17 @@ function createWorker (mod, window) {
     onError: (e) => emit(mod, 'error', e)
   });
   workers.set(mod, worker);
   worker.on('*', (event, ...args) => {
     // worker's "attach" event passes a window as the argument
     // page-mod's "attach" event needs a worker
     if (event === 'attach')
       emit(mod, event, worker)
-    else 
+    else
       emit(mod, event, ...args);
   })
   once(worker, 'detach', () => worker.destroy());
 }
 
 function onContent (mod, window) {
   // not registered yet
   if (!pagemods.has(mod))
--- a/addon-sdk/source/lib/sdk/page-worker.js
+++ b/addon-sdk/source/lib/sdk/page-worker.js
@@ -23,19 +23,19 @@ const { getParentWindow } = require('./w
 const { create: makeFrame, getDocShell } = require('./frame/utils');
 const { contract } = require('./util/contract');
 const { contract: loaderContract } = require('./content/loader');
 const { has } = require('./util/array');
 const { Rules } = require('./util/rules');
 const { merge } = require('./util/object');
 const { data } = require('./self');
 
-const views = WeakMap();
-const workers = WeakMap();
-const pages = WeakMap();
+const views = new WeakMap();
+const workers = new WeakMap();
+const pages = new WeakMap();
 
 const readyEventNames = [
   'DOMContentLoaded',
   'document-element-inserted',
   'load'
 ];
 
 function workerFor(page) workers.get(page)
@@ -131,17 +131,17 @@ const Page = Class({
   get contentURL() { return viewFor(this).getAttribute("data-src") },
   set contentURL(value) {
     if (!isValidURL(this, value)) return;
     let view = viewFor(this);
     let contentURL = pageContract({ contentURL: value }).contentURL;
 
     // page-worker doesn't have a model like other APIs, so to be consitent
     // with the behavior "what you set is what you get", we need to store
-    // the original `contentURL` given. 
+    // the original `contentURL` given.
     // Even if XUL elements doesn't support `dataset`, properties, to
     // indicate that is a custom attribute the syntax "data-*" is used.
     view.setAttribute('data-src', contentURL);
     view.setAttribute('src', data.url(contentURL));
   },
   dispose: function () {
     if (isDisposed(this)) return;
     let view = viewFor(this);
--- a/addon-sdk/source/lib/sdk/panel.js
+++ b/addon-sdk/source/lib/sdk/panel.js
@@ -10,22 +10,20 @@ module.metadata = {
   "engines": {
     "Firefox": "*",
     "SeaMonkey": "*"
   }
 };
 
 const { Ci } = require("chrome");
 const { setTimeout } = require('./timers');
-const { isPrivateBrowsingSupported } = require('./self');
-const { isWindowPBSupported } = require('./private-browsing/utils');
 const { Class } = require("./core/heritage");
 const { merge } = require("./util/object");
 const { WorkerHost } = require("./content/utils");
-const { Worker } = require("./content/worker");
+const { Worker } = require("./deprecated/sync-worker");
 const { Disposable } = require("./core/disposable");
 const { WeakReference } = require('./core/reference');
 const { contract: loaderContract } = require("./content/loader");
 const { contract } = require("./util/contract");
 const { on, off, emit, setListeners } = require("./event/core");
 const { EventTarget } = require("./event/target");
 const domPanel = require("./panel/utils");
 const { events } = require("./panel/events");
@@ -150,17 +148,17 @@ const Panel = Class({
 
     // Setup view
     let view = domPanel.make();
     panels.set(view, this);
     views.set(this, view);
 
     // Load panel content.
     domPanel.setURL(view, model.contentURL);
-    
+
     // Allow context menu
     domPanel.allowContextMenu(view, model.contextMenu);
 
     setupAutoHide(this);
 
     // Setup listeners.
     setListeners(this, options);
     let worker = new Worker(stripListeners(options));
@@ -190,25 +188,25 @@ const Panel = Class({
   get height() modelFor(this).height,
   set height(value) this.resize(this.width, value),
 
   /* Public API: Panel.focus */
   get focus() modelFor(this).focus,
 
   /* Public API: Panel.position */
   get position() modelFor(this).position,
-  
+
   /* Public API: Panel.contextMenu */
   get contextMenu() modelFor(this).contextMenu,
   set contextMenu(allow) {
     let model = modelFor(this);
     model.contextMenu = panelContract({ contextMenu: allow }).contextMenu;
     domPanel.allowContextMenu(viewFor(this), model.contextMenu);
   },
-    
+
   get contentURL() modelFor(this).contentURL,
   set contentURL(value) {
     let model = modelFor(this);
     model.contentURL = panelContract({ contentURL: value }).contentURL;
     domPanel.setURL(viewFor(this), model.contentURL);
     // Detach worker so that messages send will be queued until it's
     // reatached once panel content is ready.
     workerFor(this).detach();
--- a/addon-sdk/source/lib/sdk/panel/utils.js
+++ b/addon-sdk/source/lib/sdk/panel/utils.js
@@ -208,17 +208,17 @@ function shimDefaultStyle(panel) {
       if (node) node.style.padding = 0;
   });
 }
 
 function show(panel, options, anchor) {
   // Prevent the panel from getting focus when showing up
   // if focus is set to false
   panel.setAttribute("noautofocus", !options.focus);
-  
+
   let window = anchor && getOwnerBrowserWindow(anchor);
   let { document } = window ? window : getMostRecentBrowserWindow();
   attach(panel, document);
 
   open(panel, options, anchor);
 }
 exports.show = show
 
@@ -281,18 +281,17 @@ function make(document) {
     }
   }
 
   function onContentLoad({target, type}) {
     if (target === getContentDocument(panel))
       events.emit(type, { subject: panel });
   }
 
-  function onContentChange({subject, type}) {
-    let document = subject;
+  function onContentChange({subject: document, type}) {
     if (document === getContentDocument(panel) && document.defaultView)
       events.emit(type, { subject: panel });
   }
 
   function onPanelStateChange({type}) {
     events.emit(type, { subject: panel })
   }
 
@@ -406,16 +405,16 @@ exports.getContentDocument = getContentD
 
 function setURL(panel, url) {
   getContentFrame(panel).setAttribute("src", url ? data.url(url) : url);
 }
 
 exports.setURL = setURL;
 
 function allowContextMenu(panel, allow) {
-  if(allow) {
+  if (allow) {
     panel.setAttribute("context", "contentAreaContextMenu");
-  } 
+  }
   else {
     panel.removeAttribute("context");
   }
 }
 exports.allowContextMenu = allowContextMenu;
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/panel/window.js
+++ /dev/null
@@ -1,64 +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';
-
-// The panel module currently supports only Firefox.
-// See: https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps
-module.metadata = {
-  'stability': 'unstable',
-  'engines': {
-    'Firefox': '*'
-  }
-};
-
-const { getMostRecentBrowserWindow, windows: getWindows } = require('../window/utils');
-const { ignoreWindow } = require('../private-browsing/utils');
-const { isPrivateBrowsingSupported } = require('../self');
-
-function getWindow(anchor) {
-  let window;
-  let windows = getWindows("navigator:browser", {
-    includePrivate: isPrivateBrowsingSupported
-  });
-
-  if (anchor) {
-    let anchorWindow = anchor.ownerDocument.defaultView.top;
-    let anchorDocument = anchorWindow.document;
-
-    // loop thru supported windows
-    for (let enumWindow of windows) {
-      // Check if the anchor is in this browser window.
-      if (enumWindow == anchorWindow) {
-        window = anchorWindow;
-        break;
-      }
-
-      // Check if the anchor is in a browser tab in this browser window.
-      try {
-        let browser = enumWindow.gBrowser.getBrowserForDocument(anchorDocument);
-        if (browser) {
-          window = enumWindow;
-          break;
-        }
-      }
-      catch (e) {
-      }
-
-      // Look in other subdocuments (sidebar, etc.)?
-    }
-  }
-
-  // If we didn't find the anchor's window (or we have no anchor),
-  // return the most recent browser window.
-  if (!window)
-    window = getMostRecentBrowserWindow();
-
-  // if the window is not supported, then it should be ignored
-  if (ignoreWindow(window)) {
-  	return null;
-  }
-
-  return window;
-}
-exports.getWindow = getWindow;
--- a/addon-sdk/source/lib/sdk/places/bookmarks.js
+++ b/addon-sdk/source/lib/sdk/places/bookmarks.js
@@ -2,17 +2,18 @@
  * 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",
   "engines": {
-    "Firefox": "*"
+    "Firefox": "*",
+    "SeaMonkey": "*"
   }
 };
 
 /*
  * Requiring hosts so they can subscribe to client messages
  */
 require('./host/host-bookmarks');
 require('./host/host-tags');
--- a/addon-sdk/source/lib/sdk/places/events.js
+++ b/addon-sdk/source/lib/sdk/places/events.js
@@ -2,17 +2,18 @@
  * 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',
   'engines': {
-    'Firefox': '*'
+    'Firefox': '*',
+    "SeaMonkey": '*'
   }
 };
 
 const { Cc, Ci } = require('chrome');
 const { Unknown } = require('../platform/xpcom');
 const { Class } = require('../core/heritage');
 const { merge } = require('../util/object');
 const bookmarkService = Cc['@mozilla.org/browser/nav-bookmarks-service;1']
--- a/addon-sdk/source/lib/sdk/places/favicon.js
+++ b/addon-sdk/source/lib/sdk/places/favicon.js
@@ -2,17 +2,18 @@
  * 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",
   "engines": {
-    "Firefox": "*"
+    "Firefox": "*",
+    "SeaMonkey": "*"
   }
 };
 
 const { Cc, Ci, Cu } = require("chrome");
 const { defer, reject } = require("../core/promise");
 const FaviconService = Cc["@mozilla.org/browser/favicon-service;1"].
                           getService(Ci.nsIFaviconService);
 const AsyncFavicons = FaviconService.QueryInterface(Ci.mozIAsyncFavicons);
--- a/addon-sdk/source/lib/sdk/places/history.js
+++ b/addon-sdk/source/lib/sdk/places/history.js
@@ -2,17 +2,18 @@
  * 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",
   "engines": {
-    "Firefox": "*"
+    "Firefox": "*",
+    "SeaMonkey": "*"
   }
 };
 
 /*
  * Requiring hosts so they can subscribe to client messages
  */
 require('./host/host-bookmarks');
 require('./host/host-tags');
--- a/addon-sdk/source/lib/sdk/places/host/host-bookmarks.js
+++ b/addon-sdk/source/lib/sdk/places/host/host-bookmarks.js
@@ -2,17 +2,18 @@
  * 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",
   "engines": {
-    "Firefox": "*"
+    "Firefox": "*",
+    "SeaMonkey": "*"
   }
 };
 
 const { Cc, Ci } = require('chrome');
 const browserHistory = Cc["@mozilla.org/browser/nav-history-service;1"].
                        getService(Ci.nsIBrowserHistory);
 const asyncHistory = Cc["@mozilla.org/browser/history;1"].
                      getService(Ci.mozIAsyncHistory);
--- a/addon-sdk/source/lib/sdk/places/host/host-query.js
+++ b/addon-sdk/source/lib/sdk/places/host/host-query.js
@@ -2,17 +2,18 @@
  * 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",
   "engines": {
-    "Firefox": "*"
+    "Firefox": "*",
+    "SeaMonkey": "*"
   }
 };
 
 const { Cc, Ci } = require('chrome');
 const { defer, all, resolve } = require('../../core/promise');
 const { safeMerge, omit } = require('../../util/object');
 const historyService = Cc['@mozilla.org/browser/nav-history-service;1']
                      .getService(Ci.nsINavHistoryService);
--- a/addon-sdk/source/lib/sdk/places/host/host-tags.js
+++ b/addon-sdk/source/lib/sdk/places/host/host-tags.js
@@ -2,17 +2,18 @@
  * 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",
   "engines": {
-    "Firefox": "*"
+    "Firefox": "*",
+    "SeaMonkey": "*"
   }
 };
 
 const { Cc, Ci } = require('chrome');
 const taggingService = Cc["@mozilla.org/browser/tagging-service;1"].
                        getService(Ci.nsITaggingService);
 const ios = Cc['@mozilla.org/network/io-service;1'].
             getService(Ci.nsIIOService);
--- a/addon-sdk/source/lib/sdk/places/utils.js
+++ b/addon-sdk/source/lib/sdk/places/utils.js
@@ -2,17 +2,18 @@
  * 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",
   "engines": {
-    "Firefox": "*"
+    "Firefox": "*",
+    "SeaMonkey": "*"
   }
 };
 
 const { Cc, Ci } = require('chrome');
 const { Class } = require('../core/heritage');
 const { method } = require('../lang/functional');
 const { defer, promised, all } = require('../core/promise');
 const { send } = require('../addon/events');
--- a/addon-sdk/source/lib/sdk/preferences/native-options.js
+++ b/addon-sdk/source/lib/sdk/preferences/native-options.js
@@ -118,17 +118,18 @@ function injectOptions({ preferences, pr
     }
 
     let setting = document.createElement('setting');
     setting.setAttribute('pref-name', name);
     setting.setAttribute('data-jetpack-id', id);
     setting.setAttribute('pref', 'extensions.' + preferencesBranch + '.' + name);
     setting.setAttribute('type', type);
     setting.setAttribute('title', title);
-    setting.setAttribute('desc', description);
+    if (description)
+      setting.setAttribute('desc', description);
 
     if (type === 'file' || type === 'directory') {
       setting.setAttribute('fullpath', 'true');
     }
     else if (type === 'control') {
       let button = document.createElement('button');
       button.setAttribute('pref-name', name);
       button.setAttribute('data-jetpack-id', id);
--- a/addon-sdk/source/lib/sdk/preferences/service.js
+++ b/addon-sdk/source/lib/sdk/preferences/service.js
@@ -16,16 +16,19 @@ const MIN_INT = -0x80000000;
 
 const {Cc,Ci,Cr} = require("chrome");
 
 const prefService = Cc["@mozilla.org/preferences-service;1"].
                 getService(Ci.nsIPrefService);
 const prefSvc = prefService.getBranch(null);
 const defaultBranch = prefService.getDefaultBranch(null);
 
+const { Preferences } = require("resource://gre/modules/Preferences.jsm");
+const prefs = new Preferences({});
+
 function Branch(branchName) {
   function getPrefKeys() {
     return keys(branchName).map(function(key) {
       return key.replace(branchName, "");
     });
   }
 
   return Proxy.create({
@@ -37,105 +40,59 @@ function Branch(branchName) {
     },
     delete: function(pref) {
       reset(branchName + pref);
       return true;
     },
     has: function hasPrefKey(pref) {
       return has(branchName + pref)
     },
+    hasOwn: function(pref) {
+      return has(branchName + pref)
+    },
     getPropertyDescriptor: function(name) {
       return {
         value: get(branchName + name)
       };
     },
     enumerate: getPrefKeys,
     keys: getPrefKeys
   }, Branch.prototype);
 }
 
 function get(name, defaultValue) {
-  switch (prefSvc.getPrefType(name)) {
-  case Ci.nsIPrefBranch.PREF_STRING:
-    return prefSvc.getComplexValue(name, Ci.nsISupportsString).data;
-
-  case Ci.nsIPrefBranch.PREF_INT:
-    return prefSvc.getIntPref(name);
-
-  case Ci.nsIPrefBranch.PREF_BOOL:
-    return prefSvc.getBoolPref(name);
-
-  case Ci.nsIPrefBranch.PREF_INVALID:
-    return defaultValue;
-
-  default:
-    // This should never happen.
-    throw new Error("Error getting pref " + name +
-                    "; its value's type is " +
-                    prefSvc.getPrefType(name) +
-                    ", which I don't know " +
-                    "how to handle.");
-  }
+  return prefs.get(name, defaultValue);
 }
 exports.get = get;
 
+
 function set(name, value) {
   var prefType;
   if (typeof value != "undefined" && value != null)
     prefType = value.constructor.name;
 
   switch (prefType) {
-  case "String":
-    {
-      var string = Cc["@mozilla.org/supports-string;1"].
-                   createInstance(Ci.nsISupportsString);
-      string.data = value;
-      prefSvc.setComplexValue(name, Ci.nsISupportsString, string);
-    }
-    break;
-
   case "Number":
-    // We throw if the number is outside the range or not an integer, since
-    // the result will not be what the consumer wanted to store.
-    if (value > MAX_INT || value < MIN_INT)
-      throw new Error("you cannot set the " + name +
-                      " pref to the number " + value +
-                      ", as number pref values must be in the signed " +
-                      "32-bit integer range -(2^31) to 2^31-1.  " +
-                      "To store numbers outside that range, store " +
-                      "them as strings.");
     if (value % 1 != 0)
       throw new Error("cannot store non-integer number: " + value);
-    prefSvc.setIntPref(name, value);
-    break;
+  }
 
-  case "Boolean":
-    prefSvc.setBoolPref(name, value);
-    break;
-
-  default:
-    throw new Error("can't set pref " + name + " to value '" + value +
-                    "'; it isn't a string, integer, or boolean");
-  }
+  prefs.set(name, value);
 }
 exports.set = set;
 
-function has(name) {
-  return (prefSvc.getPrefType(name) != Ci.nsIPrefBranch.PREF_INVALID);
-}
+const has = prefs.has.bind(prefs)
 exports.has = has;
 
 function keys(root) {
   return prefSvc.getChildList(root);
 }
 exports.keys = keys;
 
-function isSet(name) {
-  return (has(name) && prefSvc.prefHasUserValue(name));
-}
+const isSet = prefs.isSet.bind(prefs);
 exports.isSet = isSet;
 
 function reset(name) {
   try {
     prefSvc.clearUserPref(name);
   }
   catch (e) {
     // The pref service throws NS_ERROR_UNEXPECTED when the caller tries
--- a/addon-sdk/source/lib/sdk/preferences/utils.js
+++ b/addon-sdk/source/lib/sdk/preferences/utils.js
@@ -3,48 +3,40 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 module.metadata = {
   "stability": "unstable"
 };
 
 const { openTab, getBrowserForTab, getTabId } = require("sdk/tabs/utils");
-const { defer, all } = require("sdk/core/promise");
 const { on, off } = require("sdk/system/events");
-const { setTimeout } = require("sdk/timers");
 const { getMostRecentBrowserWindow } = require('../window/utils');
 
-const open = function open({ id }) {
-  let showing = defer();
-  let loaded = defer();
-  let result = { id: id };
-  let tab = openTab(getMostRecentBrowserWindow(), "about:addons", {
-    inBackground: true
-  });
+// Opens about:addons in a new tab, then displays the inline
+// preferences of the provided add-on
+const open = ({ id }) => new Promise((resolve, reject) => {
+  // opening the about:addons page in a new tab
+  let tab = openTab(getMostRecentBrowserWindow(), "about:addons");
   let browser = getBrowserForTab(tab);
 
+  // waiting for the about:addons page to load
   browser.addEventListener("load", function onPageLoad() {
     browser.removeEventListener("load", onPageLoad, true);
     let window = browser.contentWindow;
 
     // wait for the add-on's "addon-options-displayed"
     on("addon-options-displayed", function onPrefDisplayed({ subject: doc, data }) {
       if (data === id) {
         off("addon-options-displayed", onPrefDisplayed);
-        result.tabId = getTabId(tab);
-        result.document = doc;
-        loaded.resolve();
+        resolve({
+          id: id,
+          tabId: getTabId(tab),
+          "document": doc
+        });
       }
     }, true);
 
     // display the add-on inline preferences page
     window.gViewController.commands.cmd_showItemDetails.doCommand({ id: id }, true);
-    let { node } = window.gViewController.viewObjects.detail;
-    node.addEventListener("ViewChanged", function whenViewChanges() {
-      node.removeEventListener("ViewChanged", whenViewChanges, false);
-      showing.resolve();
-    }, false);
   }, true);
-
-  return all([ showing.promise, loaded.promise ]).then(_ => result);
-}
+});
 exports.open = open;
--- a/addon-sdk/source/lib/sdk/self.js
+++ b/addon-sdk/source/lib/sdk/self.js
@@ -16,26 +16,29 @@ const { readURISync } = require('./net/u
 const id = options.id;
 
 const readPref = key => get("extensions." + id + ".sdk." + key);
 
 const name = readPref("name") || options.name;
 const version = readPref("version") || options.version;
 const loadReason = readPref("load.reason") || options.loadReason;
 const rootURI = readPref("rootURI") || options.rootURI || "";
-const baseURI = readPref("baseURI") || options.prefixURI + name + "/";
+const baseURI = readPref("baseURI") || options.prefixURI + name + "/"
 const addonDataURI = baseURI + "data/";
 const metadata = options.metadata || {};
 const permissions = metadata.permissions || {};
 const isPacked = rootURI && rootURI.indexOf("jar:") === 0;
 
 const uri = (path="") =>
   path.contains(":") ? path : addonDataURI + path.replace(/^\.\//, "");
 
-let { preferencesBranch } = options;
+let preferencesBranch = ("preferences-branch" in metadata)
+                            ? metadata["preferences-branch"]
+                            : options.preferencesBranch
+
 if (/[^\w{@}.-]/.test(preferencesBranch)) {
   preferencesBranch = id;
   console.warn("Ignoring preferences-branch (not a valid branch name)");
 }
 
 // Some XPCOM APIs require valid URIs as an argument for certain operations
 // (see `nsILoginManager` for example). This property represents add-on
 // associated unique URI string that can be used for that.
--- a/addon-sdk/source/lib/sdk/system/child_process.js
+++ b/addon-sdk/source/lib/sdk/system/child_process.js
@@ -15,17 +15,17 @@ let { on, emit, off } = require('../even
 let { Class } = require('../core/heritage');
 let { platform } = require('../system');
 let { isFunction, isArray } = require('../lang/type');
 let { delay } = require('../lang/functional');
 let { merge } = require('../util/object');
 let { setTimeout, clearTimeout } = require('../timers');
 let isWindows = platform.indexOf('win') === 0;
 
-let processes = WeakMap();
+let processes = new WeakMap();
 
 
 /**
  * The `Child` class wraps a subprocess command, exposes
  * the stdio streams, and methods to manipulate the subprocess
  */
 let Child = Class({
   implements: [EventTarget],
--- a/addon-sdk/source/lib/sdk/system/xul-app.js
+++ b/addon-sdk/source/lib/sdk/system/xul-app.js
@@ -2,12 +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": "experimental"
 };
 
-var { Cu } = require("chrome");
-var { XulApp } = Cu.import("resource://gre/modules/sdk/system/XulApp.js", {});
+const { XulApp } = require("./xul-app.jsm");
 
 Object.keys(XulApp).forEach(k => exports[k] = XulApp[k]);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/system/xul-app.jsm
@@ -0,0 +1,241 @@
+/* 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 EXPORTED_SYMBOLS = ["XulApp"];
+
+var { classes: Cc, interfaces: Ci } = Components;
+
+var exports = {};
+var XulApp = exports;
+
+var appInfo;
+
+// NOTE: below is required to avoid failing xpcshell tests,
+//       which do not implement nsIXULAppInfo
+// See Bug 1114752 https://bugzilla.mozilla.org/show_bug.cgi?id=1114752
+try {
+ appInfo = Cc["@mozilla.org/xre/app-info;1"]
+              .getService(Ci.nsIXULAppInfo);
+}
+catch (e) {
+  // xpcshell test case
+  appInfo = {};
+}
+var vc = Cc["@mozilla.org/xpcom/version-comparator;1"]
+         .getService(Ci.nsIVersionComparator);
+
+var ID = exports.ID = appInfo.ID;
+var name = exports.name = appInfo.name;
+var version = exports.version = appInfo.version;
+var platformVersion = exports.platformVersion = appInfo.platformVersion;
+
+// The following mapping of application names to GUIDs was taken from:
+//
+//   https://addons.mozilla.org/en-US/firefox/pages/appversions
+//
+// Using the GUID instead of the app's name is preferable because sometimes
+// re-branded versions of a product have different names: for instance,
+// Firefox, Minefield, Iceweasel, and Shiretoko all have the same
+// GUID.
+
+var ids = exports.ids = {
+  Firefox: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
+  Mozilla: "{86c18b42-e466-45a9-ae7a-9b95ba6f5640}",
+  SeaMonkey: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
+  Fennec: "{aa3c5121-dab2-40e2-81ca-7ea25febc110}",
+  Thunderbird: "{3550f703-e582-4d05-9a08-453d09bdfdc6}"
+};
+
+function is(name) {
+  if (!(name in ids))
+    throw new Error("Unkown Mozilla Application: " + name);
+  return ID == ids[name];
+};
+exports.is = is;
+
+function isOneOf(names) {
+  for (var i = 0; i < names.length; i++)
+    if (is(names[i]))
+      return true;
+  return false;
+};
+exports.isOneOf = isOneOf;
+
+/**
+ * Use this to check whether the given version (e.g. xulApp.platformVersion)
+ * is in the given range. Versions must be in version comparator-compatible
+ * format. See MDC for details:
+ * https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIVersionComparator
+ */
+var versionInRange = exports.versionInRange =
+function versionInRange(version, lowInclusive, highExclusive) {
+  return (vc.compare(version, lowInclusive) >= 0) &&
+         (vc.compare(version, highExclusive) < 0);
+}
+
+const reVersionRange = /^((?:<|>)?=?)?\s*((?:\d+[\S]*)|\*)(?:\s+((?:<|>)=?)?(\d+[\S]+))?$/;
+const reOnlyInifinity = /^[<>]?=?\s*[*x]$/;
+const reSubInfinity = /\.[*x]/g;
+const reHyphenRange = /^(\d+.*?)\s*-\s*(\d+.*?)$/;
+const reRangeSeparator = /\s*\|\|\s*/;
+
+const compares = {
+  "=": function (c) { return c === 0 },
+  ">=": function (c) { return c >= 0 },
+  "<=": function (c) { return c <= 0},
+  "<": function (c) { return c < 0 },
+  ">": function (c) { return c > 0 }
+}
+
+function normalizeRange(range) {
+    return range
+        .replace(reOnlyInifinity, "")
+        .replace(reSubInfinity, ".*")
+        .replace(reHyphenRange, ">=$1 <=$2")
+}
+
+/**
+ * Compare the versions given, using the comparison operator provided.
+ * Internal use only.
+ *
+ * @example
+ *  compareVersion("1.2", "<=", "1.*") // true
+ *
+ * @param {String} version
+ *  A version to compare
+ *
+ * @param {String} comparison
+ *  The comparison operator
+ *
+ * @param {String} compareVersion
+ *  A version to compare
+ */
+function compareVersion(version, comparison, compareVersion) {
+  let hasWildcard = compareVersion.indexOf("*") !== -1;
+
+  comparison = comparison || "=";
+
+  if (hasWildcard) {
+    switch (comparison) {
+      case "=":
+        let zeroVersion = compareVersion.replace(reSubInfinity, ".0");
+        return versionInRange(version, zeroVersion, compareVersion);
+      case ">=":
+        compareVersion = compareVersion.replace(reSubInfinity, ".0");
+        break;
+    }
+  }
+
+  let compare = compares[comparison];
+
+  return typeof compare === "function" && compare(vc.compare(version, compareVersion));
+}
+
+/**
+ * Returns `true` if `version` satisfies the `versionRange` given.
+ * If only an argument is passed, is used as `versionRange` and compared against
+ * `xulApp.platformVersion`.
+ *
+ * `versionRange` is either a string which has one or more space-separated
+ * descriptors, or a range like "fromVersion - toVersion".
+ * Version range descriptors may be any of the following styles:
+ *
+ * - "version" Must match `version` exactly
+ * - "=version" Same as just `version`
+ * - ">version" Must be greater than `version`
+ * - ">=version" Must be greater or equal than `version`
+ * - "<version" Must be less than `version`
+ * - "<=version" Must be less or equal than `version`
+ * - "1.2.x" or "1.2.*" See 'X version ranges' below
+ * - "*" or "" (just an empty string) Matches any version
+ * - "version1 - version2" Same as ">=version1 <=version2"
+ * - "range1 || range2" Passes if either `range1` or `range2` are satisfied
+ *
+ * For example, these are all valid:
+ * - "1.0.0 - 2.9999.9999"
+ * - ">=1.0.2 <2.1.2"
+ * - ">1.0.2 <=2.3.4"
+ * - "2.0.1"
+ * - "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"
+ * - "2.x" (equivalent to "2.*")
+ * - "1.2.x" (equivalent to "1.2.*" and ">=1.2.0 <1.3.0")
+ */
+function satisfiesVersion(version, versionRange) {
+  if (arguments.length === 1) {
+    versionRange = version;
+    version = appInfo.version;
+  }
+
+  let ranges = versionRange.trim().split(reRangeSeparator);
+
+  return ranges.some(function(range) {
+    range = normalizeRange(range);
+
+    // No versions' range specified means that any version satisfies the
+    // requirements.
+    if (range === "")
+      return true;
+
+    let matches = range.match(reVersionRange);
+
+    if (!matches)
+      return false;
+
+    let [, lowMod, lowVer, highMod, highVer] = matches;
+
+    return compareVersion(version, lowMod, lowVer) && (highVer !== undefined
+      ? compareVersion(version, highMod, highVer)
+      : true);
+  });
+}
+exports.satisfiesVersion = satisfiesVersion;
+
+/**
+ * Ensure the current application satisfied the requirements specified in the
+ * module given. If not, an exception related to the incompatibility is
+ * returned; `null` otherwise.
+ *
+ * @param {Object} module
+ *  The module to check
+ * @returns {Error}
+ */
+function incompatibility(module) {
+  let { metadata, id } = module;
+
+  // if metadata or engines are not specified we assume compatibility is not
+  // an issue.
+  if (!metadata || !("engines" in metadata))
+    return null;
+
+  let { engines } = metadata;
+
+  if (engines === null || typeof(engines) !== "object")
+    return new Error("Malformed engines' property in metadata");
+
+  let applications = Object.keys(engines);
+
+  let versionRange;
+  applications.forEach(function(name) {
+    if (is(name)) {
+      versionRange = engines[name];
+      // Continue iteration. We want to ensure the module doesn't
+      // contain a typo in the applications' name or some unknown
+      // application - `is` function throws an exception in that case.
+    }
+  });
+
+  if (typeof(versionRange) === "string") {
+    if (satisfiesVersion(versionRange))
+      return null;
+
+    return new Error("Unsupported Application version: The module " + id +
+            " currently supports only version " + versionRange + " of " +
+            name + ".");
+  }
+
+  return new Error("Unsupported Application: The module " + id +
+            " currently supports only " + applications.join(", ") + ".")
+}
+exports.incompatibility = incompatibility;
--- a/addon-sdk/source/lib/sdk/tabs/observer.js
+++ b/addon-sdk/source/lib/sdk/tabs/observer.js
@@ -2,97 +2,106 @@
  * 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 { EventEmitterTrait: EventEmitter } = require("../deprecated/events");
+const { EventTarget } = require("../event/target");
+const { emit } = require("../event/core");
 const { DOMEventAssembler } = require("../deprecated/events/assembler");
-const { Trait } = require("../deprecated/light-traits");
-const { getActiveTab, getTabs, getTabContainer } = require("./utils");
+const { Class } = require("../core/heritage");
+const { getActiveTab, getTabs } = require("./utils");
 const { browserWindowIterator } = require("../deprecated/window-utils");
-const { isBrowser } = require('../window/utils');
+const { isBrowser, windows, getMostRecentBrowserWindow } = require("../window/utils");
 const { observer: windowObserver } = require("../windows/observer");
 
 const EVENTS = {
   "TabOpen": "open",
   "TabClose": "close",
   "TabSelect": "select",
   "TabMove": "move",
   "TabPinned": "pinned",
   "TabUnpinned": "unpinned"
 };
 
+const selectedTab = Symbol("observer/state/selectedTab");
 
 // Event emitter objects used to register listeners and emit events on them
 // when they occur.
-const observer = Trait.compose(DOMEventAssembler, EventEmitter).create({
-  /**
-   * Method is implemented by `EventEmitter` and is used just for emitting
-   * events on registered listeners.
-   */
-  _emit: Trait.required,
+const Observer = Class({
+  implements: [EventTarget, DOMEventAssembler],
+  initialize() {
+    this[selectedTab] = null;
+    // Currently Gecko does not dispatch any event on the previously selected
+    // tab before / after "TabSelect" is dispatched. In order to work around this
+    // limitation we keep track of selected tab and emit "deactivate" event with
+    // that before emitting "activate" on selected tab.
+    this.on("select", tab => {
+      const selected = this[selectedTab];
+      if (selected !== tab) {
+        if (selected) {
+          emit(this, 'deactivate', selected);
+        }
+
+        if (tab) {
+          this[selectedTab] = tab;
+          emit(this, 'activate', this[selectedTab]);
+        }
+      }
+    });
+
+
+    // We also observe opening / closing windows in order to add / remove it's
+    // containers to the observed list.
+    windowObserver.on("open", chromeWindow => {
+      if (isBrowser(chromeWindow)) {
+        this.observe(chromeWindow);
+      }
+    });
+
+    windowObserver.on("close", chromeWindow => {
+      if (isBrowser(chromeWindow)) {
+        // Bug 751546: Emit `deactivate` event on window close immediatly
+        // Otherwise we are going to face "dead object" exception on `select` event
+        if (getActiveTab(chromeWindow) === this[selectedTab]) {
+          emit(this, "deactivate", this[selectedTab]);
+          this[selectedTab] = null;
+        }
+        this.ignore(chromeWindow);
+      }
+    });
+
+
+    // Currently gecko does not dispatches "TabSelect" events when different
+    // window gets activated. To work around this limitation we emulate "select"
+    // event for this case.
+    windowObserver.on("activate", chromeWindow => {
+      if (isBrowser(chromeWindow)) {
+        emit(this, "select", getActiveTab(chromeWindow));
+      }
+    });
+
+    // We should synchronize state, since probably we already have at least one
+    // window open.
+    for (let chromeWindow of browserWindowIterator()) {
+      this.observe(chromeWindow);
+    }
+  },
   /**
    * Events that are supported and emitted by the module.
    */
   supportedEventsTypes: Object.keys(EVENTS),
   /**
    * Function handles all the supported events on all the windows that are
    * observed. Method is used to proxy events to the listeners registered on
    * this event emitter.
    * @param {Event} event
    *    Keyboard event being emitted.
    */
   handleEvent: function handleEvent(event) {
-    this._emit(EVENTS[event.type], event.target, event);
+    emit(this, EVENTS[event.type], event.target, event);
   }
 });
 
-// Currently Gecko does not dispatch any event on the previously selected
-// tab before / after "TabSelect" is dispatched. In order to work around this
-// limitation we keep track of selected tab and emit "deactivate" event with
-// that before emitting "activate" on selected tab.
-var selectedTab = null;
-function onTabSelect(tab) {
-  if (selectedTab !== tab) {
-    if (selectedTab) observer._emit('deactivate', selectedTab);
-    if (tab) observer._emit('activate', selectedTab = tab);
-  }
-};
-observer.on('select', onTabSelect);
-
-// We also observe opening / closing windows in order to add / remove it's
-// containers to the observed list.
-function onWindowOpen(chromeWindow) {
-  if (!isBrowser(chromeWindow)) return; // Ignore if it's not a browser window.
-  observer.observe(getTabContainer(chromeWindow));
-}
-windowObserver.on("open", onWindowOpen);
-
-function onWindowClose(chromeWindow) {
-  if (!isBrowser(chromeWindow)) return; // Ignore if it's not a browser window.
-  // Bug 751546: Emit `deactivate` event on window close immediatly
-  // Otherwise we are going to face "dead object" exception on `select` event
-  if (getActiveTab(chromeWindow) == selectedTab) {
-    observer._emit("deactivate", selectedTab);
-    selectedTab = null;
-  }
-  observer.ignore(getTabContainer(chromeWindow));
-}
-windowObserver.on("close", onWindowClose);
-
-
-// Currently gecko does not dispatches "TabSelect" events when different
-// window gets activated. To work around this limitation we emulate "select"
-// event for this case.
-windowObserver.on("activate", function onWindowActivate(chromeWindow) {
-  if (!isBrowser(chromeWindow)) return; // Ignore if it's not a browser window.
-  observer._emit("select", getActiveTab(chromeWindow));
-});
-
-// We should synchronize state, since probably we already have at least one
-// window open.
-for (let window of browserWindowIterator()) onWindowOpen(window);
-
-exports.observer = observer;
+exports.observer = new Observer();
--- a/addon-sdk/source/lib/sdk/tabs/worker.js
+++ b/addon-sdk/source/lib/sdk/tabs/worker.js
@@ -1,14 +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 ContentWorker = require('../content/worker-parent').Worker;
+const ContentWorker = require('../content/worker').Worker;
 
 function Worker(options, window) {
   options.window = window;
 
   let worker = ContentWorker(options);
   worker.once("detach", function detach() {
     worker.destroy();
   });
--- a/addon-sdk/source/lib/sdk/test.js
+++ b/addon-sdk/source/lib/sdk/test.js
@@ -3,20 +3,20 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 module.metadata = {
   "stability": "unstable"
 };
 
 const { Cu } = require("chrome");
-const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
+const { Task } = require("resource://gre/modules/Task.jsm", {});
 const { defer } = require("sdk/core/promise");
 const BaseAssert = require("sdk/test/assert").Assert;
-const { isFunction, isObject } = require("sdk/lang/type");
+const { isFunction, isObject, isGenerator } = require("sdk/lang/type");
 const { extend } = require("sdk/util/object");
 
 exports.Assert = BaseAssert;
 
 /**
  * Function takes test `suite` object in CommonJS format and defines all of the
  * tests from that suite and nested suites in a jetpack format on a given
  * `target` object. Optionally third argument `prefix` can be passed to prefix
@@ -44,28 +44,27 @@ function defineTestSuite(target, suite, 
         target[prefix + key] = function(options) {
 
           // Creating `assert` functions for this test.
           let assert = Assert(options);
           assert.end = () => options.done();
 
           // If test function is a generator use a task JS to allow yield-ing
           // style test runs.
-          if (test.isGenerator && test.isGenerator()) {
+          if (isGenerator(test)) {
             options.waitUntilDone();
             Task.spawn(test.bind(null, assert)).
-                then(null, assert.fail).
+                catch(assert.fail).
                 then(assert.end);
           }
 
           // If CommonJS test function expects more than one argument
           // it means that test is async and second argument is a callback
           // to notify that test is finished.
           else if (1 < test.length) {
-
             // Letting test runner know that test is executed async and
             // creating a callback function that CommonJS tests will call
             // once it's done.
             options.waitUntilDone();
             test(assert, function() {
               options.done();
             });
           }
--- a/addon-sdk/source/lib/sdk/test/assert.js
+++ b/addon-sdk/source/lib/sdk/test/assert.js
@@ -73,161 +73,169 @@ Assert.prototype = {
       this._log.fail(e);
       return;
     }
     let message = e.message;
     try {
       if ('operator' in e) {
         message += [
           " -",
-          source(e.expected),
+          source(e.actual),
           e.operator,
-          source(e.actual)
+          source(e.expected)
         ].join(" ");
       }
     }
     catch(e) {}
     this._log.fail(message);
   },
   pass: function pass(message) {
     this._log.pass(message);
+    return true;
   },
   error: function error(e) {
     this._log.exception(e);
   },
   ok: function ok(value, message) {
     if (!!!value) {
       this.fail({
         actual: value,
         expected: true,
         message: message,
         operator: "=="
       });
+      return false;
     }
-    else {
-      this.pass(message);
-    }
+
+    this.pass(message);
+    return true;
   },
 
   /**
    * The equality assertion tests shallow, coercive equality with `==`.
    * @example
    *    assert.equal(1, 1, "one is one");
    */
   equal: function equal(actual, expected, message) {
     if (actual == expected) {
       this.pass(message);
+      return true;
     }
-    else {
-      this.fail({
-        actual: actual,
-        expected: expected,
-        message: message,
-        operator: "=="
-      });
-    }
+
+    this.fail({
+      actual: actual,
+      expected: expected,
+      message: message,
+      operator: "=="
+    });
+    return false;
   },
 
   /**
    * The non-equality assertion tests for whether two objects are not equal
    * with `!=`.
    * @example
    *    assert.notEqual(1, 2, "one is not two");
    */
   notEqual: function notEqual(actual, expected, message) {
     if (actual != expected) {
       this.pass(message);
+      return true;
     }
-    else {
-      this.fail({
-        actual: actual,
-        expected: expected,
-        message: message,
-        operator: "!=",
-      });
-    }
+
+    this.fail({
+      actual: actual,
+      expected: expected,
+      message: message,
+      operator: "!=",
+    });
+    return false;
   },
 
   /**
    * The equivalence assertion tests a deep (with `===`) equality relation.
    * @example
    *    assert.deepEqual({ a: "foo" }, { a: "foo" }, "equivalent objects")
    */
    deepEqual: function deepEqual(actual, expected, message) {
     if (isDeepEqual(actual, expected)) {
       this.pass(message);
+      return true;
     }
-    else {
-      this.fail({
-        actual: actual,
-        expected: expected,
-        message: message,
-        operator: "deepEqual"
-      });
-    }
+
+    this.fail({
+      actual: actual,
+      expected: expected,
+      message: message,
+      operator: "deepEqual"
+    });
+    return false;
   },
 
   /**
    * The non-equivalence assertion tests for any deep (with `===`) inequality.
    * @example
    *    assert.notDeepEqual({ a: "foo" }, Object.create({ a: "foo" }),
    *                        "object's inherit from different prototypes");
    */
   notDeepEqual: function notDeepEqual(actual, expected, message) {
     if (!isDeepEqual(actual, expected)) {
       this.pass(message);
+      return true;
     }
-    else {
-      this.fail({
-        actual: actual,
-        expected: expected,
-        message: message,
-        operator: "notDeepEqual"
-      });
-    }
+
+    this.fail({
+      actual: actual,
+      expected: expected,
+      message: message,
+      operator: "notDeepEqual"
+    });
+    return false;
   },
 
   /**
    * The strict equality assertion tests strict equality, as determined by
    * `===`.
    * @example
    *    assert.strictEqual(null, null, "`null` is `null`")
    */
   strictEqual: function strictEqual(actual, expected, message) {
     if (actual === expected) {
       this.pass(message);
+      return true;
     }
-    else {
-      this.fail({
-        actual: actual,
-        expected: expected,
-        message: message,
-        operator: "==="
-      });
-    }
+
+    this.fail({
+      actual: actual,
+      expected: expected,
+      message: message,
+      operator: "==="
+    });
+    return false;
   },
 
   /**
    * The strict non-equality assertion tests for strict inequality, as
    * determined by `!==`.
    * @example
    *    assert.notStrictEqual(null, undefined, "`null` is not `undefined`");
    */
   notStrictEqual: function notStrictEqual(actual, expected, message) {
     if (actual !== expected) {
       this.pass(message);
+      return true;
     }
-    else {
-      this.fail({
-        actual: actual,
-        expected: expected,
-        message: message,
-        operator: "!=="
-      })
-    }
+
+    this.fail({
+      actual: actual,
+      expected: expected,
+      message: message,
+      operator: "!=="
+    });
+    return false;
   },
 
   /**
    * The assertion whether or not given `block` throws an exception. If optional
    * `Error` argument is provided and it's type of function thrown error is
    * asserted to be an instance of it, if type of `Error` is string then message
    * of throw exception is asserted to contain it.
    * @param {Function} block
@@ -270,45 +278,46 @@ Assert.prototype = {
       exception = e;
     }
 
     // If exception was thrown and `Error` argument was not passed assert is
     // passed.
     if (threw && (isUndefined(Error) ||
                  // If passed `Error` is RegExp using it's test method to
                  // assert thrown exception message.
-                 (isRegExp(Error) && Error.test(exception.message)) ||
+                 (isRegExp(Error) && (Error.test(exception.message) || Error.test(exception.toString()))) ||
                  // If passed `Error` is a constructor function testing if
                  // thrown exception is an instance of it.
                  (isFunction(Error) && instanceOf(exception, Error))))
     {
       this.pass(message);
+      return true;
     }
 
     // Otherwise we report assertion failure.
-    else {
-      let failure = {
-        message: message,
-        operator: "throws"
-      };
+    let failure = {
+      message: message,
+      operator: "matches"
+    };
 
-      if (exception)
-        failure.actual = exception;
+    if (exception) {
+      failure.actual = exception.message || exception.toString();
+    }
 
-      if (Error)
-        failure.expected = Error;
+    if (Error) {
+      failure.expected = Error.toString();
+    }
 
-      this.fail(failure);
-    }
+    this.fail(failure);
+    return false;
   }
 };
 exports.Assert = Assert;
 
 function isDeepEqual(actual, expected) {
-
   // 7.1. All identical values are equivalent, as determined by ===.
   if (actual === expected) {
     return true;
   }
 
   // 7.2. If the expected value is a Date object, the actual value is
   // equivalent if it is also a Date object that refers to the same time.
   else if (isDate(actual) && isDate(expected)) {
--- a/addon-sdk/source/lib/sdk/test/harness.js
+++ b/addon-sdk/source/lib/sdk/test/harness.js
@@ -433,17 +433,18 @@ var POINTLESS_ERRORS = [
   '[JavaScript Error: "The character encoding of the HTML document was ' +
     'not declared.',
   '[Javascript Warning: "Error: Failed to preserve wrapper of wrapped ' +
     'native weak map key',
   '[JavaScript Warning: "Duplicate resource declaration for',
   'file: "chrome://browser/content/',
   'file: "chrome://global/content/',
   '[JavaScript Warning: "The character encoding of a framed document was ' +
-    'not declared.'
+    'not declared.',
+  'file: "chrome://browser/skin/'
 ];
 
 var consoleListener = {
   registered: false,
 
   register: function() {
     if (this.registered)
       return;
--- a/addon-sdk/source/lib/sdk/test/utils.js
+++ b/addon-sdk/source/lib/sdk/test/utils.js
@@ -7,16 +7,19 @@ 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");
+const { isGenerator } = require("../lang/type");
+
+const { Task } = require("resource://gre/modules/Task.jsm");
 
 function getTestNames (exports)
   Object.keys(exports).filter(name => /^test/.test(name))
 
 function isTestAsync (fn) fn.length > 1
 function isHelperAsync (fn) fn.length > 2
 
 /*
@@ -24,36 +27,71 @@ function isHelperAsync (fn) fn.length > 
  * to be run before each test. `beforeFn` is called with a `name` string
  * as the first argument of the test name, and may specify a second
  * argument function `done` to indicate that this function should
  * resolve asynchronously
  */
 function before (exports, beforeFn) {
   getTestNames(exports).map(name => {
     let testFn = exports[name];
-    if (!isTestAsync(testFn) && !isHelperAsync(beforeFn)) {
+
+    // GENERATOR TESTS
+    if (isGenerator(testFn) && isGenerator(beforeFn)) {
+      exports[name] = function*(assert) {
+        yield Task.spawn(beforeFn.bind(null, name, assert));
+        yield Task.spawn(testFn.bind(null, assert));
+      }
+    }
+    else if (isGenerator(testFn) && !isHelperAsync(beforeFn)) {
+      exports[name] = function*(assert) {
+        beforeFn(name, assert);
+        yield Task.spawn(testFn.bind(null, assert));
+      }
+    }
+    else if (isGenerator(testFn) && isHelperAsync(beforeFn)) {
+      exports[name] = function*(assert) {
+        yield new Promise(resolve => beforeFn(name, assert, resolve));
+        yield Task.spawn(testFn.bind(null, assert));
+      }
+    }
+    // SYNC TESTS
+    else if (!isTestAsync(testFn) && isGenerator(beforeFn)) {
+      exports[name] = function*(assert) {
+        yield Task.spawn(beforeFn.bind(null, name, assert));
+        testFn(assert);
+      };
+    }
+    else if (!isTestAsync(testFn) && !isHelperAsync(beforeFn)) {
       exports[name] = function (assert) {
         beforeFn(name, assert);
         testFn(assert);
       };
     }
-    else if (isTestAsync(testFn) && !isHelperAsync(beforeFn)) {
-      exports[name] = function (assert, done) {
-        beforeFn(name, assert);
-        testFn(assert, done);
-      };
-    }
     else if (!isTestAsync(testFn) && isHelperAsync(beforeFn)) {
       exports[name] = function (assert, done) {
         beforeFn(name, assert, () => {
           testFn(assert);
           done();
         });
       };
-    } else if (isTestAsync(testFn) && isHelperAsync(beforeFn)) {
+    }
+    // ASYNC TESTS
+    else if (isTestAsync(testFn) && isGenerator(beforeFn)) {
+      exports[name] = function*(assert) {
+        yield Task.spawn(beforeFn.bind(null, name, assert));
+        yield new Promise(resolve => testFn(assert, resolve));
+      };
+    }
+    else if (isTestAsync(testFn) && !isHelperAsync(beforeFn)) {
+      exports[name] = function (assert, done) {
+        beforeFn(name, assert);
+        testFn(assert, done);
+      };
+    }
+    else if (isTestAsync(testFn) && isHelperAsync(beforeFn)) {
       exports[name] = function (assert, done) {
         beforeFn(name, assert, () => {
           testFn(assert, done);
         });
       };
     }
   });
 }
@@ -64,40 +102,72 @@ exports.before = before;
  * to be run after each test. `afterFn` is called with a `name` string
  * as the first argument of the test name, and may specify a second
  * argument function `done` to indicate that this function should
  * resolve asynchronously
  */
 function after (exports, afterFn) {
   getTestNames(exports).map(name => {
     let testFn = exports[name];
-    if (!isTestAsync(testFn) && !isHelperAsync(afterFn)) {
+
+    // GENERATOR TESTS
+    if (isGenerator(testFn) && isGenerator(afterFn)) {
+      exports[name] = function*(assert) {
+        yield Task.spawn(testFn.bind(null, assert));
+        yield Task.spawn(afterFn.bind(null, name, assert));
+      }
+    }
+    else if (isGenerator(testFn) && !isHelperAsync(afterFn)) {
+      exports[name] = function*(assert) {
+        yield Task.spawn(testFn.bind(null, assert));
+        afterFn(name, assert);
+      }
+    }
+    else if (isGenerator(testFn) && isHelperAsync(afterFn)) {
+      exports[name] = function*(assert) {
+        yield Task.spawn(testFn.bind(null, assert));
+        yield new Promise(resolve => afterFn(name, assert, resolve));
+      }
+    }
+    // SYNC TESTS
+    else if (!isTestAsync(testFn) && isGenerator(afterFn)) {
+      exports[name] = function*(assert) {
+        testFn(assert);
+        yield Task.spawn(afterFn.bind(null, name, assert));
+      };
+    }
+    else if (!isTestAsync(testFn) && !isHelperAsync(afterFn)) {
       exports[name] = function (assert) {
         testFn(assert);
         afterFn(name, assert);
       };
     }
-    else if (isTestAsync(testFn) && !isHelperAsync(afterFn)) {
-      exports[name] = function (assert, done) {
-        testFn(assert, () => {
-          afterFn(name, assert);
-          done();
-        });
-      };
-    }
     else if (!isTestAsync(testFn) && isHelperAsync(afterFn)) {
       exports[name] = function (assert, done) {
         testFn(assert);
         afterFn(name, assert, done);
       };
-    } else if (isTestAsync(testFn) && isHelperAsync(afterFn)) {
-      exports[name] = function (assert, done) {
-        testFn(assert, () => {
-          afterFn(name, assert, done);
-        });
+    }
+    // ASYNC TESTS
+    else if (isTestAsync(testFn) && isGenerator(afterFn)) {
+      exports[name] = function*(assert) {
+        yield new Promise(resolve => testFn(assert, resolve));
+        yield Task.spawn(afterFn.bind(null, name, assert));
+      };
+    }
+    else if (isTestAsync(testFn) && !isHelperAsync(afterFn)) {
+      exports[name] = function*(assert) {
+        yield new Promise(resolve => testFn(assert, resolve));
+        afterFn(name, assert);
+      };
+    }
+    else if (isTestAsync(testFn) && isHelperAsync(afterFn)) {
+      exports[name] = function*(assert) {
+        yield new Promise(resolve => testFn(assert, resolve));
+        yield new Promise(resolve => afterFn(name, assert, resolve));
       };
     }
   });
 }
 exports.after = after;
 
 function waitUntil (predicate, delay) {
   let { promise, resolve } = defer();
--- a/addon-sdk/source/lib/sdk/ui/button/view/events.js
+++ b/addon-sdk/source/lib/sdk/ui/button/view/events.js
@@ -2,15 +2,17 @@
  * 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',
   'engines': {
-    'Firefox': '*'
+    'Firefox': '*',
+    'SeaMonkey': '*',
+    'Thunderbird': '*'
   }
 };
 
 let channel = {};
 
 exports.events = channel;
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/ui/component.js
@@ -0,0 +1,179 @@
+"use strict";
+
+// Internal properties not exposed to the public.
+const cache = Symbol("component/cache");
+const writer = Symbol("component/writer");
+const isFirstWrite = Symbol("component/writer/first-write?");
+const currentState = Symbol("component/state/current");
+const pendingState = Symbol("component/state/pending");
+const isWriting = Symbol("component/writing?");
+
+const isntNull = x => x !== null;
+
+const Component = function(options, children) {
+  this[currentState] = null;
+  this[pendingState] = null;
+  this[writer] = null;
+  this[cache] = null;
+  this[isFirstWrite] = true;
+
+  this[Component.construct](options, children);
+}
+Component.Component = Component;
+// Constructs component.
+Component.construct = Symbol("component/construct");
+// Called with `options` and `children` and must return
+// initial state back.
+Component.initial = Symbol("component/initial");
+
+// Function patches current `state` with a given update.
+Component.patch = Symbol("component/patch");
+// Function that replaces current `state` with a passed state.
+Component.reset = Symbol("component/reset");
+
+// Function that must return render tree from passed state.
+Component.render = Symbol("component/render");
+
+// Path of the component with in the mount point.
+Component.path = Symbol("component/path");
+
+Component.isMounted = component => !!component[writer];
+Component.isWriting = component => !!component[isWriting];
+
+// Internal method that mounts component to a writer.
+// Mounts component to a writer.
+Component.mount = (component, write) => {
+  if (Component.isMounted(component)) {
+    throw Error("Can not mount already mounted component");
+  }
+
+  component[writer] = write;
+  Component.write(component);
+
+  if (component[Component.mounted]) {
+    component[Component.mounted]();
+  }
+}
+
+// Unmounts component from a writer.
+Component.unmount = (component) => {
+  if (Component.isMounted(component)) {
+    component[writer] = null;
+    if (component[Component.unmounted]) {
+      component[Component.unmounted]();
+    }
+  } else {
+    console.warn("Unmounting component that is not mounted is redundant");
+  }
+};
+ // Method invoked once after inital write occurs.
+Component.mounted = Symbol("component/mounted");
+// Internal method that unmounts component from the writer.
+Component.unmounted = Symbol("component/unmounted");
+// Function that must return true if component is changed
+Component.isUpdated = Symbol("component/updated?");
+Component.update = Symbol("component/update");
+Component.updated = Symbol("component/updated");
+
+const writeChild = base => (child, index) => Component.write(child, base, index)
+Component.write = (component, base, index) => {
+  if (component === null) {
+    return component;
+  }
+
+  if (!(component instanceof Component)) {
+    const path = base ? `${base}${component.key || index}/` : `/`;
+    return Object.assign({}, component, {
+      [Component.path]: path,
+      children: component.children && component.children.
+                                        map(writeChild(path)).
+                                        filter(isntNull)
+    });
+  }
+
+  component[isWriting] = true;
+
+  try {
+
+    const current = component[currentState];
+    const pending = component[pendingState] || current;
+    const isUpdated = component[Component.isUpdated];
+    const isInitial = component[isFirstWrite];
+
+    if (isUpdated(current, pending) || isInitial) {
+      if (!isInitial && component[Component.update]) {
+        component[Component.update](pending, current)
+      }
+
+      // Note: [Component.update] could have caused more updates so can't use
+      // `pending` as `component[pendingState]` may have changed.
+      component[currentState] = component[pendingState] || current;
+      component[pendingState] = null;
+
+      const tree = component[Component.render](component[currentState]);
+      component[cache] = Component.write(tree, base, index);
+      if (component[writer]) {
+        component[writer].call(null, component[cache]);
+      }
+
+      if (!isInitial && component[Component.updated]) {
+        component[Component.updated](current, pending);
+      }
+    }
+
+    component[isFirstWrite] = false;
+
+    return component[cache];
+  } finally {
+    component[isWriting] = false;
+  }
+};
+
+Component.prototype = Object.freeze({
+  constructor: Component,
+
+  [Component.mounted]: null,
+  [Component.unmounted]: null,
+  [Component.update]: null,
+  [Component.updated]: null,
+
+  get state() {
+    return this[pendingState] || this[currentState];
+  },
+
+
+  [Component.construct](settings, items) {
+    const initial = this[Component.initial];
+    const base = initial(settings, items);
+    const options = Object.assign(Object.create(null), base.options, settings);
+    const children = base.children || items || null;
+    const state = Object.assign(Object.create(null), base, {options, children});
+    this[currentState] = state;
+
+    if (this.setup) {
+      this.setup(state);
+    }
+  },
+  [Component.initial](options, children) {
+    return Object.create(null);
+  },
+  [Component.patch](update) {
+    this[Component.reset](Object.assign({}, this.state, update));
+  },
+  [Component.reset](state) {
+    this[pendingState] = state;
+    if (Component.isMounted(this) && !Component.isWriting(this)) {
+      Component.write(this);
+    }
+  },
+
+  [Component.isUpdated](before, after) {
+    return before != after
+  },
+
+  [Component.render](state) {
+    throw Error("Component must implement [Component.render] member");
+  }
+});
+
+module.exports = Component;
--- a/addon-sdk/source/lib/sdk/ui/sidebar.js
+++ b/addon-sdk/source/lib/sdk/ui/sidebar.js
@@ -19,17 +19,17 @@ const { URL } = require('../url');
 const { add, remove, has, clear, iterator } = require('../lang/weak-set');
 const { id: addonID, data } = require('../self');
 const { WindowTracker } = require('../deprecated/window-utils');
 const { isShowing } = require('./sidebar/utils');
 const { isBrowser, getMostRecentBrowserWindow, windows, isWindowPrivate } = require('../window/utils');
 const { ns } = require('../core/namespace');
 const { remove: removeFromArray } = require('../util/array');
 const { show, hide, toggle } = require('./sidebar/actions');
-const { Worker } = require('../content/worker');
+const { Worker } = require('../deprecated/sync-worker');
 const { contract: sidebarContract } = require('./sidebar/contract');
 const { create, dispose, updateTitle, updateURL, isSidebarShowing, showSidebar, hideSidebar } = require('./sidebar/view');
 const { defer } = require('../core/promise');
 const { models, views, viewsFor, modelFor } = require('./sidebar/namespace');
 const { isLocalURL } = require('../url');
 const { ensure } = require('../system/unload');
 const { identify } = require('./id');
 const { uuid } = require('../util/uuid');
--- a/addon-sdk/source/lib/sdk/ui/state.js
+++ b/addon-sdk/source/lib/sdk/ui/state.js
@@ -3,17 +3,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 'use strict';
 
 // The Button module currently supports only Firefox.
 // See: https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps
 module.metadata = {
   'stability': 'experimental',
   'engines': {
-    'Firefox': '*'
+    'Firefox': '*',
+    'SeaMonkey': '*',
+    'Thunderbird': '*'
   }
 };
 
 const { Ci } = require('chrome');
 
 const events = require('../event/utils');
 const { events: browserEvents } = require('../browser/events');
 const { events: tabEvents } = require('../tab/events');
--- a/addon-sdk/source/lib/sdk/ui/state/events.js
+++ b/addon-sdk/source/lib/sdk/ui/state/events.js
@@ -2,15 +2,17 @@
  * 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',
   'engines': {
-    'Firefox': '*'
+    'Firefox': '*',
+    'SeaMonkey': '*',
+    'Thunderbird': '*'
   }
 };
 
 let channel = {};
 
 exports.events = channel;
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/uri/resource.js
@@ -0,0 +1,37 @@
+/* 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} = require("chrome");
+const ioService = Cc["@mozilla.org/network/io-service;1"].
+                  getService(Ci.nsIIOService);
+const resourceHandler = ioService.getProtocolHandler("resource").
+                        QueryInterface(Ci.nsIResProtocolHandler);
+
+const URI = (uri, base=null) =>
+  ioService.newURI(uri, null, base && URI(base))
+
+const mount = (domain, uri) =>
+  resourceHandler.setSubstitution(domain, ioService.newURI(uri, null, null));
+exports.mount = mount;
+
+const unmount = (domain, uri) =>
+  resourceHandler.setSubstitution(domain, null);
+exports.unmount = unmount;
+
+const domain = 1;
+const path = 2;
+const resolve = (uri) => {
+  const match = /resource\:\/\/([^\/]+)\/{0,1}([\s\S]*)/.exec(uri);
+  const domain = match && match[1];
+  const path = match && match[2];
+  return !match ? null :
+         !resourceHandler.hasSubstitution(domain) ? null :
+  resourceHandler.resolveURI(URI(`/${path}`, `resource://${domain}/`));
+}
+exports.resolve = resolve;
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/util/bond.js
@@ -0,0 +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/. */
+"use strict";
+
+module.metadata = {
+  "stability": "experimental"
+};
+
+const makeDescriptor = (name, method) => ({
+  get() {
+    if (!Object.hasOwnProperty.call(this, name)) {
+      Object.defineProperty(this, name, {value: method.bind(this)});
+      return this[name];
+    } else {
+      return method;
+    }
+  }
+});
+
+const Bond = function(methods) {
+  let descriptor = {};
+  let members = [...Object.getOwnPropertyNames(methods),
+                 ...Object.getOwnPropertySymbols(methods)];
+
+  for (let name of members) {
+    let method = methods[name];
+    if (typeof(method) !== "function") {
+      throw new TypeError(`Property named "${name}" passed to Bond must be a function`);
+    }
+    descriptor[name] = makeDescriptor(name, method);
+  }
+
+  return Object.create(Bond.prototype, descriptor);
+}
+exports.Bond = Bond;
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/util/registry.js
+++ /dev/null
@@ -1,59 +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";
-
-module.metadata = {
-  "stability": "unstable"
-};
-
-const { EventEmitter } = require('../deprecated/events');
-const unload = require('../system/unload');
-
-const Registry = EventEmitter.compose({
-  _registry: null,
-  _constructor: null,
-  constructor: function Registry(constructor) {
-    this._registry = [];
-    this._constructor = constructor;
-    this.on('error', this._onError = this._onError.bind(this));
-    unload.ensure(this, "_destructor");
-  },
-  _destructor: function _destructor() {
-    let _registry = this._registry.slice(0);
-    for (let instance of _registry)
-      this._emit('remove', instance);
-    this._registry.splice(0);
-  },
-  _onError: function _onError(e) {
-    if (!this._listeners('error').length)
-      console.error(e);
-  },
-  has: function has(instance) {
-    let _registry = this._registry;
-    return (
-      (0 <= _registry.indexOf(instance)) ||
-      (instance && instance._public && 0 <= _registry.indexOf(instance._public))
-    );
-  },
-  add: function add(instance) {
-    let { _constructor, _registry } = this; 
-    if (!(instance instanceof _constructor))
-      instance = new _constructor(instance);
-    if (0 > _registry.indexOf(instance)) {
-      _registry.push(instance);
-      this._emit('add', instance);
-    }
-    return instance;
-  },
-  remove: function remove(instance) {
-    let _registry = this._registry;
-    let index = _registry.indexOf(instance)
-    if (0 <= index) {
-      this._emit('remove', instance);
-      _registry.splice(index, 1);
-    }
-  }
-});
-exports.Registry = Registry;
-
--- a/addon-sdk/source/lib/sdk/util/sequence.js
+++ b/addon-sdk/source/lib/sdk/util/sequence.js
@@ -1,12 +1,11 @@
 /* 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/.
- */
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 module.metadata = {
   "stability": "experimental"
 };
 
 // Disclamer:
 // In this module we'll have some common argument / variable names
@@ -17,24 +16,25 @@ module.metadata = {
 // - `p` stands for "predicate" that is function which returns logical
 //   true or false and is intended to be side effect free.
 // - `x` / `y` single item of the sequence.
 // - `xs` / `ys` sequence of `x` / `y` items where `x` / `y` signifies
 //    type of the items in sequence, so sequence is not of the same item.
 // - `_` used for argument(s) or variable(s) who's values are ignored.
 
 const { complement, flip, identity } = require("../lang/functional");
-const { isArray, isArguments, isMap, isSet,
+const { isArray, isArguments, isMap, isSet, isGenerator,
         isString, isBoolean, isNumber } = require("../lang/type");
 
 const Sequence = function Sequence(iterator) {
-  if (iterator.isGenerator && iterator.isGenerator())
-    this[Symbol.iterator] = iterator;
-  else
+  if (!isGenerator(iterator)) {
     throw TypeError("Expected generator argument");
+  }
+
+  this[Symbol.iterator] = iterator;
 };
 exports.Sequence = Sequence;
 
 const polymorphic = dispatch => x =>
   x === null ? dispatch.null(null) :
   x === void(0) ? dispatch.void(void(0)) :
   isArray(x) ? (dispatch.array || dispatch.indexed)(x) :
   isString(x) ? (dispatch.string || dispatch.indexed)(x) :
@@ -56,19 +56,16 @@ const seq = polymorphic({
   string: identity,
   arguments: identity,
   map: identity,
   set: identity,
   default: x => x instanceof Sequence ? x : new Sequence(x)
 });
 exports.seq = seq;
 
-
-
-
 // Function to cast seq to string.
 const string = (...etc) => "".concat(...etc);
 exports.string = string;
 
 // Function for casting seq to plain object.
 const object = (...pairs) => {
   let result = {};
   for (let [key, value] of pairs)
@@ -106,16 +103,37 @@ const pairs = polymorphic({
   }),
   default: object => seq(function* () {
     for (let key of Object.keys(object))
       yield [key, object[key]];
   })
 });
 exports.pairs = pairs;
 
+const names = polymorphic({
+  null: empty,
+  void: empty,
+  default: object => seq(function*() {
+    for (let name of Object.getOwnPropertyNames(object)) {
+      yield name;
+    }
+  })
+});
+exports.names = names;
+
+const symbols = polymorphic({
+  null: empty,
+  void: empty,
+  default: object => seq(function* () {
+    for (let symbol of Object.getOwnPropertySymbols(object)) {
+      yield symbol;
+    }
+  })
+});
+exports.symbols = symbols;
 
 const keys = polymorphic({
   null: empty,
   void: empty,
   indexed: indexed => seq(function* () {
     const count = indexed.length;
     let index = 0;
     while (index < count) {
--- a/addon-sdk/source/lib/sdk/window/utils.js
+++ b/addon-sdk/source/lib/sdk/window/utils.js
@@ -21,17 +21,19 @@ const WM = Cc['@mozilla.org/appshell/win
 const io = Cc['@mozilla.org/network/io-service;1'].
            getService(Ci.nsIIOService);
 const FM = Cc["@mozilla.org/focus-manager;1"].
               getService(Ci.nsIFocusManager);
 
 const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
 
 const BROWSER = 'navigator:browser',
-      URI_BROWSER = 'chrome://browser/content/browser.xul',
+      URI_BROWSER = Cc['@mozilla.org/preferences-service;1'].
+                    getService(Ci.nsIPrefService).
+                    getBranch(null).getCharPref('browser.chromeURL'),
       NAME = '_blank',
       FEATURES = 'chrome,all,dialog=no,non-private';
 
 function isWindowPrivate(win) {
   if (!win)
     return false;
 
   // if the pbService is undefined, the PrivateBrowsingUtils.jsm is available,
--- a/addon-sdk/source/lib/sdk/windows/firefox.js
+++ b/addon-sdk/source/lib/sdk/windows/firefox.js
@@ -4,46 +4,132 @@
 'use strict';
 
 const { Cc, Ci, Cr } = require('chrome'),
       { Trait } = require('../deprecated/traits'),
       { List } = require('../deprecated/list'),
       { EventEmitter } = require('../deprecated/events'),
       { WindowTabs, WindowTabTracker } = require('./tabs-firefox'),
       { WindowDom } = require('./dom'),
-      { WindowLoader } = require('./loader'),
       { isBrowser, getWindowDocShell, isFocused,
         windows: windowIterator, isWindowPrivate } = require('../window/utils'),
       { Options } = require('../tabs/common'),
       apiUtils = require('../deprecated/api-utils'),
       unload = require('../system/unload'),
       windowUtils = require('../deprecated/window-utils'),
       { WindowTrackerTrait } = windowUtils,
       { ns } = require('../core/namespace'),
       { observer: windowObserver } = require('./observer');
 const { windowNS } = require('../window/namespace');
 const { isPrivateBrowsingSupported } = require('../self');
 const { ignoreWindow, isPrivate } = require('sdk/private-browsing/utils');
 const { viewFor } = require('../view/core');
-
+const { openDialog } = require('../window/utils');
+const ON_LOAD = 'load',
+      ON_UNLOAD = 'unload',
+      STATE_LOADED = 'complete';
 /**
  * Window trait composes safe wrappers for browser window that are E10S
  * compatible.
  */
 const BrowserWindowTrait = Trait.compose(
   EventEmitter,
   WindowDom.resolve({ close: '_close' }),
   WindowTabs,
   WindowTabTracker,
-  WindowLoader,
   /* WindowSidebars, */
   Trait.compose({
     _emit: Trait.required,
     _close: Trait.required,
-    _load: Trait.required,
+    /**
+     * Private window who's load event is being tracked. Once window is loaded
+     * `_onLoad` is called.
+     * @type {nsIWindow}
+     */
+    get _window() this.__window,
+    set _window(window) {
+      let _window = this.__window;
+      if (!window) window = null;
+
+      if (window !== _window) {
+        if (_window) {
+          if (this.__unloadListener)
+            _window.removeEventListener(ON_UNLOAD, this.__unloadListener, false);
+
+          if (this.__loadListener)
+            _window.removeEventListener(ON_LOAD, this.__loadListener, false);
+        }
+
+        if (window) {
+          window.addEventListener(
+            ON_UNLOAD,
+            this.__unloadListener ||
+              (this.__unloadListener = this._unloadListener.bind(this))
+            ,
+            false
+          );
+
+          this.__window = window;
+
+          // If window is not loaded yet setting up a listener.
+          if (STATE_LOADED != window.document.readyState) {
+            window.addEventListener(
+              ON_LOAD,
+              this.__loadListener ||
+                (this.__loadListener = this._loadListener.bind(this))
+              ,
+              false
+            );
+          }
+          else { // If window is loaded calling listener next turn of event loop.
+            this._onLoad(window)
+          }
+        }
+        else {
+          this.__window = null;
+        }
+      }
+    },
+    __window: null,
+    /**
+     * Internal method used for listening 'load' event on the `_window`.
+     * Method takes care of removing itself from 'load' event listeners once
+     * event is being handled.
+     */
+    _loadListener: function _loadListener(event) {
+      let window = this._window;
+      if (!event.target || event.target.defaultView != window) return;
+      window.removeEventListener(ON_LOAD, this.__loadListener, false);
+      this._onLoad(window);
+    },
+    __loadListener: null,
+    /**
+     * Internal method used for listening 'unload' event on the `_window`.
+     * Method takes care of removing itself from 'unload' event listeners once
+     * event is being handled.
+     */
+    _unloadListener: function _unloadListener(event) {
+      let window = this._window;
+      if (!event.target
+        || event.target.defaultView != window
+        || STATE_LOADED != window.document.readyState
+      ) return;
+      window.removeEventListener(ON_UNLOAD, this.__unloadListener, false);
+      this._onUnload(window);
+    },
+    __unloadListener: null,
+    _load: function _load() {
+      if (this.__window)
+        return;
+
+      this._window = openDialog({
+        private: this._isPrivate,
+        args: this._tabOptions.map(function(options) options.url).join("|")
+      });
+    },
     /**
      * Constructor returns wrapper of the specified chrome window.
      * @param {nsIWindow} window
      */
     constructor: function BrowserWindow(options) {
       // Register this window ASAP, in order to avoid loop that would try
       // to create this window instance over and over (see bug 648244)
       windows.push(this);
deleted file mode 100644
--- a/addon-sdk/source/lib/sdk/windows/loader.js
+++ /dev/null
@@ -1,128 +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';
-
-module.metadata = {
-  "stability": "unstable"
-};
-
-const { Cc, Ci } = require('chrome'),
-      { setTimeout } = require('../timers'),
-      { Trait } = require('../deprecated/traits'),
-      { openDialog } = require('../window/utils'),
-
-      ON_LOAD = 'load',
-      ON_UNLOAD = 'unload',
-      STATE_LOADED = 'complete';
-
-/**
- * Trait provides private `_window` property and requires `_onLoad` property
- * that will be called when `_window` is loaded. If `_window` property value
- * is changed with already loaded window `_onLoad` still will be called.
- */
-const WindowLoader = Trait.compose({
-  /**
-   * Internal listener that is called when window is loaded.
-   * Please keep in mind that this trait will not handle exceptions that may
-   * be thrown by this method so method itself should take care of
-   * handling them.
-   * @param {nsIWindow} window
-   */
-  _onLoad: Trait.required,
-  _tabOptions: Trait.required,
-  /**
-   * Internal listener that is called when `_window`'s DOM 'unload' event
-   * is dispatched. Please note that this trait will not handle exceptions that
-   * may be thrown by this method so method itself should take care of
-   * handling them.
-   */
-  _onUnload: Trait.required,
-  _load: function _load() {
-    if (this.__window)
-      return;
-
-    this._window = openDialog({
-      private: this._isPrivate,
-      args: this._tabOptions.map(function(options) options.url).join("|")
-    });
-  },
-  /**
-   * Private window who's load event is being tracked. Once window is loaded
-   * `_onLoad` is called.
-   * @type {nsIWindow}
-   */
-  get _window() this.__window,
-  set _window(window) {
-    let _window = this.__window;
-    if (!window) window = null;
-
-    if (window !== _window) {
-      if (_window) {
-        if (this.__unloadListener)
-          _window.removeEventListener(ON_UNLOAD, this.__unloadListener, false);
-
-        if (this.__loadListener)
-          _window.removeEventListener(ON_LOAD, this.__loadListener, false);
-      }
-
-      if (window) {
-        window.addEventListener(
-          ON_UNLOAD,
-          this.__unloadListener ||
-            (this.__unloadListener = this._unloadListener.bind(this))
-          ,
-          false
-        );
-
-        this.__window = window;
-
-        // If window is not loaded yet setting up a listener.
-        if (STATE_LOADED != window.document.readyState) {
-          window.addEventListener(
-            ON_LOAD,
-            this.__loadListener ||
-              (this.__loadListener = this._loadListener.bind(this))
-            ,
-            false
-          );
-        }
-        else { // If window is loaded calling listener next turn of event loop.
-          this._onLoad(window)
-        }
-      }
-      else {
-        this.__window = null;
-      }
-    }
-  },
-  __window: null,
-  /**
-   * Internal method used for listening 'load' event on the `_window`.
-   * Method takes care of removing itself from 'load' event listeners once
-   * event is being handled.
-   */
-  _loadListener: function _loadListener(event) {
-    let window = this._window;
-    if (!event.target || event.target.defaultView != window) return;
-    window.removeEventListener(ON_LOAD, this.__loadListener, false);
-    this._onLoad(window);
-  },
-  __loadListener: null,
-  /**
-   * Internal method used for listening 'unload' event on the `_window`.
-   * Method takes care of removing itself from 'unload' event listeners once
-   * event is being handled.
-   */
-  _unloadListener: function _unloadListener(event) {
-    let window = this._window;
-    if (!event.target
-      || event.target.defaultView != window
-      || STATE_LOADED != window.document.readyState
-    ) return;
-    window.removeEventListener(ON_UNLOAD, this.__unloadListener, false);
-    this._onUnload(window);
-  },
-  __unloadListener: null
-});
-exports.WindowLoader = WindowLoader;
--- a/addon-sdk/source/lib/sdk/windows/observer.js
+++ b/addon-sdk/source/lib/sdk/windows/observer.js
@@ -2,50 +2,48 @@
  * 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 { EventEmitterTrait: EventEmitter } = require("../deprecated/events");
+const { EventTarget } = require("../event/target");
+const { emit } = require("../event/core");
 const { WindowTracker, windowIterator } = require("../deprecated/window-utils");
 const { DOMEventAssembler } = require("../deprecated/events/assembler");
-const { Trait } = require("../deprecated/light-traits");
+const { Class } = require("../core/heritage");
 
 // Event emitter objects used to register listeners and emit events on them
 // when they occur.
-const observer = Trait.compose(DOMEventAssembler, EventEmitter).create({
-  /**
-   * Method is implemented by `EventEmitter` and is used just for emitting
-   * events on registered listeners.
-   */
-  _emit: Trait.required,
+const Observer = Class({
+  initialize() {
+    // Using `WindowTracker` to track window events.
+    WindowTracker({
+      onTrack: chromeWindow => {
+        emit(this, "open", chromeWindow);
+        this.observe(chromeWindow);
+      },
+      onUntrack: chromeWindow => {
+        emit(this, "close", chromeWindow);
+        this.ignore(chromeWindow);
+      }
+    });
+  },
+  implements: [EventTarget, DOMEventAssembler],
   /**
    * Events that are supported and emitted by the module.
    */
   supportedEventsTypes: [ "activate", "deactivate" ],
   /**
    * Function handles all the supported events on all the windows that are
    * observed. Method is used to proxy events to the listeners registered on
    * this event emitter.
    * @param {Event} event
    *    Keyboard event being emitted.
    */
-  handleEvent: function handleEvent(event) {
-    this._emit(event.type, event.target, event);
+  handleEvent(event) {
+    emit(this, event.type, event.target, event);
   }
 });
 
-// Using `WindowTracker` to track window events.
-WindowTracker({
-  onTrack: function onTrack(chromeWindow) {
-    observer._emit("open", chromeWindow);
-    observer.observe(chromeWindow);
-  },
-  onUntrack: function onUntrack(chromeWindow) {
-    observer._emit("close", chromeWindow);
-    observer.ignore(chromeWindow);
-  }
-});
-
-exports.observer = observer;
+exports.observer = new Observer();
--- a/addon-sdk/source/lib/sdk/windows/tabs-fennec.js
+++ b/addon-sdk/source/lib/sdk/windows/tabs-fennec.js
@@ -153,17 +153,17 @@ function onTabSelect(event) {
   if (ignoreWindow(browser.contentWindow))
     return;
 
   // Set value whenever new tab becomes active.
   let tab = getTabForBrowser(browser);
   emit(tab, 'activate', tab);
   emit(gTabs, 'activate', tab);
 
-  for (let of in gTabs) {
+  for (let t of gTabs) {
     if (t === tab) continue;
     emit(t, 'deactivate', t);
     emit(gTabs, 'deactivate', t);
   }
 }
 
 // TabClose
 function onTabClose(tab) {
--- a/addon-sdk/source/lib/toolkit/loader.js
+++ b/addon-sdk/source/lib/toolkit/loader.js
@@ -1,34 +1,26 @@
 /* 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/. */
 
-;(function(id, factory) { // Module boilerplate :(
-  if (typeof(define) === 'function') { // RequireJS
-    define(factory);
-  } else if (typeof(require) === 'function') { // CommonJS
-    factory.call(this, require, exports, module);
-  } else if (~String(this).indexOf('BackstagePass')) { // JSM
-    this[factory.name] = {};
-    factory(function require(uri) {
-      var imports = {};
-      this['Components'].utils.import(uri, imports);
-      return imports;
-    }, this[factory.name], { uri: __URI__, id: id });
-    this.EXPORTED_SYMBOLS = [factory.name];
-  } else if (~String(this).indexOf('Sandbox')) { // Sandbox
-    factory(function require(uri) {}, this, { uri: __URI__, id: id });
-  } else {  // Browser or alike
-    var globals = this
-    factory(function require(id) {
-      return globals[id];
-    }, (globals[id] = {}), { uri: document.location.href + '#' + id, id: id });
+;(function(factory) { // Module boilerplate :(
+  if (typeof(require) === 'function') { // CommonJS
+    require("chrome").Cu.import(module.uri, exports);
   }
-}).call(this, 'loader', function Loader(require, exports, module) {
+  else if (~String(this).indexOf('BackstagePass')) { // JSM
+    let module = { uri: __URI__, id: "toolkit/loader", exports: Object.create(null) }
+    factory(module);
+    Object.assign(this, module.exports);
+    this.EXPORTED_SYMBOLS = Object.getOwnPropertyNames(module.exports);
+  }
+  else {
+    throw Error("Loading environment is not supported");
+  }
+})(module => {
 
 'use strict';
 
 module.metadata = {
   "stability": "unstable"
 };
 
 const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu,
@@ -38,16 +30,20 @@ const { loadSubScript } = Cc['@mozilla.o
                      getService(Ci.mozIJSSubScriptLoader);
 const { notifyObservers } = Cc['@mozilla.org/observer-service;1'].
                         getService(Ci.nsIObserverService);
 const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
 const { Reflect } = Cu.import("resource://gre/modules/reflect.jsm", {});
 const { ConsoleAPI } = Cu.import("resource://gre/modules/devtools/Console.jsm");
 const { join: pathJoin, normalize, dirname } = Cu.import("resource://gre/modules/osfile/ospath_unix.jsm");
 
+const xulappURI = module.uri.replace("toolkit/loader.js",
+                                     "sdk/system/xul-app.jsm");
+const { incompatibility } = Cu.import(xulappURI, {}).XulApp;
+
 // Define some shortcuts.
 const bind = Function.call.bind(Function.bind);
 const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
 const define = Object.defineProperties;
 const prototypeOf = Object.getPrototypeOf;
 const create = Object.create;
 const keys = Object.keys;
 const getOwnIdentifiers = x => [...Object.getOwnPropertyNames(x),
@@ -85,17 +81,17 @@ function freeze(object) {
 // Returns map of given `object`-s own property descriptors.
 const descriptor = iced(function descriptor(object) {
   let value = {};
   getOwnIdentifiers(object).forEach(function(name) {
     value[name] = getOwnPropertyDescriptor(object, name)
   });
   return value;
 });
-exports.descriptor = descriptor;
+Loader.descriptor = descriptor;
 
 // Freeze important built-ins so they can't be used by untrusted code as a
 // message passing channel.
 freeze(Object);
 freeze(Object.prototype);
 freeze(Function);
 freeze(Function.prototype);
 freeze(Array);
@@ -122,25 +118,25 @@ function iced(f) {
 const override = iced(function override(target, source) {
   let properties = descriptor(target)
   let extension = descriptor(source || {})
   getOwnIdentifiers(extension).forEach(function(name) {
     properties[name] = extension[name];
   });
   return define({}, properties);
 });
-exports.override = override;
+Loader.override = override;
 
 function sourceURI(uri) { return String(uri).split(" -> ").pop(); }
-exports.sourceURI = iced(sourceURI);
+Loader.sourceURI = iced(sourceURI);
 
 function isntLoaderFrame(frame) { return frame.fileName !== module.uri }
 
 function parseURI(uri) { return String(uri).split(" -> ").pop(); }
-exports.parseURI = parseURI;
+Loader.parseURI = parseURI;
 
 function parseStack(stack) {
   let lines = String(stack).split("\n");
   return lines.reduce(function(frames, line) {
     if (line) {
       let atIndex = line.indexOf("@");
       let columnIndex = line.lastIndexOf(":");
       let lineIndex = line.lastIndexOf(":", columnIndex - 1);
@@ -153,28 +149,28 @@ function parseStack(stack) {
         name: name,
         lineNumber: lineNumber,
         columnNumber: columnNumber
       });
     }
     return frames;
   }, []);
 }
-exports.parseStack = parseStack;
+Loader.parseStack = parseStack;
 
 function serializeStack(frames) {
   return frames.reduce(function(stack, frame) {
     return frame.name + "@" +
            frame.fileName + ":" +
            frame.lineNumber + ":" +
            frame.columnNumber + "\n" +
            stack;
   }, "");
 }
-exports.serializeStack = serializeStack;
+Loader.serializeStack = serializeStack;
 
 function readURI(uri) {
   let stream = NetUtil.newChannel(uri, 'UTF-8', null).open();
   let count = stream.available();
   let data = NetUtil.readInputStreamToString(stream, count, {
     charset: 'UTF-8'
   });
 
@@ -189,17 +185,17 @@ function join (...paths) {
   // OS.File `normalize` strips out the second slash in
   // `resource://` or `chrome://`, and third slash in
   // `file:///`, so we work around this
   resolved = resolved.replace(/^resource\:\/([^\/])/, 'resource://$1');
   resolved = resolved.replace(/^file\:\/([^\/])/, 'file:///$1');
   resolved = resolved.replace(/^chrome\:\/([^\/])/, 'chrome://$1');
   return resolved;
 }
-exports.join = join;
+Loader.join = join;
 
 // Function takes set of options and returns a JS sandbox. Function may be
 // passed set of options:
 //  - `name`: A string value which identifies the sandbox in about:memory. Will
 //    throw exception if omitted.
 // - `principal`: String URI or `nsIPrincipal` for the sandbox. Defaults to
 //    system principal.
 // - `prototype`: Ancestor for the sandbox that will be created. Defaults to
@@ -241,17 +237,17 @@ const Sandbox = iced(function Sandbox(op
   // to avoid shadowing.
   delete sandbox.Iterator;
   delete sandbox.Components;
   delete sandbox.importFunction;
   delete sandbox.debug;
 
   return sandbox;
 });
-exports.Sandbox = Sandbox;
+Loader.Sandbox = Sandbox;
 
 // Evaluates code from the given `uri` into given `sandbox`. If
 // `options.source` is passed, then that code is evaluated instead.
 // Optionally following options may be given:
 // - `options.encoding`: Source encoding, defaults to 'UTF-8'.
 // - `options.line`: Line number to start count from for stack traces.
 //    Defaults to 1.
 // - `options.version`: Version of JS used, defaults to '1.8'.
@@ -261,17 +257,17 @@ const evaluate = iced(function evaluate(
     line: 1,
     version: '1.8',
     source: null
   }, options);
 
   return source ? Cu.evalInSandbox(source, sandbox, version, uri, line)
                 : loadSubScript(uri, sandbox, encoding);
 });
-exports.evaluate = evaluate;
+Loader.evaluate = evaluate;
 
 // Populates `exports` of the given CommonJS `module` object, in the context
 // of the given `loader` by evaluating code associated with it.
 const load = iced(function load(loader, module) {
   let { sandboxes, globals } = loader;
   let require = Require(loader, module);
 
   // We expose set of properties defined by `CommonJS` specification via
@@ -294,34 +290,36 @@ const load = iced(function load(loader, 
     // Create a new object in this sandbox, that will be used as
     // the scope object for this particular module
     sandbox = new loader.sharedGlobalSandbox.Object();
     // Inject all expected globals in the scope object
     getOwnIdentifiers(globals).forEach(function(name) {
       descriptors[name] = getOwnPropertyDescriptor(globals, name)
     });
     define(sandbox, descriptors);
-  } else {
+  }
+  else {
     sandbox = Sandbox({
       name: module.uri,
       prototype: create(globals, descriptors),
       wantXrays: false,
       wantGlobalProperties: module.id == "sdk/indexed-db" ? ["indexedDB"] : [],
       invisibleToDebugger: loader.invisibleToDebugger,
       metadata: {
         addonID: loader.id,
         URI: module.uri
       }
     });
   }
   sandboxes[module.uri] = sandbox;
 
   try {
     evaluate(sandbox, module.uri);
-  } catch (error) {
+  }
+  catch (error) {
     let { message, fileName, lineNumber } = error;
     let stack = error.stack || Error().stack;
     let frames = parseStack(stack).filter(isntLoaderFrame);
     let toString = String(error);
     let file = sourceURI(fileName);
 
     // Note that `String(error)` where error is from subscript loader does
     // not puts `:` after `"Error"` unlike regular errors thrown by JS code.
@@ -349,22 +347,29 @@ const load = iced(function load(loader, 
       message: { value: message, writable: true, configurable: true },
       fileName: { value: fileName, writable: true, configurable: true },
       lineNumber: { value: lineNumber, writable: true, configurable: true },
       stack: { value: serializeStack(frames), writable: true, configurable: true },
       toString: { value: function() toString, writable: true, configurable: true },
     });
   }
 
+  if (loader.checkCompatibility) {
+    let err = incompatibility(module);
+    if (err) {
+      throw err;
+    }
+  }
+
   if (module.exports && typeof(module.exports) === 'object')
     freeze(module.exports);
 
   return module;
 });
-exports.load = load;
+Loader.load = load;
 
 // Utility function to normalize module `uri`s so they have `.js` extension.
 function normalizeExt (uri) {
   return isJSURI(uri) ? uri :
          isJSONURI(uri) ? uri :
          isJSMURI(uri) ? uri :
          uri + '.js';
 }
@@ -391,26 +396,26 @@ const resolve = iced(function resolve(id
 
   // Joining and normalizing removes the './' from relative files.
   // We need to ensure the resolution still has the root
   if (isRelative(base))
     resolved = './' + resolved;
 
   return resolved;
 });
-exports.resolve = resolve;
+Loader.resolve = resolve;
 
 // 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);
+  id = Loader.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)))
     return stripBase(rootURI, resolvedPath);
@@ -429,17 +434,17 @@ const nodeResolve = iced(function nodeRe
       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;
+Loader.nodeResolve = nodeResolve;
 
 // Attempts to load `path` and then `path.js`
 // Returns `path` with valid file, or `undefined` otherwise
 function loadAsFile (path) {
   let found;
 
   // As per node's loader spec,
   // we first should try and load 'path' (with no extension)
@@ -526,17 +531,17 @@ const resolveURI = iced(function resolve
 
   while (index < count) {
     let [ path, uri ] = mapping[index ++];
     if (id.indexOf(path) === 0)
       return normalizeExt(id.replace(path, uri));
   }
   return void 0; // otherwise we raise a warning, see bug 910304
 });
-exports.resolveURI = resolveURI;
+Loader.resolveURI = resolveURI;
 
 // Creates version of `require` that will be exposed to the given `module`
 // in the context of the given `loader`. Each module gets own limited copy
 // of `require` that is allowed to load only a modules that are associated
 // with it during link time.
 const Require = iced(function Require(loader, requirer) {
   let {
     modules, mapping, resolve: loaderResolve, load, manifest, rootURI, isNative, requireMap
@@ -640,17 +645,17 @@ const Require = iced(function Require(lo
         });
       }
 
       // If not found in the map, not a node module, and wasn't able to be
       // looked up, it's something
       // found in the paths most likely, like `sdk/tabs`, which should
       // be resolved relatively if needed using traditional resolve
       if (!requirement) {
-        requirement = isRelative(id) ? exports.resolve(id, requirer.id) : id;
+        requirement = isRelative(id) ? Loader.resolve(id, requirer.id) : id;
       }
     } else {
       // Resolve `id` to its requirer if it's relative.
       requirement = requirer ? loaderResolve(id, requirer.id) : id;
     }
 
     // Resolves `uri` of module using loaders resolve function.
     uri = uri || resolveURI(requirement, mapping);
@@ -667,86 +672,87 @@ const Require = iced(function Require(lo
     let { uri } = getRequirements(id);
     return uri;
   }
 
   // Make `require.main === module` evaluate to true in main module scope.
   require.main = loader.main === requirer ? requirer : undefined;
   return iced(require);
 });
-exports.Require = Require;
+Loader.Require = Require;
 
 const main = iced(function main(loader, id) {
   // If no main entry provided, and native loader is used,
   // read the entry in the manifest
   if (!id && loader.isNative)
     id = getManifestMain(loader.manifest);
   let uri = resolveURI(id, loader.mapping);
   let module = loader.main = loader.modules[uri] = Module(id, uri);
   return loader.load(loader, module).exports;
 });
-exports.main = main;
+Loader.main = main;
 
 // Makes module object that is made available to CommonJS modules when they
 // are evaluated, along with `exports` and `require`.
 const Module = iced(function Module(id, uri) {
   return create(null, {
     id: { enumerable: true, value: id },
     exports: { enumerable: true, writable: true, value: create(null) },
     uri: { value: uri }
   });
 });
-exports.Module = Module;
+Loader.Module = Module;
 
 // Takes `loader`, and unload `reason` string and notifies all observers that
 // they should cleanup after them-self.
 const unload = iced(function unload(loader, reason) {
   // subject is a unique object created per loader instance.
   // This allows any code to cleanup on loader unload regardless of how
   // it was loaded. To handle unload for specific loader subject may be
   // asserted against loader.destructor or require('@loader/unload')
   // Note: We don not destroy loader's module cache or sandboxes map as
   // some modules may do cleanup in subsequent turns of event loop. Destroying
   // cache may cause module identity problems in such cases.
   let subject = { wrappedJSObject: loader.destructor };
   notifyObservers(subject, 'sdk:loader:destroy', reason);
 });
-exports.unload = unload;
+Loader.unload = unload;
 
 // Function makes new loader that can be used to load CommonJS modules
 // described by a given `options.manifest`. Loader takes following options:
 // - `globals`: Optional map of globals, that all module scopes will inherit
 //   from. Map is also exposed under `globals` property of the returned loader
 //   so it can be extended further later. Defaults to `{}`.
 // - `modules` Optional map of built-in module exports mapped by module id.
 //   These modules will incorporated into module cache. Each module will be
 //   frozen.
 // - `resolve` Optional module `id` resolution function. If given it will be
 //   used to resolve module URIs, by calling it with require term, requirer
 //   module object (that has `uri` property) and `baseURI` of the loader.
 //   If `resolve` does not returns `uri` string exception will be thrown by
 //   an associated `require` call.
-const Loader = iced(function Loader(options) {
+function Loader(options) {
   let console = new ConsoleAPI({
     consoleID: options.id ? "addon/" + options.id : ""
   });
 
   let {
     modules, globals, resolve, paths, rootURI, manifest, requireMap, isNative,
-    metadata, sharedGlobal, sharedGlobalBlacklist
+    metadata, sharedGlobal, sharedGlobalBlacklist, checkCompatibility
   } = override({
     paths: {},
     modules: {},
     globals: {
       console: console
     },
+    checkCompatibility: false,
     resolve: options.isNative ?
       // Make the returned resolve function have the same signature
-      (id, requirer) => exports.nodeResolve(id, requirer, { rootURI: rootURI }) :
-      exports.resolve,
+      (id, requirer) => Loader.nodeResolve(id, requirer, { rootURI: rootURI }) :
+      Loader.resolve,
     sharedGlobalBlacklist: ["sdk/indexed-db"]
   }, options);
 
   // We create an identity object that will be dispatched on an unload
   // event as subject. This way unload listeners will be able to assert
   // which loader is unloaded. Please note that we intentionally don't
   // use `loader` as subject to prevent a loader access leakage through
   // observer notifications.
@@ -813,16 +819,17 @@ const Loader = iced(function Loader(opti
     sandboxes: { enumerable: false, value: {} },
     resolve: { enumerable: false, value: resolve },
     // ID of the addon, if provided.
     id: { enumerable: false, value: options.id },
     // Whether the modules loaded should be ignored by the debugger
     invisibleToDebugger: { enumerable: false,
                            value: options.invisibleToDebugger || false },
     load: { enumerable: false, value: options.load || load },
+    checkCompatibility: { enumerable: false, value: checkCompatibility },
     // Main (entry point) module, it can be set only once, since loader
     // instance can have only one main module.
     main: new function() {
       let main;
       return {
         enumerable: false,
         get: function() { return main; },
         // Only set main if it has not being set yet!
@@ -834,31 +841,31 @@ const Loader = iced(function Loader(opti
   if (isNative) {
     returnObj.isNative = { enumerable: false, value: true };
     returnObj.manifest = { enumerable: false, value: manifest };
     returnObj.requireMap = { enumerable: false, value: requireMap };
     returnObj.rootURI = { enumerable: false, value: addTrailingSlash(rootURI) };
   }
 
   return freeze(create(null, returnObj));
-});
-exports.Loader = Loader;
+};
+Loader.Loader = Loader;
 
 let isJSONURI = uri => uri.substr(-5) === '.json';
 let isJSMURI = uri => uri.substr(-4) === '.jsm';
 let isJSURI = uri => uri.substr(-3) === '.js';
 let isAbsoluteURI = uri => uri.indexOf("resource://") >= 0 ||
                            uri.indexOf("chrome://") >= 0 ||
                            uri.indexOf("file://") >= 0
 let isRelative = id => id[0] === '.'
 
 const generateMap = iced(function generateMap(options, callback) {
   let { rootURI, resolve, paths } = override({
     paths: {},
-    resolve: exports.nodeResolve
+    resolve: Loader.nodeResolve
   }, options);
 
   rootURI = addTrailingSlash(rootURI);
 
   let manifest;
   let manifestURI = join(rootURI, 'package.json');
 
   if (rootURI)
@@ -870,17 +877,17 @@ const generateMap = iced(function genera
 
   findAllModuleIncludes(main, {
     resolve: resolve,
     manifest: manifest,
     rootURI: rootURI
   }, {}, callback);
 
 });
-exports.generateMap = generateMap;
+Loader.generateMap = generateMap;
 
 // Default `main` entry to './index.js' and ensure is relative,
 // since node allows 'lib/index.js' without relative `./`
 function getManifestMain (manifest) {
   let main = manifest.main || './index.js';
   return isRelative(main) ? main : './' + main;
 }
 
@@ -976,9 +983,10 @@ function isRequire (node) {
   return c
     && node.type === 'CallExpression'
     && c.type === 'Identifier'
     && c.name === 'require'
     && node.arguments.length
    && node.arguments[0].type === 'Literal';
 }
 
+module.exports = iced(Loader);
 });
--- a/addon-sdk/source/lib/toolkit/require.js
+++ b/addon-sdk/source/lib/toolkit/require.js
@@ -7,26 +7,59 @@ const make = (exports, rootURI, componen
     rootURI: rootURI,
     isNative: true,
     paths: {
      "": rootURI,
      "devtools/": "resource://gre/modules/devtools/"
     }
   });
 
+  // Implement require.unload(uri) that can be used to unload
+  // already loaded module which is convinient during development phase.
+  const unload = uri => {
+    delete loader.sandboxes[uri];
+    delete loader.modules[uri];
+  };
+
+  const builtins = new Set(Object.keys(loader.modules));
+
   // Below we define `require` & `require.resolve` that resolve passed
   // module id relative to the caller URI. This is not perfect but good
   // enough for common case & there is always an option to pass absolute
   // id when that
   // but presumably well enough to cover
 
-  const require = id => {
+  const require = (id, options={}) => {
+    const { reload, all } = options;
     const requirerURI = components.stack.caller.filename;
     const requirer = Module(requirerURI, requirerURI);
-    return Require(loader, requirer)(id);
+    const require = Require(loader, requirer);
+    if (reload) {
+      // To load JS code into modules, loader uses `mozIJSSubScriptLoader`
+      // which uses startup cache to avoid reading source from the same URI
+      // more than once. Unless we invalidate statup cache changes to a module
+      // won't be reflected even after reload. Therefor we must dispatch an
+      // nsIObserverService notification that causes cache invalidation.
+      // Note: This is not ideal since it destroys whole cache, but since there
+      // is no way to invalidate individual entries, we assume performance hit
+      // during development is acceptable.
+      components.classes["@mozilla.org/observer-service;1"].
+        getService(components.interfaces.nsIObserverService).
+        notifyObservers({}, "startupcache-invalidate", null);
+
+      if (all) {
+        for (let uri of Object.keys(loader.sandboxes)) {
+          unload(uri);
+        }
+      }
+      else {
+        unload(require.resolve(id));
+      }
+    }
+    return require(id);
   };
 
   require.resolve = id => {
     const requirerURI = components.stack.caller.filename;
     const requirer = Module(requirerURI, requirerURI);
     return Require(loader, requirer).resolve(id);
   };
 
--- a/addon-sdk/source/modules/system/Startup.js
+++ b/addon-sdk/source/modules/system/Startup.js
@@ -2,19 +2,20 @@
  * 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 EXPORTED_SYMBOLS = ["Startup"];
 
 const { utils: Cu, interfaces: Ci, classes: Cc } = Components;
 const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
-const { XulApp } = Cu.import("resource://gre/modules/sdk/system/XulApp.js", {});
 const { defer } = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
 
+const { XulApp } = Cu.import("resource://gre/modules/commonjs/sdk/system/xul-app.jsm", {});
+
 const appStartupSrv = Cc["@mozilla.org/toolkit/app-startup;1"]
                        .getService(Ci.nsIAppStartup);
 
 const NAME2TOPIC = {
   'Firefox': 'sessionstore-windows-restored',
   'Fennec': 'sessionstore-windows-restored',
   'SeaMonkey': 'sessionstore-windows-restored',
   'Thunderbird': 'mail-startup-done'
deleted file mode 100644
--- a/addon-sdk/source/modules/system/XulApp.js
+++ /dev/null
@@ -1,185 +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";
-
-var EXPORTED_SYMBOLS = ["XulApp"];
-
-var { classes: Cc, interfaces: Ci } = Components;
-
-var exports = {};
-var XulApp = exports;
-
-var appInfo = Cc["@mozilla.org/xre/app-info;1"]
-              .getService(Ci.nsIXULAppInfo);
-var vc = Cc["@mozilla.org/xpcom/version-comparator;1"]
-         .getService(Ci.nsIVersionComparator);
-
-var ID = exports.ID = appInfo.ID;
-var name = exports.name = appInfo.name;
-var version = exports.version = appInfo.version;
-var platformVersion = exports.platformVersion = appInfo.platformVersion;
-
-// The following mapping of application names to GUIDs was taken from:
-//
-//   https://addons.mozilla.org/en-US/firefox/pages/appversions
-//
-// Using the GUID instead of the app's name is preferable because sometimes
-// re-branded versions of a product have different names: for instance,
-// Firefox, Minefield, Iceweasel, and Shiretoko all have the same
-// GUID.
-// This mapping is duplicated in `app-extensions/bootstrap.js`. They should keep
-// in sync, so if you change one, change the other too!
-
-var ids = exports.ids = {
-  Firefox: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
-  Mozilla: "{86c18b42-e466-45a9-ae7a-9b95ba6f5640}",
-  Sunbird: "{718e30fb-e89b-41dd-9da7-e25a45638b28}",
-  SeaMonkey: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
-  Fennec: "{aa3c5121-dab2-40e2-81ca-7ea25febc110}",
-  Thunderbird: "{3550f703-e582-4d05-9a08-453d09bdfdc6}"
-};
-
-function is(name) {
-  if (!(name in ids))
-    throw new Error("Unkown Mozilla Application: " + name);
-  return ID == ids[name];
-};
-exports.is = is;
-
-function isOneOf(names) {
-  for (var i = 0; i < names.length; i++)
-    if (is(names[i]))
-      return true;
-  return false;
-};
-exports.isOneOf = isOneOf;
-
-/**
- * Use this to check whether the given version (e.g. xulApp.platformVersion)
- * is in the given range. Versions must be in version comparator-compatible
- * format. See MDC for details:
- * https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIVersionComparator
- */
-var versionInRange = exports.versionInRange =
-function versionInRange(version, lowInclusive, highExclusive) {
-  return (vc.compare(version, lowInclusive) >= 0) &&
-         (vc.compare(version, highExclusive) < 0);
-}
-
-const reVersionRange = /^((?:<|>)?=?)?\s*((?:\d+[\S]*)|\*)(?:\s+((?:<|>)=?)?(\d+[\S]+))?$/;
-const reOnlyInifinity = /^[<>]?=?\s*[*x]$/;
-const reSubInfinity = /\.[*x]/g;
-const reHyphenRange = /^(\d+.*?)\s*-\s*(\d+.*?)$/;
-const reRangeSeparator = /\s*\|\|\s*/;
-
-const compares = {
-  "=": function (c) { return c === 0 },
-  ">=": function (c) { return c >= 0 },
-  "<=": function (c) { return c <= 0},
-  "<": function (c) { return c < 0 },
-  ">": function (c) { return c > 0 }
-}
-
-function normalizeRange(range) {
-    return range
-        .replace(reOnlyInifinity, "")
-        .replace(reSubInfinity, ".*")
-        .replace(reHyphenRange, ">=$1 <=$2")
-}
-
-/**
- * Compare the versions given, using the comparison operator provided.
- * Internal use only.
- *
- * @example
- *  compareVersion("1.2", "<=", "1.*") // true
- *
- * @param {String} version
- *  A version to compare
- *
- * @param {String} comparison
- *  The comparison operator
- *
- * @param {String} compareVersion
- *  A version to compare
- */
-function compareVersion(version, comparison, compareVersion) {
-  let hasWildcard = compareVersion.indexOf("*") !== -1;
-
-  comparison = comparison || "=";
-
-  if (hasWildcard) {
-    switch (comparison) {
-      case "=":
-        let zeroVersion = compareVersion.replace(reSubInfinity, ".0");
-        return versionInRange(version, zeroVersion, compareVersion);
-      case ">=":
-        compareVersion = compareVersion.replace(reSubInfinity, ".0");
-        break;
-    }
-  }
-
-  let compare = compares[comparison];
-
-  return typeof compare === "function" && compare(vc.compare(version, compareVersion));
-}
-
-/**
- * Returns `true` if `version` satisfies the `versionRange` given.
- * If only an argument is passed, is used as `versionRange` and compared against
- * `xulApp.platformVersion`.
- *
- * `versionRange` is either a string which has one or more space-separated
- * descriptors, or a range like "fromVersion - toVersion".
- * Version range descriptors may be any of the following styles:
- *
- * - "version" Must match `version` exactly
- * - "=version" Same as just `version`
- * - ">version" Must be greater than `version`
- * - ">=version" Must be greater or equal than `version`
- * - "<version" Must be less than `version`
- * - "<=version" Must be less or equal than `version`
- * - "1.2.x" or "1.2.*" See 'X version ranges' below
- * - "*" or "" (just an empty string) Matches any version
- * - "version1 - version2" Same as ">=version1 <=version2"
- * - "range1 || range2" Passes if either `range1` or `range2` are satisfied
- *
- * For example, these are all valid:
- * - "1.0.0 - 2.9999.9999"
- * - ">=1.0.2 <2.1.2"
- * - ">1.0.2 <=2.3.4"
- * - "2.0.1"
- * - "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"
- * - "2.x" (equivalent to "2.*")
- * - "1.2.x" (equivalent to "1.2.*" and ">=1.2.0 <1.3.0")
- */
-function satisfiesVersion(version, versionRange) {
-  if (arguments.length === 1) {
-    versionRange = version;
-    version = appInfo.version;
-  }
-
-  let ranges = versionRange.trim().split(reRangeSeparator);
-
-  return ranges.some(function(range) {
-    range = normalizeRange(range);
-
-    // No versions' range specified means that any version satisfies the
-    // requirements.
-    if (range === "")
-      return true;
-
-    let matches = range.match(reVersionRange);
-
-    if (!matches)
-      return false;
-
-    let [, lowMod, lowVer, highMod, highVer] = matches;
-
-    return compareVersion(version, lowMod, lowVer) && (highVer !== undefined
-      ? compareVersion(version, highMod, highVer)
-      : true);
-  });
-}
-exports.satisfiesVersion = satisfiesVersion;
--- a/addon-sdk/source/modules/system/moz.build
+++ b/addon-sdk/source/modules/system/moz.build
@@ -1,10 +1,9 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXTRA_JS_MODULES.sdk.system += [
     'Startup.js',
-    'XulApp.js',
 ]
--- a/addon-sdk/source/package.json
+++ b/addon-sdk/source/package.json
@@ -1,11 +1,36 @@
 {
-    "name": "addon-sdk",
-    "description": "Add-on development made easy.",
-    "keywords": [
-      "javascript", "engine", "addon", "extension",
-      "xulrunner", "firefox", "browser"
-    ],
-    "loader": "lib/sdk/loader/cuddlefish.js",
-    "license": "MPL 2.0",
-    "unpack": true
+  "name": "addon-sdk",
+  "description": "Add-on development made easy.",
+  "keywords": [
+    "javascript", "engine", "addon", "extension",
+    "xulrunner", "firefox", "browser"
+  ],
+  "license": "MPL 2.0",
+  "unpack": true,
+  "scripts": {
+    "test": "node ./bin/jpm-test.js",
+    "modules": "node ./bin/jpm-test.js --type modules",
+    "addons": "node ./bin/jpm-test.js --type addons",
+    "examples": "node ./bin/jpm-test.js --type examples"
+  },
+  "homepage": "https://github.com/mozilla/addon-sdk",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/mozilla/addon-sdk.git"
+  },
+  "version": "0.1.18",
+  "main": "./lib/index.js",
+  "loader": "lib/sdk/loader/cuddlefish.js",
+  "devDependencies": {
+    "async": "0.2.10",
+    "chai": "1.9.2",
+    "glob": "4.0.6",
+    "jpm": "0.0.21",
+    "lodash": "2.4.1",
+    "mocha": "1.21.5",
+    "promise": "6.0.1",
+    "rimraf": "2.2.8",
+    "unzip": "0.1.9",
+    "xmldom": "0.1.19"
+  }
 }
--- a/addon-sdk/source/python-lib/cuddlefish/manifest.py
+++ b/addon-sdk/source/python-lib/cuddlefish/manifest.py
@@ -412,16 +412,19 @@ class ManifestBuilder:
                 looked_in = [] # populated by subroutines
                 them_me = self.find_req_for(mi, reqname, looked_in, locations)
                 if them_me is None:
                     if mi.section == "tests":
                         # tolerate missing modules in tests, because
                         # test-securable-module.js, and the modules/red.js
                         # that it imports, both do that intentionally
                         continue
+                    if reqname.endswith(".jsm"):
+                        # ignore JSM modules
+                        continue
                     if not self.abort_on_missing:
                         # print a warning, but tolerate missing modules
                         # unless cfx --abort-on-missing-module flag was set
                         print >>self.stderr, "Warning: missing module: %s" % reqname
                         me.add_requirement(reqname, reqname)
                         continue
                     lineno = locations.get(reqname) # None means define()
                     if lineno is None:
@@ -797,9 +800,8 @@ if __name__ == '__main__':
         requires, problems, locations = scan_module(fn, open(fn).readlines())
         print
         print "---", fn
         if problems:
             print "PROBLEMS"
             sys.exit(1)
         print "requires: %s" % (",".join(sorted(requires.keys())))
         print "locations: %s" % locations
-
--- a/addon-sdk/source/python-lib/cuddlefish/packaging.py
+++ b/addon-sdk/source/python-lib/cuddlefish/packaging.py
@@ -79,17 +79,17 @@ def validate_resource_hostname(name):
       Traceback (most recent call last):
       ...
       ValueError: Error: the name of your package contains an invalid character.
       Package names can contain only lower-case letters, numbers, underscores, and dashes.
       Current package name: foo@bar
     """
 
     # See https://bugzilla.mozilla.org/show_bug.cgi?id=568131 for details.
-    if not name.islower():
+    if not name.lower() == name:
         raise ValueError("""Error: the name of your package contains upper-case letters.
 Package names can contain only lower-case letters, numbers, underscores, and dashes.
 Current package name: %s""" % name)
 
     if not RESOURCE_HOSTNAME_RE.match(name):
         raise ValueError("""Error: the name of your package contains an invalid character.
 Package names can contain only lower-case letters, numbers, underscores, and dashes.
 Current package name: %s""" % name)
--- a/addon-sdk/source/python-lib/cuddlefish/prefs.py
+++ b/addon-sdk/source/python-lib/cuddlefish/prefs.py
@@ -31,58 +31,90 @@ DEFAULT_COMMON_PREFS = {
     'extensions.getAddons.cache.enabled' : False,
     # Disable intalling any distribution add-ons
     'extensions.installDistroAddons' : False,
     # Allow installing extensions dropped into the profile folder
     'extensions.autoDisableScopes' : 10,
 
     # shut up some warnings on `about:` page
     'app.releaseNotesURL': 'http://localhost/app-dummy/',
-    'app.vendorURL': 'http://localhost/app-dummy/'
+    'app.vendorURL': 'http://localhost/app-dummy/',
+
+    # Don't prompt about e10s
+    'browser.displayedE10SPrompt.1': 5
 }
 
 DEFAULT_NO_CONNECTIONS_PREFS = {
     'toolkit.telemetry.enabled': False,
+    'toolkit.telemetry.server': 'https://localhost/telemetry-dummy/',
     'app.update.auto' : False,
     'app.update.url': 'http://localhost/app-dummy/update',
+    # Make sure GMPInstallManager won't hit the network.
     'media.gmp-gmpopenh264.autoupdate' : False,
     'media.gmp-manager.cert.checkAttributes' : False,
     'media.gmp-manager.cert.requireBuiltIn' : False,
     'media.gmp-manager.url' : 'http://localhost/media-dummy/gmpmanager',
+    'media.gmp-manager.url.override': 'http://localhost/dummy-gmp-manager.xml',
     'browser.newtab.url' : 'about:blank',
     'browser.search.update': False,
     'browser.safebrowsing.enabled' : False,
     'browser.safebrowsing.updateURL': 'http://localhost/safebrowsing-dummy/update',
     'browser.safebrowsing.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
     'browser.safebrowsing.reportURL': 'http://localhost/safebrowsing-dummy/report',
     'browser.safebrowsing.malware.reportURL': 'http://localhost/safebrowsing-dummy/malwarereport',
+    'browser.trackingprotection.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
+    'browser.trackingprotection.updateURL': 'http://localhost/safebrowsing-dummy/update',
 
     # Disable app update
     'app.update.enabled' : False,
+    'app.update.staging.enabled': False,
 
     # Disable about:newtab content fetch and ping
     'browser.newtabpage.directory.source': 'data:application/json,{"jetpack":1}',
     'browser.newtabpage.directory.ping': '',
 
     # Point update checks to a nonexistent local URL for fast failures.
     'extensions.update.url' : 'http://localhost/extensions-dummy/updateURL',
+    'extensions.update.background.url': 'http://localhost/extensions-dummy/updateBackgroundURL',
     'extensions.blocklist.url' : 'http://localhost/extensions-dummy/blocklistURL',
     # Make sure opening about:addons won't hit the network.
-    'extensions.webservice.discoverURL' : 'http://localhost/extensions-dummy/discoveryURL'
+    'extensions.webservice.discoverURL' : 'http://localhost/extensions-dummy/discoveryURL',
+    'extensions.getAddons.maxResults': 0,
+
+    # Disable webapp updates.  Yes, it is supposed to be an integer.
+    'browser.webapps.checkForUpdates': 0,
+
+    # Location services
+    'geo.wifi.uri': 'http://localhost/location-dummy/locationURL',
+    'browser.search.geoip.url': 'http://localhost/location-dummy/locationURL',
+
+    # Tell the search service we are running in the US.  This also has the
+    # desired side-effect of preventing our geoip lookup.
+    'browser.search.isUS' : True,
+    'browser.search.countryCode' : 'US',
+
+    'geo.wifi.uri' : 'http://localhost/extensions-dummy/geowifiURL',
+    'geo.wifi.scan' : False,
+
+    # We don't want to hit the real Firefox Accounts server for tests.  We don't
+    # actually need a functioning FxA server, so just set it to something that
+    # resolves and accepts requests, even if they all fail.
+    'identity.fxaccounts.auth.uri': 'http://localhost/fxa-dummy/'
 }
 
 DEFAULT_FENNEC_PREFS = {
   'browser.console.showInPanel': True,
   'browser.firstrun.show.uidiscovery': False
 }
 
 # When launching a temporary new Firefox profile, use these preferences.
 DEFAULT_FIREFOX_PREFS = {
     'browser.startup.homepage' : 'about:blank',
     'startup.homepage_welcome_url' : 'about:blank',
+    'devtools.browsertoolbox.panel': 'jsdebugger',
     'devtools.errorconsole.enabled' : True,
     'devtools.chrome.enabled' : True,
 
     # 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.
@@ -140,11 +172,74 @@ DEFAULT_THUNDERBIRD_PREFS = {
     '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 = {
+    'browser.console.showInPanel': True,
+    'browser.startup.page': 0,
+    'browser.firstrun.show.localepicker': False,
+    'browser.firstrun.show.uidiscovery': False,
+    'browser.ui.layout.tablet': 0,
+    'dom.disable_open_during_load': False,
+    'dom.experimental_forms': True,
+    'dom.forms.number': True,
+    'dom.forms.color': True,
+    'dom.max_script_run_time': 0,
+    'hangmonitor.timeout': 0,
+    'dom.max_chrome_script_run_time': 0,
+    'dom.popup_maximum': -1,
+    'dom.send_after_paint_to_content': True,
+    'dom.successive_dialog_time_limit': 0,
+    'browser.shell.checkDefaultBrowser': False,
+    'shell.checkDefaultClient': False,
+    'browser.warnOnQuit': False,
+    'accessibility.typeaheadfind.autostart': False,
+    'browser.EULA.override': True,
+    'gfx.color_management.force_srgb': True,
+    'network.manage-offline-status': False,
+    # Disable speculative connections so they aren't reported as leaking when they're hanging around.
+    'network.http.speculative-parallel-limit': 0,
+    'test.mousescroll': True,
+    # Need to client auth test be w/o any dialogs
+    'security.default_personal_cert': 'Select Automatically',
+    'network.http.prompt-temp-redirect': False,
+    'security.warn_viewing_mixed': False,
+    'browser.panorama.experienced_first_run': True,
+    # Set a future policy version to avoid the telemetry prompt.
+    'toolkit.telemetry.prompted': 999,
+    'toolkit.telemetry.notifiedOptOut': 999,
+    'extensions.defaultProviders.enabled': True,
+    'datareporting.policy.dataSubmissionPolicyBypassNotification': True,
+    'layout.css.report_errors': True,
+    'layout.css.grid.enabled': True,
+    'layout.spammy_warnings.enabled': False,
+    'dom.mozSettings.enabled': True,
+    # Make sure the disk cache doesn't get auto disabled
+    'network.http.bypass-cachelock-threshold': 200000,
+    # Always use network provider for geolocation tests
+    # so we bypass the OSX dialog raised by the corelocation provider
+    'geo.provider.testing': True,
+    # Background thumbnails in particular cause grief, and disabling thumbnails
+    # in general can't hurt - we re-enable them when tests need them.
+    'browser.pagethumbnails.capturing_disabled': True,
+    # Indicate that the download panel has been shown once so that whichever
+    # download test runs first doesn't show the popup inconsistently.
+    'browser.download.panel.shown': True,
+    # Assume the about:newtab page's intro panels have been shown to not depend on
+    # which test runs first and happens to open about:newtab
+    'browser.newtabpage.introShown': True,
+    # Disable useragent updates.
+    'general.useragent.updates.enabled': False,
+    'dom.mozApps.debug': True,
+    'dom.apps.customization.enabled': True,
+    'media.eme.enabled': True,
+    # Don't forceably kill content processes after a timeout
+    'dom.ipc.tabs.shutdownTimeoutSecs': 0,
+    # Don't show the search first run UI by default
+    'browser.search.highlightCount': 0,
     'general.useragent.locale': "en-US",
-    'intl.locale.matchOS': "en-US"
+    'intl.locale.matchOS': "en-US",
+    'dom.indexedDB.experimental': True
 }
--- a/addon-sdk/source/python-lib/cuddlefish/runner.py
+++ b/addon-sdk/source/python-lib/cuddlefish/runner.py
@@ -413,16 +413,20 @@ def run_app(harness_root_dir, manifest_r
     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
+    else:
+        preferences['browser.tabs.remote.autostart'] = False
+        preferences['browser.tabs.remote.autostart.1'] = False
+        preferences['browser.tabs.remote.autostart.2'] = False
 
     # For now, only allow running on Mobile with --force-mobile argument
     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"""
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/addon-manager/lib/main.js
@@ -0,0 +1,8 @@
+/* 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.exports = require("./test-main.js");
+
+require('sdk/test/runner').runTestsFromModule(module);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/addon-manager/lib/test-main.js
@@ -0,0 +1,12 @@
+/* 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");
+}
deleted file mode 100644
--- a/addon-sdk/source/test/addons/addon-manager/main.js
+++ /dev/null
@@ -1,14 +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 { 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/runner').runTestsFromModule(module);
--- a/addon-sdk/source/test/addons/addon-manager/package.json
+++ b/addon-sdk/source/test/addons/addon-manager/package.json
@@ -1,3 +1,7 @@
 {
-  "id": "test-addon-manager"
+  "id": "test-addon-manager@jetpack",
+  "main": "./lib/main.js",
+  "name": "test-addon-manager",
+  "version": "0.0.1",
+  "author": "Erik Vold"
 }
--- a/addon-sdk/source/test/addons/author-email/package.json
+++ b/addon-sdk/source/test/addons/author-email/package.json
@@ -1,4 +1,6 @@
 {
   "id": "test-addon-author-email@jetpack",
-  "author": "test <test@mozilla.com>"
+  "author": "test <test@mozilla.com>",
+  "version": "0.0.1",
+  "main": "./main.js"
 }
--- a/addon-sdk/source/test/addons/child_process/package.json
+++ b/addon-sdk/source/test/addons/child_process/package.json
@@ -1,4 +1,5 @@
 {
-  "id": "test-child-process",
-  "main": "index.js"
+  "id": "test-child-process@jetpack",
+  "main": "./index.js",
+  "version": "0.0.1"
 }
--- a/addon-sdk/source/test/addons/chrome/main.js
+++ b/addon-sdk/source/test/addons/chrome/main.js
@@ -4,17 +4,16 @@
 '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,22 +72,18 @@ exports.testChromeInPanel = function(ass
     contentURL: 'chrome://test/content/panel.html',
     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();
-      });
-      setTimeout(() => panel.hide());
+      panel.destroy();
+      assert.pass('panel is destroyed');
+      done();
     });
     panel.port.emit('echo');
   });
   panel.show();
 }
 
 require('sdk/test/runner').runTestsFromModule(module);
--- a/addon-sdk/source/test/addons/chrome/package.json
+++ b/addon-sdk/source/test/addons/chrome/package.json
@@ -1,3 +1,5 @@
 {
-  "id": "test-chrome"
+  "id": "test-chrome@jetpack",
+  "main": "./main.js",
+  "version": "0.0.1"
 }
--- a/addon-sdk/source/test/addons/content-permissions/package.json
+++ b/addon-sdk/source/test/addons/content-permissions/package.json
@@ -1,6 +1,8 @@
 {
-  "id": "content-permissions",
+  "id": "content-permissions@jetpack",
   "permissions": {
     "cross-domain-content": ["http://localhost:8099"]
-  }
+  },
+  "main": "./main.js",
+  "version": "0.0.1"
 }
--- a/addon-sdk/source/test/addons/contributors/package.json
+++ b/addon-sdk/source/test/addons/contributors/package.json
@@ -1,4 +1,6 @@
 {
-  "id": "test-contributors",
-  "contributors": [ "A", "B" ]
+  "id": "test-contributors@jetpack",
+  "contributors": [ "A", "B" ],
+  "main": "./main.js",
+  "version": "0.0.1"
 }
--- a/addon-sdk/source/test/addons/curly-id/package.json
+++ b/addon-sdk/source/test/addons/curly-id/package.json
@@ -1,12 +1,13 @@
 {
     "id": "{34a1eae1-c20a-464f-9b0e-000000000000}",
     "fullName": "curly ID test",
     "author": "Tomislav Jovanovic",
-
     "preferences": [{
         "name": "test13",
         "type": "integer",
         "title": "test13",
         "value": 26
-    }]
+    }],
+    "main": "./lib/main.js",
+    "version": "0.0.1"
 }
--- a/addon-sdk/source/test/addons/developers/package.json
+++ b/addon-sdk/source/test/addons/developers/package.json
@@ -1,6 +1,8 @@
 {
   "id": "test-developers@jetpack",
   "title": "Test developers package key",
   "author": "Erik Vold",
-  "developers": [ "A", "B" ]
+  "developers": [ "A", "B" ],
+  "main": "./main.js",
+  "version": "0.0.1"
 }
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/e10s-content/data/test-contentScriptFile.js
@@ -0,0 +1,5 @@
+/* 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/. */
+
+self.postMessage("msg from contentScriptFile");
rename from addon-sdk/source/test/fixtures/test-page-worker.html
rename to addon-sdk/source/test/addons/e10s-content/data/test-page-worker.html
rename from addon-sdk/source/test/fixtures/test-page-worker.js
rename to addon-sdk/sou