Bug 1114752 - Uplift Add-on SDK to Firefox a=me
authorErik Vold <evold@mozilla.com>
Tue, 03 Feb 2015 09:51:16 -0800
changeset 227230 4171b3354b53ec1869417dd8714d7065b05db07b
parent 227229 9c18e56a7b50990d651257c3d8462ddfa2a65266
child 227231 2098158a0374770665ff8befc891dda7937cfb42
push id11127
push userevold@mozilla.com
push dateTue, 03 Feb 2015 17:52:40 +0000
treeherderfx-team@4171b3354b53 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersme
bugs1114752
milestone38.0a1
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/dev/volcan.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-mod/match-pattern.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/memory.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/__init__.py
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/rdf.py
addon-sdk/source/python-lib/cuddlefish/runner.py
addon-sdk/source/python-lib/cuddlefish/tests/bug-661083-files/packages/noLocalization/locale/en-GB.properties
addon-sdk/source/python-lib/cuddlefish/tests/bug-661083-files/packages/noLocalization/locale/en-US.properties
addon-sdk/source/python-lib/cuddlefish/tests/bug-661083-files/packages/noLocalization/package.json
addon-sdk/source/python-lib/cuddlefish/tests/bug-661083-files/packages/twoLanguages/locale/en-GB.properties
addon-sdk/source/python-lib/cuddlefish/tests/bug-661083-files/packages/twoLanguages/locale/en-US.properties
addon-sdk/source/python-lib/cuddlefish/tests/bug-661083-files/packages/twoLanguages/package.json
addon-sdk/source/python-lib/cuddlefish/tests/test_licenses.py
addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py
addon-sdk/source/python-lib/cuddlefish/tests/test_rdf.py
addon-sdk/source/test/addons/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/chrome/skin/style.css
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-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/data/index.html
addon-sdk/source/test/addons/page-mod-debugger-post/data/script.js
addon-sdk/source/test/addons/page-mod-debugger-post/package.json
addon-sdk/source/test/addons/page-mod-debugger-pre/data/index.html
addon-sdk/source/test/addons/page-mod-debugger-pre/data/script.js
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-panel.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-regression/app-extension/defaults/preferences/prefs.js
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/main.js
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/border-style.css
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/border-style.css
addon-sdk/source/test/fixtures/include-file.css
addon-sdk/source/test/fixtures/loader/json/nodotjson.json.js
addon-sdk/source/test/fixtures/loader/json/test.json.js
addon-sdk/source/test/fixtures/native-addon-test/dir/a.js
addon-sdk/source/test/fixtures/native-addon-test/dir/a/index.js
addon-sdk/source/test/fixtures/native-addon-test/dir/b.js
addon-sdk/source/test/fixtures/native-addon-test/dir/c.js
addon-sdk/source/test/fixtures/native-addon-test/dir/dummy.js
addon-sdk/source/test/fixtures/native-addon-test/dir/test.jsm
addon-sdk/source/test/fixtures/native-addon-test/index.js
addon-sdk/source/test/fixtures/native-addon-test/newmodule/index.js
addon-sdk/source/test/fixtures/native-addon-test/newmodule/lib/file.js
addon-sdk/source/test/fixtures/native-addon-test/node_modules/test-assets/index.js
addon-sdk/source/test/fixtures/native-addon-test/node_modules/test-assets/styles.css
addon-sdk/source/test/fixtures/native-addon-test/node_modules/test-custom-main-relative/lib/custom-entry.js
addon-sdk/source/test/fixtures/native-addon-test/node_modules/test-custom-main/lib/custom-entry.js
addon-sdk/source/test/fixtures/native-addon-test/node_modules/test-default-main/index.js
addon-sdk/source/test/fixtures/native-addon-test/node_modules/test-math/index.js
addon-sdk/source/test/fixtures/native-addon-test/node_modules/test-math/lib/sqrt.js
addon-sdk/source/test/fixtures/native-addon-test/node_modules/test-math/node_modules/test-add/index.js
addon-sdk/source/test/fixtures/native-addon-test/node_modules/test-math/node_modules/test-subtract/index.js
addon-sdk/source/test/fixtures/native-addon-test/utils/index.js
addon-sdk/source/test/fixtures/preferences/simple-prefs/package.json
addon-sdk/source/test/fixtures/test-iframe-postmessage.html
addon-sdk/source/test/fixtures/test-iframe.html
addon-sdk/source/test/fixtures/test-iframe.js
addon-sdk/source/test/fixtures/test-page-worker.html
addon-sdk/source/test/fixtures/test-page-worker.js
addon-sdk/source/test/fixtures/test-sidebar-addon-global.html
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/loader/b2g.js
addon-sdk/source/test/pagemod-test-helpers.js
addon-sdk/source/test/path/test-path.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/private-browsing/tabs.js
addon-sdk/source/test/querystring/test-querystring.js
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-utils.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',
 ]
 
 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
@@ -9,10 +9,9 @@ JETPACK_PACKAGE_MANIFESTS += ['source/te
 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;
--- a/addon-sdk/source/lib/dev/volcan.js
+++ b/addon-sdk/source/lib/dev/volcan.js
@@ -1,8 +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/. */
 !function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.volcan=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
 "use strict";
 
 var Client = _dereq_("../client").Client;
 
 function connect(port) {
   var client = new Client();
   return client.connect(port);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/framescript/context-menu.js
@@ -0,0 +1,215 @@
+/* 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 { 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,147 @@
+/* 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/. */
+
+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,384 @@
+/* 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 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,112 @@
+/* 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/. */
+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,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const 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-mod/match-pattern.js
+++ b/addon-sdk/source/lib/sdk/page-mod/match-pattern.js
@@ -1,5 +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";
+
 let { deprecateUsage } = require("../util/deprecate");
 
 deprecateUsage("Module 'sdk/page-mod/match-pattern' is deprecated use 'sdk/util/match-pattern' instead");
 
 module.exports = require("../util/match-pattern");
--- 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,126 +16,88 @@ 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);
 
-function Branch(branchName) {
-  function getPrefKeys() {
-    return keys(branchName).map(function(key) {
-      return key.replace(branchName, "");
-    });
-  }
+const { Preferences } = require("resource://gre/modules/Preferences.jsm");
+const prefs = new Preferences({});
+
+const branchKeys = branchName =>
+  keys(branchName).map($ => $.replace(branchName, ""));
 
-  return Proxy.create({
-    get: function(receiver, pref) {
-      return get(branchName + pref);
-    },
-    set: function(receiver, pref, val) {
-      set(branchName + pref, val);
-    },
-    delete: function(pref) {
-      reset(branchName + pref);
-      return true;
-    },
-    has: function hasPrefKey(pref) {
-      return has(branchName + pref)
-    },
-    getPropertyDescriptor: function(name) {
+const Branch = function(branchName) {
+  return new Proxy(Branch.prototype, {
+    getOwnPropertyDescriptor(target, name, receiver) {
       return {
-        value: get(branchName + name)
+        configurable: true,
+        enumerable: true,
+        writable: false,
+        value: this.get(target, name, receiver)
       };
     },
-    enumerate: getPrefKeys,
-    keys: getPrefKeys
-  }, Branch.prototype);
+    enumerate(target) {
+      return branchKeys(branchName)[Symbol.iterator]();
+    },
+    ownKeys(target) {
+      return branchKeys(branchName);
+    },
+    get(target, name, receiver) {
+      return get(`${branchName}${name}`);
+    },
+    set(target, name, value, receiver) {
+      set(`${branchName}${name}`, value);
+    },
+    has(target, name) {
+      return this.hasOwn(target, name);
+    },
+    hasOwn(target, name) {
+      return has(`${branchName}${name}`);
+    },
+    deleteProperty(target, name) {
+      reset(`${branchName}${name}`);
+      return true;
+    }
+  });
 }
 
+
 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";
+
+this.EXPORTED_SYMBOLS = [ "XulApp" ];
+
+var { classes: Cc, interfaces: Ci } = Components;
+
+var exports = {};
+this.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
@@ -53,21 +53,17 @@ var profileMemory;
 
 // Whether we should stop as soon as a test reports a failure.
 var stopOnError;
 
 // Function to call to retrieve a list of tests to execute
 var findAndRunTests;
 
 // Combined information from all test runs.
-var results = {
-  passed: 0,
-  failed: 0,
-  testRuns: []
-};
+var results;
 
 // A list of the compartments and windows loaded after startup
 var startLeaks;
 
 // JSON serialization of last memory usage stats; we keep it stringified
 // so we don't actually change the memory usage stats (in terms of objects)
 // of the JSRuntime we're profiling.
 var lastMemoryUsage;
@@ -433,17 +429,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;
@@ -585,16 +582,22 @@ TestRunnerTinderboxConsole.prototype = {
 var runTests = exports.runTests = function runTests(options) {
   iterationsLeft = options.iterations;
   profileMemory = options.profileMemory;
   stopOnError = options.stopOnError;
   onDone = options.onDone;
   print = options.print;
   findAndRunTests = options.findAndRunTests;
 
+  results = {
+    passed: 0,
+    failed: 0,
+    testRuns: []
+  };
+
   try {
     consoleListener.register();
     print("Running tests on " + system.name + " " + system.version +
           "/Gecko " + system.platformVersion + " (" +
           system.id + ") under " +
           system.platform + "/" + system.architecture + ".\n");
 
     if (options.parseable)
--- a/addon-sdk/source/lib/sdk/test/memory.js
+++ b/addon-sdk/source/lib/sdk/test/memory.js
@@ -1,8 +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';
 
 const { Cu } = require("chrome");
 const memory = require('../deprecated/memory');
 const { defer } = require('../core/promise');
 
 function gc() {
   let { promise, resolve } = defer();
--- 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,182 @@
+/* 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";
+
+// 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
@@ -20,18 +20,19 @@ const WM = Cc['@mozilla.org/appshell/win
            getService(Ci.nsIWindowMediator);
 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 prefs = require("../preferences/service");
 const BROWSER = 'navigator:browser',
-      URI_BROWSER = 'chrome://browser/content/browser.xul',
+      URI_BROWSER = prefs.get('browser.chromeURL', null),
       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,
@@ -181,16 +182,19 @@ function serializeFeatures(options) {
  *    Optional name that is assigned to the window.
  * @params {Object} options.features
  *    Map of key, values like: `{ width: 10, height: 15, chrome: true, private: true }`.
  */
 function open(uri, options) {
   uri = uri || URI_BROWSER;
   options = options || {};
 
+  if (!uri)
+    throw new Error('browser.chromeURL is undefined, please provide an explicit uri');
+
   if (['chrome', 'resource', 'data'].indexOf(io.newURI(uri, null, null).scheme) < 0)
     throw new Error('only chrome, resource and data uris are allowed');
 
   let newWindow = windowWatcher.
     openWindow(options.parent || null,
                uri,
                options.name || null,
                options.features ? serializeFeatures(options.features) : null,
--- 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,53 +1,50 @@
 /* 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 });
+;((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,
         results: Cr, manager: Cm } = Components;
 const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
 const { loadSubScript } = Cc['@mozilla.org/moz/jssubscript-loader;1'].
                      getService(Ci.mozIJSSubScriptLoader);
 const { notifyObservers } = Cc['@mozilla.org/observer-service;1'].
                         getService(Ci.nsIObserverService);
+const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
 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");
 
+XPCOMUtils.defineLazyGetter(this, "XulApp", () => {
+  let xulappURI = module.uri.replace("toolkit/loader.js",
+                                       "sdk/system/xul-app.jsm");
+  return Cu.import(xulappURI, {});
+});
+
 // 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 +82,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 +119,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 +150,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.newChannel2(uri,
                                    'UTF-8',
                                    null,
                                    null,      // aLoadingNode
                                    systemPrincipal,
                                    null,      // aTriggeringPrincipal
@@ -196,17 +193,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
@@ -248,17 +245,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'.
@@ -268,17 +265,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
@@ -301,34 +298,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.
@@ -356,22 +355,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 = XulApp.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';
 }
@@ -398,26 +404,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);
@@ -436,17 +442,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)
@@ -533,17 +539,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
@@ -647,17 +653,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);
@@ -674,86 +680,91 @@ 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) {
-  let console = new ConsoleAPI({
-    consoleID: options.id ? "addon/" + options.id : ""
-  });
-
+function Loader(options) {
   let {
     modules, globals, resolve, paths, rootURI, manifest, requireMap, isNative,
-    metadata, sharedGlobal, sharedGlobalBlacklist
+    metadata, sharedGlobal, sharedGlobalBlacklist, checkCompatibility
   } = override({
     paths: {},
     modules: {},
     globals: {
-      console: console
+      get console() {
+        // Import Console.jsm from here to prevent loading it until someone uses it
+        let { ConsoleAPI } = Cu.import("resource://gre/modules/devtools/Console.jsm");
+        let console = new ConsoleAPI({
+          consoleID: options.id ? "addon/" + options.id : ""
+        });
+        Object.defineProperty(this, "console", { value: console });
+        return this.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.
@@ -820,16 +831,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!
@@ -841,31 +853,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)
@@ -877,17 +889,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;
 }
 
@@ -945,16 +957,18 @@ function findModuleIncludes (uri, callba
     if (isRequire(node))
       modules.push(node.arguments[0].value);
   });
 
   callback(modules);
 }
 
 function walk (src, callback) {
+  // Import Reflect.jsm from here to prevent loading it until someone uses it
+  let { Reflect } = Cu.import("resource://gre/modules/reflect.jsm", {});
   let nodes = Reflect.parse(src);
   traverse(nodes, callback);
 }
 
 function traverse (node, cb) {
   if (Array.isArray(node)) {
     node.map(x => {
       if (x != null) {
@@ -983,9 +997,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
@@ -1,32 +1,69 @@
+/* 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/. */
+
 const make = (exports, rootURI, components) => {
   const { Loader: { Loader, Require, Module, main } } =
         components.utils.import(rootURI + "toolkit/loader.js", {});
 
   const loader = Loader({
     id: "toolkit/require",
     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;