aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.12/lib/sdk
diff options
context:
space:
mode:
Diffstat (limited to 'tools/addon-sdk-1.12/lib/sdk')
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/addon-page.js38
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/addon/installer.js106
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/addon/runner.js153
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/base64.js41
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/clipboard.js333
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/console/plain-text.js88
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/console/traceback.js118
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/content/content-proxy.js870
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/content/content-worker.js308
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/content/content.js15
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/content/loader.js204
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/content/symbiont.js197
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/content/thumbnail.js46
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/content/worker.js582
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/context-menu.js1494
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/core/heritage.js146
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/core/namespace.js43
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/core/promise.js230
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/deprecated/api-utils.js158
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/deprecated/app-strings.js67
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/deprecated/cortex.js113
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/deprecated/errors.js64
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/deprecated/events.js182
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/deprecated/events/assembler.js53
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/deprecated/light-traits.js600
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/deprecated/list.js118
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/deprecated/memory.js118
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/deprecated/observer-service.js134
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/deprecated/tab-browser.js731
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/deprecated/traits.js187
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/deprecated/traits/core.js322
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/deprecated/unit-test-finder.js71
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/deprecated/unit-test.js451
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/deprecated/window-utils.js201
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/dom/events.js140
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/dom/events/keys.js64
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/event/core.js152
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/event/target.js80
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/frame/hidden-frame.js181
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/frame/utils.js63
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/hotkeys.js41
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/io/byte-streams.js106
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/io/data.js84
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/io/file.js196
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/io/text-streams.js244
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/keyboard/hotkeys.js111
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/keyboard/observer.js58
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/keyboard/utils.js190
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/l10n.js83
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/l10n/core.js35
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/l10n/html.js93
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/l10n/loader.js71
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/l10n/locale.js126
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/l10n/plural-rules.js403
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/l10n/prefs.js44
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/lang/functional.js163
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/lang/type.js344
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/loader/cuddlefish.js78
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/loader/sandbox.js50
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/net/url.js108
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/net/xhr.js154
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/notifications.js83
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/page-mod.js380
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/page-mod/match-pattern.js108
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/page-worker.js71
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/panel.js403
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/passwords.js62
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/passwords/utils.js105
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/platform/xpcom.js229
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/preferences/event-target.js60
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/preferences/service.js174
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/private-browsing.js36
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/private-browsing/utils.js74
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/querystring.js121
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/request.js213
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/selection.js481
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/self.js36
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/simple-prefs.js32
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/simple-storage.js237
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/system.js155
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/system/environment.js58
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/system/events.js113
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/system/globals.js54
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/system/runtime.js19
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/system/unload.js82
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/system/xul-app.js67
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/tabs.js10
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/tabs/common.js23
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/tabs/events.js30
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/tabs/helpers.js17
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/tabs/namespace.js9
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/tabs/observer.js99
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/tabs/tab-fennec.js141
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/tabs/tab-firefox.js201
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/tabs/tab.js21
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/tabs/tabs-firefox.js26
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/tabs/tabs.js21
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/tabs/utils.js222
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/tabs/worker.js17
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/test.js112
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/test/assert.js349
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/test/harness.js327
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/test/httpd.js5174
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/test/loader.js28
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/test/runner.js150
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/test/tmp-file.js73
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/timers.js53
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/url.js236
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/util/array.js89
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/util/collection.js112
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/util/deprecate.js27
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/util/list.js78
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/util/object.js57
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/util/registry.js61
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/util/uuid.js17
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/widget.js938
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/window/browser.js41
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/window/namespace.js6
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/window/utils.js205
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/windows.js21
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/windows/dom.js33
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/windows/fennec.js84
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/windows/firefox.js241
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/windows/loader.js120
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/windows/observer.js52
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/windows/tabs-fennec.js190
-rw-r--r--tools/addon-sdk-1.12/lib/sdk/windows/tabs-firefox.js188
127 files changed, 25396 insertions, 0 deletions
diff --git a/tools/addon-sdk-1.12/lib/sdk/addon-page.js b/tools/addon-sdk-1.12/lib/sdk/addon-page.js
new file mode 100644
index 0000000..6440b25
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/addon-page.js
@@ -0,0 +1,38 @@
+/* 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 { WindowTracker } = require('./deprecated/window-utils');
+const { isXULBrowser } = require('./window/utils');
+const { add, remove } = require('./util/array');
+const { getTabs, closeTab, getURI } = require('./tabs/utils');
+const { data } = require('./self');
+
+const addonURL = data.url('index.html');
+
+WindowTracker({
+ onTrack: function onTrack(window) {
+ if (isXULBrowser(window))
+ add(window.XULBrowserWindow.inContentWhitelist, addonURL);
+ },
+ onUntrack: function onUntrack(window) {
+ if (isXULBrowser(window))
+ getTabs(window).filter(tabFilter).forEach(untrackTab.bind(null, window));
+ }
+});
+
+function tabFilter(tab) {
+ return getURI(tab) === addonURL;
+}
+
+function untrackTab(window, tab) {
+ // Note: `onUntrack` will be called for all windows on add-on unloads,
+ // so we want to clean them up from these URLs.
+ remove(window.XULBrowserWindow.inContentWhitelist, addonURL);
+ closeTab(tab);
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/addon/installer.js b/tools/addon-sdk-1.12/lib/sdk/addon/installer.js
new file mode 100644
index 0000000..f72b21e
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/addon/installer.js
@@ -0,0 +1,106 @@
+/* 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/. */
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Cc, Ci, Cu } = require("chrome");
+const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm");
+const { defer } = require("../core/promise");
+const { setTimeout } = require("../timers");
+
+/**
+ * `install` method error codes:
+ *
+ * https://developer.mozilla.org/en/Addons/Add-on_Manager/AddonManager#AddonInstall_errors
+ */
+exports.ERROR_NETWORK_FAILURE = AddonManager.ERROR_NETWORK_FAILURE;
+exports.ERROR_INCORRECT_HASH = AddonManager.ERROR_INCORRECT_HASH;
+exports.ERROR_CORRUPT_FILE = AddonManager.ERROR_CORRUPT_FILE;
+exports.ERROR_FILE_ACCESS = AddonManager.ERROR_FILE_ACCESS;
+
+/**
+ * Immediatly install an addon.
+ *
+ * @param {String} xpiPath
+ * file path to an xpi file to install
+ * @return {Promise}
+ * A promise resolved when the addon is finally installed.
+ * Resolved with addon id as value or rejected with an error code.
+ */
+exports.install = function install(xpiPath) {
+ let { promise, resolve, reject } = defer();
+
+ // Create nsIFile for the xpi file
+ let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
+ try {
+ file.initWithPath(xpiPath);
+ }
+ catch(e) {
+ reject(exports.ERROR_FILE_ACCESS);
+ return promise;
+ }
+
+ // Listen for installation end
+ let listener = {
+ onInstallEnded: function(aInstall, aAddon) {
+ aInstall.removeListener(listener);
+ // Bug 749745: on FF14+, onInstallEnded is called just before `startup()`
+ // is called, but we expect to resolve the promise only after it.
+ // As startup is called synchronously just after onInstallEnded,
+ // a simple setTimeout(0) is enough
+ setTimeout(resolve, 0, aAddon.id);
+ },
+ onInstallFailed: function (aInstall) {
+ console.log("failed");
+ aInstall.removeListener(listener);
+ reject(aInstall.error);
+ },
+ onDownloadFailed: function(aInstall) {
+ this.onInstallFailed(aInstall);
+ }
+ };
+
+ // Order AddonManager to install the addon
+ AddonManager.getInstallForFile(file, function(install) {
+ install.addListener(listener);
+ install.install();
+ });
+
+ return promise;
+};
+
+exports.uninstall = function uninstall(addonId) {
+ let { promise, resolve, reject } = defer();
+
+ // Listen for uninstallation end
+ let listener = {
+ onUninstalled: function onUninstalled(aAddon) {
+ if (aAddon.id != addonId)
+ return;
+ AddonManager.removeAddonListener(listener);
+ resolve();
+ }
+ };
+ AddonManager.addAddonListener(listener);
+
+ // Order Addonmanager to uninstall the addon
+ AddonManager.getAddonByID(addonId, function (addon) {
+ addon.uninstall();
+ });
+
+ return promise;
+};
+
+exports.disable = function disable(addonId) {
+ let { promise, resolve, reject } = defer();
+
+ AddonManager.getAddonByID(addonId, function (addon) {
+ addon.userDisabled = true;
+ resolve();
+ });
+
+ return promise;
+};
diff --git a/tools/addon-sdk-1.12/lib/sdk/addon/runner.js b/tools/addon-sdk-1.12/lib/sdk/addon/runner.js
new file mode 100644
index 0000000..0552de2
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/addon/runner.js
@@ -0,0 +1,153 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* 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/.
+ */
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Cc, Ci } = require('chrome');
+const { descriptor, Sandbox, evaluate, main, resolveURI } = require('toolkit/loader');
+const { once } = require('../system/events');
+const { exit, env, staticArgs, name } = require('../system');
+const { when: unload } = require('../system/unload');
+const { loadReason } = require('../self');
+const { rootURI } = require("@loader/options");
+const globals = require('../system/globals');
+
+const NAME2TOPIC = {
+ 'Firefox': 'sessionstore-windows-restored',
+ 'Fennec': 'sessionstore-windows-restored',
+ 'SeaMonkey': 'sessionstore-windows-restored',
+ 'Thunderbird': 'mail-startup-done',
+ '*': 'final-ui-startup'
+};
+
+// Gets the topic that fit best as application startup event, in according with
+// the current application (e.g. Firefox, Fennec, Thunderbird...)
+const APP_STARTUP = NAME2TOPIC[name] || NAME2TOPIC['*'];
+
+// Initializes default preferences
+function setDefaultPrefs(prefsURI) {
+ const prefs = Cc['@mozilla.org/preferences-service;1'].
+ getService(Ci.nsIPrefService).
+ QueryInterface(Ci.nsIPrefBranch2);
+ const branch = prefs.getDefaultBranch('');
+ const sandbox = Sandbox({
+ name: prefsURI,
+ prototype: {
+ pref: function(key, val) {
+ switch (typeof val) {
+ case 'boolean':
+ branch.setBoolPref(key, val);
+ break;
+ case 'number':
+ if (val % 1 == 0) // number must be a integer, otherwise ignore it
+ branch.setIntPref(key, val);
+ break;
+ case 'string':
+ branch.setCharPref(key, val);
+ break;
+ }
+ }
+ }
+ });
+ // load preferences.
+ evaluate(sandbox, prefsURI);
+}
+
+function definePseudo(loader, id, exports) {
+ let uri = resolveURI(id, loader.mapping);
+ loader.modules[uri] = { exports: exports };
+}
+
+function wait(reason, options) {
+ once(APP_STARTUP, function() {
+ startup(null, options);
+ });
+}
+
+function startup(reason, options) {
+ if (reason === 'startup')
+ return wait(reason, options);
+
+ // Inject globals ASAP in order to have console API working ASAP
+ Object.defineProperties(options.loader.globals, descriptor(globals));
+
+ // Load localization manifest and .properties files.
+ // Run the addon even in case of error (best effort approach)
+ require('../l10n/loader').
+ load(rootURI).
+ then(null, function failure(error) {
+ console.info("Error while loading localization: " + error.message);
+ }).
+ then(function onLocalizationReady(data) {
+ // Exports data to a pseudo module so that api-utils/l10n/core
+ // can get access to it
+ if (data)
+ definePseudo(options.loader, '@l10n/data', data);
+ run(options);
+ });
+}
+
+function run(options) {
+ try {
+ // Try initializing HTML localization before running main module. Just print
+ // an exception in case of error, instead of preventing addon to be run.
+ try {
+ // Do not enable HTML localization while running test as it is hard to
+ // disable. Because unit tests are evaluated in a another Loader who
+ // doesn't have access to this current loader.
+ if (options.main !== 'test-harness/run-tests')
+ require('../l10n/html').enable();
+ }
+ catch(error) {
+ console.exception(error);
+ }
+ // Initialize inline options localization, without preventing addon to be
+ // run in case of error
+ try {
+ require('../l10n/prefs');
+ }
+ catch(error) {
+ console.exception(error);
+ }
+
+ // TODO: When bug 564675 is implemented this will no longer be needed
+ // Always set the default prefs, because they disappear on restart
+ setDefaultPrefs(options.prefsURI);
+
+ // this is where the addon's main.js finally run.
+ let program = main(options.loader, options.main);
+
+ if (typeof(program.onUnload) === 'function')
+ unload(program.onUnload);
+
+ if (typeof(program.main) === 'function') {
+
+ program.main({
+ loadReason: loadReason,
+ staticArgs: staticArgs
+ }, {
+ print: function print(_) { dump(_ + '\n') },
+ quit: exit
+ });
+ }
+ } catch (error) {
+ console.exception(error);
+ throw error;
+ }
+}
+exports.startup = startup;
+
+// If add-on is lunched via `cfx run` we need to use `system.exit` to let
+// cfx know we're done (`cfx test` will take care of exit so we don't do
+// anything here).
+if (env.CFX_COMMAND === 'run') {
+ unload(function(reason) {
+ if (reason === 'shutdown')
+ exit(0);
+ });
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/base64.js b/tools/addon-sdk-1.12/lib/sdk/base64.js
new file mode 100644
index 0000000..42fea57
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/base64.js
@@ -0,0 +1,41 @@
+/* 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 { Cu } = require("chrome");
+
+// If an object is not given as second argument, the JavaScript Module scope is
+// returned, so we can obtain from it the `atob` and `btoa` functions
+const { atob, btoa } = Cu.import("resource://gre/modules/Services.jsm");
+
+function isUTF8(charset) {
+ let type = typeof charset;
+
+ if (type === "undefined")
+ return false;
+
+ if (type === "string" && charset.toLowerCase() === "utf-8")
+ return true;
+
+ throw new Error("The charset argument can be only 'utf-8'");
+}
+
+exports.decode = function (data, charset) {
+ if (isUTF8(charset))
+ return decodeURIComponent(escape(atob(data)))
+
+ return atob(data);
+}
+
+exports.encode = function (data, charset) {
+ if (isUTF8(charset))
+ return btoa(unescape(encodeURIComponent(data)))
+
+ return btoa(data);
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/clipboard.js b/tools/addon-sdk-1.12/lib/sdk/clipboard.js
new file mode 100644
index 0000000..58a3d7e
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/clipboard.js
@@ -0,0 +1,333 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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": "stable"
+};
+
+const { Cc, Ci } = require("chrome");
+const { DataURL } = require("./url");
+const errors = require("./deprecated/errors");
+const apiUtils = require("./deprecated/api-utils");
+/*
+While these data flavors resemble Internet media types, they do
+no directly map to them.
+*/
+const kAllowableFlavors = [
+ "text/unicode",
+ "text/html",
+ "image/png"
+ /* CURRENTLY UNSUPPORTED FLAVORS
+ "text/plain",
+ "image/jpg",
+ "image/jpeg",
+ "image/gif",
+ "text/x-moz-text-internal",
+ "AOLMAIL",
+ "application/x-moz-file",
+ "text/x-moz-url",
+ "text/x-moz-url-data",
+ "text/x-moz-url-desc",
+ "text/x-moz-url-priv",
+ "application/x-moz-nativeimage",
+ "application/x-moz-nativehtml",
+ "application/x-moz-file-promise-url",
+ "application/x-moz-file-promise-dest-filename",
+ "application/x-moz-file-promise",
+ "application/x-moz-file-promise-dir"
+ */
+];
+
+/*
+Aliases for common flavors. Not all flavors will
+get an alias. New aliases must be approved by a
+Jetpack API druid.
+*/
+const kFlavorMap = [
+ { short: "text", long: "text/unicode" },
+ { short: "html", long: "text/html" },
+ { short: "image", long: "image/png" }
+];
+
+let clipboardService = Cc["@mozilla.org/widget/clipboard;1"].
+ getService(Ci.nsIClipboard);
+
+let clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Ci.nsIClipboardHelper);
+
+let imageTools = Cc["@mozilla.org/image/tools;1"].
+ getService(Ci.imgITools);
+
+exports.set = function(aData, aDataType) {
+
+ let options = {
+ data: aData,
+ datatype: aDataType || "text"
+ };
+
+ // If `aDataType` is not given or if it's "image", the data is parsed as
+ // data URL to detect a better datatype
+ if (aData && (!aDataType || aDataType === "image")) {
+ try {
+ let dataURL = new DataURL(aData);
+
+ options.datatype = dataURL.mimeType;
+ options.data = dataURL.data;
+ }
+ catch (e if e.name === "URIError") {
+ // Not a valid Data URL
+ }
+ }
+
+ options = apiUtils.validateOptions(options, {
+ data: {
+ is: ["string"]
+ },
+ datatype: {
+ is: ["string"]
+ }
+ });
+
+ let flavor = fromJetpackFlavor(options.datatype);
+
+ if (!flavor)
+ throw new Error("Invalid flavor for " + options.datatype);
+
+ // Additional checks for using the simple case
+ if (flavor == "text/unicode") {
+ clipboardHelper.copyString(options.data);
+ return true;
+ }
+
+ // Below are the more complex cases where we actually have to work with a
+ // nsITransferable object
+ var xferable = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ if (!xferable)
+ throw new Error("Couldn't set the clipboard due to an internal error " +
+ "(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);
+ }
+
+ // 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);
+ }
+ 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;
+
+ let container = {};
+
+ try {
+ let input = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+
+ input.setData(image, image.length);
+
+ imageTools.decodeImageData(input, flavor, container);
+ }
+ catch (e) {
+ throw new Error("Unable to decode data given in a valid image.");
+ }
+
+ // Store directly the input stream makes the cliboard's data available
+ // for Firefox but not to the others application or to the OS. Therefore,
+ // a `nsISupportsInterfacePointer` object that reference an `imgIContainer`
+ // with the image is needed.
+ var imgPtr = Cc["@mozilla.org/supports-interface-pointer;1"].
+ createInstance(Ci.nsISupportsInterfacePointer);
+
+ imgPtr.data = container.value;
+
+ xferable.addDataFlavor(flavor);
+ xferable.setTransferData(flavor, imgPtr, -1);
+
+ break;
+ default:
+ throw new Error("Unable to handle the flavor " + flavor + ".");
+ }
+
+ // TODO: Not sure if this will ever actually throw. -zpao
+ try {
+ clipboardService.setData(
+ xferable,
+ null,
+ clipboardService.kGlobalClipboard
+ );
+ } catch (e) {
+ throw new Error("Couldn't set clipboard data due to an internal error: " + e);
+ }
+ return true;
+};
+
+
+exports.get = function(aDataType) {
+ let options = {
+ datatype: aDataType
+ };
+
+ // Figure out the best data type for the clipboard's data, if omitted
+ if (!aDataType) {
+ if (~currentFlavors().indexOf("image"))
+ options.datatype = "image";
+ else
+ options.datatype = "text";
+ }
+
+ options = apiUtils.validateOptions(options, {
+ datatype: {
+ is: ["string"]
+ }
+ });
+
+ var xferable = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ if (!xferable)
+ throw new Error("Couldn't set the clipboard due to an internal error " +
+ "(couldn't create a Transferable object).");
+ // Bug 769440: Starting with FF16, transferable have to be inited
+ if ("init" in xferable)
+ xferable.init(null);
+
+ var flavor = fromJetpackFlavor(options.datatype);
+
+ // Ensure that the user hasn't requested a flavor that we don't support.
+ if (!flavor)
+ throw new Error("Getting the clipboard with the flavor '" + flavor +
+ "' is not supported.");
+
+ // TODO: Check for matching flavor first? Probably not worth it.
+
+ xferable.addDataFlavor(flavor);
+ // Get the data into our transferable.
+ clipboardService.getData(
+ xferable,
+ clipboardService.kGlobalClipboard
+ );
+
+ var data = {};
+ var dataLen = {};
+ try {
+ xferable.getTransferData(flavor, data, dataLen);
+ } catch (e) {
+ // Clipboard doesn't contain data in flavor, return null.
+ return null;
+ }
+
+ // There's no data available, return.
+ if (data.value === null)
+ return null;
+
+ // TODO: Add flavors here as we support more in kAllowableFlavors.
+ switch (flavor) {
+ case "text/unicode":
+ case "text/html":
+ data = data.value.QueryInterface(Ci.nsISupportsString).data;
+ break;
+ case "image/png":
+ let dataURL = new DataURL();
+
+ dataURL.mimeType = flavor;
+ dataURL.base64 = true;
+
+ let image = data.value;
+
+ // Due to the differences in how images could be stored in the clipboard
+ // the checks below are needed. The clipboard could already provide the
+ // image as byte streams, but also as pointer, or as image container.
+ // If it's not possible obtain a byte stream, the function returns `null`.
+ if (image instanceof Ci.nsISupportsInterfacePointer)
+ image = image.data;
+
+ if (image instanceof Ci.imgIContainer)
+ image = imageTools.encodeImage(image, flavor);
+
+ if (image instanceof Ci.nsIInputStream) {
+ let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+
+ binaryStream.setInputStream(image);
+
+ dataURL.data = binaryStream.readBytes(binaryStream.available());
+
+ data = dataURL.toString();
+ }
+ else
+ data = null;
+
+ break;
+ default:
+ data = null;
+ }
+
+ return data;
+};
+
+function currentFlavors() {
+ // Loop over kAllowableFlavors, calling hasDataMatchingFlavors for each.
+ // This doesn't seem like the most efficient way, but we can't get
+ // confirmation for specific flavors any other way. This is supposed to be
+ // an inexpensive call, so performance shouldn't be impacted (much).
+ var currentFlavors = [];
+ for each (var flavor in kAllowableFlavors) {
+ var matches = clipboardService.hasDataMatchingFlavors(
+ [flavor],
+ 1,
+ clipboardService.kGlobalClipboard
+ );
+ if (matches)
+ currentFlavors.push(toJetpackFlavor(flavor));
+ }
+ return currentFlavors;
+};
+
+Object.defineProperty(exports, "currentFlavors", { get : currentFlavors });
+
+// SUPPORT FUNCTIONS ////////////////////////////////////////////////////////
+
+function toJetpackFlavor(aFlavor) {
+ for each (let flavorMap in kFlavorMap)
+ if (flavorMap.long == aFlavor)
+ return flavorMap.short;
+ // Return null in the case where we don't match
+ return null;
+}
+
+function fromJetpackFlavor(aJetpackFlavor) {
+ // TODO: Handle proper flavors better
+ for each (let flavorMap in kFlavorMap)
+ if (flavorMap.short == aJetpackFlavor || flavorMap.long == aJetpackFlavor)
+ return flavorMap.long;
+ // Return null in the case where we don't match.
+ return null;
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/console/plain-text.js b/tools/addon-sdk-1.12/lib/sdk/console/plain-text.js
new file mode 100644
index 0000000..9e3b748
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/console/plain-text.js
@@ -0,0 +1,88 @@
+/* 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 self = require("../self");
+
+function stringify(arg) {
+ try {
+ return String(arg);
+ }
+ catch(ex) {
+ return "<toString() error>";
+ }
+}
+
+function stringifyArgs(args) {
+ return Array.map(args, stringify).join(" ");
+}
+
+function message(print, level, args, name) {
+ print(level + ": " + name + ": " +
+ stringifyArgs(args) + "\n", level);
+}
+
+var Console = exports.PlainTextConsole = function PlainTextConsole(print) {
+ if (!print)
+ print = dump;
+ if (print === dump) {
+ // If we're just using dump(), auto-enable preferences so
+ // that the developer actually sees the console output.
+ var prefs = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ prefs.setBoolPref("browser.dom.window.dump.enabled", true);
+ }
+ this.print = print;
+
+ // Binding all the public methods to an instance so that they can be used
+ // as callback / listener functions straightaway.
+ this.log = this.log.bind(this);
+ this.info = this.info.bind(this);
+ this.warn = this.warn.bind(this);
+ this.error = this.error.bind(this);
+ this.debug = this.debug.bind(this);
+ this.exception = this.exception.bind(this);
+ this.trace = this.trace.bind(this);
+};
+
+Console.prototype = {
+ log: function log() {
+ message(this.print, "info", arguments, self.name);
+ },
+
+ info: function info() {
+ message(this.print, "info", arguments, self.name);
+ },
+
+ warn: function warn() {
+ message(this.print, "warning", arguments, self.name);
+ },
+
+ error: function error() {
+ message(this.print, "error", arguments, self.name);
+ },
+
+ debug: function debug() {
+ message(this.print, "debug", arguments, self.name);
+ },
+
+ exception: function exception(e) {
+ var fullString = ("An exception occurred.\n" +
+ require("./traceback").format(e) + "\n" + e);
+ this.error(fullString);
+ },
+
+ trace: function trace() {
+ var traceback = require("./traceback");
+ var stack = traceback.get();
+ stack.splice(-1, 1);
+ message(this.print, "info", [traceback.format(stack)], self.name);
+ }
+};
diff --git a/tools/addon-sdk-1.12/lib/sdk/console/traceback.js b/tools/addon-sdk-1.12/lib/sdk/console/traceback.js
new file mode 100644
index 0000000..6b23be1
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/console/traceback.js
@@ -0,0 +1,118 @@
+/* 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 { Cc, Ci, components } = require("chrome");
+const { readURISync } = require("../net/url");
+
+// Undo the auto-parentification of URLs done in bug 418356.
+function deParentifyURL(url) {
+ return url ? url.split(" -> ").slice(-1)[0] : url;
+}
+
+function safeGetFileLine(path, line) {
+ try {
+ var scheme = require("../url").URL(path).scheme;
+ // TODO: There should be an easier, more accurate way to figure out
+ // what's the case here.
+ if (!(scheme == "http" || scheme == "https"))
+ return readURISync(path).split("\n")[line - 1];
+ } catch (e) {}
+ return null;
+}
+
+function errorStackToJSON(stack) {
+ var lines = stack.split("\n");
+
+ var frames = [];
+ lines.forEach(
+ function(line) {
+ if (!line)
+ return;
+ var atIndex = line.indexOf("@");
+ var colonIndex = line.lastIndexOf(":");
+ var filename = deParentifyURL(line.slice(atIndex + 1, colonIndex));
+ var lineNo = parseInt(line.slice(colonIndex + 1));
+ var funcSig = line.slice(0, atIndex);
+ var endFuncName = funcSig.indexOf("(");
+ // Bug 751149: FF15 changed function signature
+ // Instead of: runTest([object Object])
+ // We now have: runTest
+ var funcName = endFuncName != -1
+ ? funcSig.slice(0, endFuncName)
+ : funcSig;
+ frames.unshift({filename: filename,
+ funcName: funcName,
+ lineNo: lineNo});
+ });
+
+ return frames;
+};
+
+function nsIStackFramesToJSON(frame) {
+ var stack = [];
+
+ while (frame) {
+ if (frame.filename) {
+ var filename = deParentifyURL(frame.filename);
+ stack.splice(0, 0, {filename: filename,
+ lineNo: frame.lineNumber,
+ funcName: frame.name});
+ }
+ frame = frame.caller;
+ }
+
+ return stack;
+};
+
+var fromException = exports.fromException = function fromException(e) {
+ if (e instanceof Ci.nsIException)
+ return nsIStackFramesToJSON(e.location);
+ if (e.stack && e.stack.length)
+ return errorStackToJSON(e.stack);
+ if (e.fileName && typeof(e.lineNumber == "number"))
+ return [{filename: deParentifyURL(e.fileName),
+ lineNo: e.lineNumber,
+ funcName: null}];
+ return [];
+};
+
+var get = exports.get = function get() {
+ return nsIStackFramesToJSON(components.stack.caller);
+};
+
+var format = exports.format = function format(tbOrException) {
+ if (tbOrException === undefined) {
+ tbOrException = get();
+ tbOrException.splice(-1, 1);
+ }
+
+ var tb;
+ if (typeof(tbOrException) == "object" &&
+ tbOrException.constructor.name == "Array")
+ tb = tbOrException;
+ else
+ tb = fromException(tbOrException);
+
+ var lines = ["Traceback (most recent call last):"];
+
+ tb.forEach(
+ function(frame) {
+ if (!(frame.filename || frame.lineNo || frame.funcName))
+ return;
+
+ lines.push(' File "' + frame.filename + '", line ' +
+ frame.lineNo + ', in ' + frame.funcName);
+ var sourceLine = safeGetFileLine(frame.filename, frame.lineNo);
+ if (sourceLine)
+ lines.push(' ' + sourceLine.trim());
+ });
+
+ return lines.join("\n");
+};
diff --git a/tools/addon-sdk-1.12/lib/sdk/content/content-proxy.js b/tools/addon-sdk-1.12/lib/sdk/content/content-proxy.js
new file mode 100644
index 0000000..76df7f6
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/content/content-proxy.js
@@ -0,0 +1,870 @@
+/* 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";
+
+/* Trick the linker in order to avoid error on `Components.interfaces` usage.
+ We are tricking the linker with `require('./content-proxy.js')` from
+ worjer.js in order to ensure shipping this file! But then the linker think
+ that this file is going to be used as a CommonJS module where we forbid usage
+ of `Components`.
+*/
+let Ci = Components['interfaces'];
+
+/**
+ * Access key that allows privileged code to unwrap proxy wrappers through
+ * valueOf:
+ * let xpcWrapper = proxyWrapper.valueOf(UNWRAP_ACCESS_KEY);
+ * This key should only be used by proxy unit test.
+ */
+ const UNWRAP_ACCESS_KEY = {};
+
+
+ /**
+ * Returns a closure that wraps arguments before calling the given function,
+ * which can be given to native functions that accept a function, such that when
+ * the closure is called, the given function is called with wrapped arguments.
+ *
+ * @param fun {Function}
+ * the function for which to create a closure wrapping its arguments
+ * @param obj {Object}
+ * target object from which `fun` comes from
+ * (optional, for debugging purpose)
+ * @param name {String}
+ * name of the attribute from which `fun` is binded on `obj`
+ * (optional, for debugging purpose)
+ *
+ * Example:
+ * function contentScriptListener(event) {}
+ * let wrapper = ContentScriptFunctionWrapper(contentScriptListener);
+ * xray.addEventListener("...", wrapper, false);
+ * -> Allow to `event` to be wrapped
+ */
+function ContentScriptFunctionWrapper(fun, obj, name) {
+ if ("___proxy" in fun && typeof fun.___proxy == "function")
+ return fun.___proxy;
+
+ let wrappedFun = function () {
+ let args = [];
+ for (let i = 0, l = arguments.length; i < l; i++)
+ args.push(wrap(arguments[i]));
+
+ //console.log("Called from native :"+obj+"."+name);
+ //console.log(">args "+arguments.length);
+ //console.log(fun);
+
+ // Native code can execute this callback with `this` being the wrapped
+ // function. For example, window.mozRequestAnimationFrame.
+ if (this == wrappedFun)
+ return fun.apply(fun, args);
+
+ return fun.apply(wrap(this), args);
+ };
+
+ Object.defineProperty(fun, "___proxy", {value : wrappedFun,
+ writable : false,
+ enumerable : false,
+ configurable : false});
+
+ return wrappedFun;
+}
+
+/**
+ * Returns a closure that unwraps arguments before calling the `fun` function,
+ * which can be used to build a wrapper for a native function that accepts
+ * wrapped arguments, since native function only accept unwrapped arguments.
+ *
+ * @param fun {Function}
+ * the function to wrap
+ * @param originalObject {Object}
+ * target object from which `fun` comes from
+ * (optional, for debugging purpose)
+ * @param name {String}
+ * name of the attribute from which `fun` is binded on `originalObject`
+ * (optional, for debugging purpose)
+ *
+ * Example:
+ * wrapper.appendChild = NativeFunctionWrapper(xray.appendChild, xray);
+ * wrapper.appendChild(anotherWrapper);
+ * -> Allow to call xray.appendChild with unwrapped version of anotherWrapper
+ */
+function NativeFunctionWrapper(fun, originalObject, name) {
+ return function () {
+ let args = [];
+ let obj = this && typeof this.valueOf == "function" ?
+ this.valueOf(UNWRAP_ACCESS_KEY) : this;
+
+ for (let i = 0, l = arguments.length; i < l; i++)
+ args.push( unwrap(arguments[i], obj, name) );
+
+ //if (name != "toString")
+ //console.log(">>calling native ["+(name?name:'#closure#')+"]: \n"+fun.apply+"\n"+obj+"\n("+args.join(', ')+")\nthis :"+obj+"from:"+originalObject+"\n");
+
+ // Need to use Function.prototype.apply.apply because XMLHttpRequest
+ // is a function (typeof return 'function') and fun.apply is null :/
+ let unwrapResult = Function.prototype.apply.apply(fun, [obj, args]);
+ let result = wrap(unwrapResult, obj, name);
+
+ //console.log("<< "+rr+" -> "+r);
+
+ return result;
+ };
+}
+
+/*
+ * Unwrap a JS value that comes from the content script.
+ * Mainly converts proxy wrapper to XPCNativeWrapper.
+ */
+function unwrap(value, obj, name) {
+ //console.log("unwrap : "+value+" ("+name+")");
+ if (!value)
+ return value;
+ let type = typeof value;
+
+ // In case of proxy, unwrap them recursively
+ // (it should not be recursive, just in case of)
+ if (["object", "function"].indexOf(type) !== -1 &&
+ "__isWrappedProxy" in value) {
+ while("__isWrappedProxy" in value)
+ value = value.valueOf(UNWRAP_ACCESS_KEY);
+ return value;
+ }
+
+ // In case of functions we need to return a wrapper that converts native
+ // arguments applied to this function into proxies.
+ if (type == "function")
+ return ContentScriptFunctionWrapper(value, obj, name);
+
+ // We must wrap objects coming from content script too, as they may have
+ // a function that will be called by a native method.
+ // For example:
+ // addEventListener(..., { handleEvent: function(event) {} }, ...);
+ if (type == "object")
+ return ContentScriptObjectWrapper(value);
+
+ if (["string", "number", "boolean"].indexOf(type) !== -1)
+ return value;
+ //console.log("return non-wrapped to native : "+typeof value+" -- "+value);
+ return value;
+}
+
+/**
+ * Returns an XrayWrapper proxy object that allow to wrap any of its function
+ * though `ContentScriptFunctionWrapper`. These proxies are given to
+ * XrayWrappers in order to automatically wrap values when they call a method
+ * of these proxies. So that they are only used internaly and content script,
+ * nor web page have ever access to them. As a conclusion, we can consider
+ * this code as being safe regarding web pages overload.
+ *
+ *
+ * @param obj {Object}
+ * object coming from content script context to wrap
+ *
+ * Example:
+ * let myListener = { handleEvent: function (event) {} };
+ * node.addEventListener("click", myListener, false);
+ * `event` has to be wrapped, so handleEvent has to be wrapped using
+ * `ContentScriptFunctionWrapper` function.
+ * In order to do so, we build this new kind of proxies.
+ */
+function ContentScriptObjectWrapper(obj) {
+ if ("___proxy" in obj && typeof obj.___proxy == "object")
+ return obj.___proxy;
+
+ function valueOf(key) {
+ if (key === UNWRAP_ACCESS_KEY)
+ return obj;
+ return this;
+ }
+
+ let proxy = Proxy.create({
+ // Fundamental traps
+ getPropertyDescriptor: function(name) {
+ return Object.getOwnPropertyDescriptor(obj, name);
+ },
+ defineProperty: function(name, desc) {
+ return Object.defineProperty(obj, name, desc);
+ },
+ getOwnPropertyNames: function () {
+ return Object.getOwnPropertyNames(obj);
+ },
+ delete: function(name) {
+ return delete obj[name];
+ },
+ // derived traps
+ has: function(name) {
+ return name === "__isXrayWrapperProxy" ||
+ name in obj;
+ },
+ hasOwn: function(name) {
+ return Object.prototype.hasOwnProperty.call(obj, name);
+ },
+ get: function(receiver, name) {
+ if (name == "valueOf")
+ return valueOf;
+ let value = obj[name];
+ if (!value)
+ return value;
+
+ return unwrap(value);
+ },
+ set: function(receiver, name, val) {
+ obj[name] = val;
+ return true;
+ },
+ enumerate: function() {
+ var result = [];
+ for each (let name in obj) {
+ result.push(name);
+ };
+ return result;
+ },
+ keys: function() {
+ return Object.keys(obj);
+ }
+ });
+
+ Object.defineProperty(obj, "___proxy", {value : proxy,
+ writable : false,
+ enumerable : false,
+ configurable : false});
+
+ return proxy;
+}
+
+// List of all existing typed arrays.
+// Can be found here:
+// http://mxr.mozilla.org/mozilla-central/source/js/src/jsapi.cpp#1790
+const typedArraysCtor = [
+ ArrayBuffer,
+ Int8Array,
+ Uint8Array,
+ Int16Array,
+ Uint16Array,
+ Int32Array,
+ Uint32Array,
+ Float32Array,
+ Float64Array,
+ Uint8ClampedArray
+];
+
+/*
+ * Wrap a JS value coming from the document by building a proxy wrapper.
+ */
+function wrap(value, obj, name, debug) {
+ if (!value)
+ return value;
+ let type = typeof value;
+ if (type == "object") {
+ // Bug 671016: Typed arrays don't need to be proxified.
+ // We avoid checking the whole constructor list on all objects
+ // by doing this check only on non-extensible objects:
+ if (!Object.isExtensible(value) &&
+ typedArraysCtor.indexOf(value.constructor) !== -1)
+ return value;
+
+ // Bug 715755: do not proxify COW wrappers
+ // These wrappers throw an exception when trying to access
+ // any attribute that is not in a white list
+ try {
+ ("nonExistantAttribute" in value);
+ }
+ catch(e) {
+ if (e.message.indexOf("Permission denied to access property") !== -1)
+ return value;
+ }
+
+ // We may have a XrayWrapper proxy.
+ // For example:
+ // let myListener = { handleEvent: function () {} };
+ // node.addEventListener("click", myListener, false);
+ // When native code want to call handleEvent,
+ // we go though ContentScriptFunctionWrapper that calls `wrap(this)`
+ // `this` is the XrayWrapper proxy of myListener.
+ // We return this object without building a CS proxy as it is already
+ // a value coming from the CS.
+ if ("__isXrayWrapperProxy" in value)
+ return value.valueOf(UNWRAP_ACCESS_KEY);
+
+ // Unwrap object before wrapping it.
+ // It should not happen with CS proxy objects.
+ while("__isWrappedProxy" in value) {
+ value = value.valueOf(UNWRAP_ACCESS_KEY);
+ }
+
+ if (XPCNativeWrapper.unwrap(value) !== value)
+ return getProxyForObject(value);
+ // In case of Event, HTMLCollection or NodeList or ???
+ // XPCNativeWrapper.unwrap(value) === value
+ // but it's still a XrayWrapper so let's build a proxy
+ return getProxyForObject(value);
+ }
+ if (type == "function") {
+ if (XPCNativeWrapper.unwrap(value) !== value
+ || (typeof value.toString === "function" &&
+ value.toString().match(/\[native code\]/))) {
+ return getProxyForFunction(value, NativeFunctionWrapper(value, obj, name));
+ }
+ return value;
+ }
+ if (type == "string")
+ return value;
+ if (type == "number")
+ return value;
+ if (type == "boolean")
+ return value;
+ //console.log("return non-wrapped to wrapped : "+value);
+ return value;
+}
+
+/*
+ * Wrap an object from the document to a proxy wrapper
+ */
+function getProxyForObject(obj) {
+ if (typeof obj != "object") {
+ let msg = "tried to proxify something other than an object: " + typeof obj;
+ console.warn(msg);
+ throw msg;
+ }
+ if ("__isWrappedProxy" in obj) {
+ return obj;
+ }
+ // Check if there is a proxy cached on this wrapper,
+ // but take care of prototype ___proxy attribute inheritance!
+ if (obj && obj.___proxy && obj.___proxy.valueOf(UNWRAP_ACCESS_KEY) === obj) {
+ return obj.___proxy;
+ }
+
+ let proxy = Proxy.create(handlerMaker(obj));
+
+ Object.defineProperty(obj, "___proxy", {value : proxy,
+ writable : false,
+ enumerable : false,
+ configurable : false});
+ return proxy;
+}
+
+/*
+ * Wrap a function from the document to a proxy wrapper
+ */
+function getProxyForFunction(fun, callTrap) {
+ if (typeof fun != "function") {
+ let msg = "tried to proxify something other than a function: " + typeof fun;
+ console.warn(msg);
+ throw msg;
+ }
+ if ("__isWrappedProxy" in fun)
+ return obj;
+ if ("___proxy" in fun)
+ return fun.___proxy;
+
+ let proxy = Proxy.createFunction(handlerMaker(fun), callTrap);
+
+ Object.defineProperty(fun, "___proxy", {value : proxy,
+ writable : false,
+ enumerable : false,
+ configurable : false});
+
+ return proxy;
+}
+
+/*
+ * Check if a DOM attribute name is an event name.
+ */
+function isEventName(id) {
+ if (id.indexOf("on") != 0 || id.length == 2)
+ return false;
+ // Taken from:
+ // http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#7616
+ switch (id[2]) {
+ case 'a' :
+ return (id == "onabort" ||
+ id == "onafterscriptexecute" ||
+ id == "onafterprint");
+ case 'b' :
+ return (id == "onbeforeunload" ||
+ id == "onbeforescriptexecute" ||
+ id == "onblur" ||
+ id == "onbeforeprint");
+ case 'c' :
+ return (id == "onchange" ||
+ id == "onclick" ||
+ id == "oncontextmenu" ||
+ id == "oncopy" ||
+ id == "oncut" ||
+ id == "oncanplay" ||
+ id == "oncanplaythrough");
+ case 'd' :
+ return (id == "ondblclick" ||
+ id == "ondrag" ||
+ id == "ondragend" ||
+ id == "ondragenter" ||
+ id == "ondragleave" ||
+ id == "ondragover" ||
+ id == "ondragstart" ||
+ id == "ondrop" ||
+ id == "ondurationchange");
+ case 'e' :
+ return (id == "onerror" ||
+ id == "onemptied" ||
+ id == "onended");
+ case 'f' :
+ return id == "onfocus";
+ case 'h' :
+ return id == "onhashchange";
+ case 'i' :
+ return (id == "oninput" ||
+ id == "oninvalid");
+ case 'k' :
+ return (id == "onkeydown" ||
+ id == "onkeypress" ||
+ id == "onkeyup");
+ case 'l' :
+ return (id == "onload" ||
+ id == "onloadeddata" ||
+ id == "onloadedmetadata" ||
+ id == "onloadstart");
+ case 'm' :
+ return (id == "onmousemove" ||
+ id == "onmouseout" ||
+ id == "onmouseover" ||
+ id == "onmouseup" ||
+ id == "onmousedown" ||
+ id == "onmessage");
+ case 'p' :
+ return (id == "onpaint" ||
+ id == "onpageshow" ||
+ id == "onpagehide" ||
+ id == "onpaste" ||
+ id == "onpopstate" ||
+ id == "onpause" ||
+ id == "onplay" ||
+ id == "onplaying" ||
+ id == "onprogress");
+ case 'r' :
+ return (id == "onreadystatechange" ||
+ id == "onreset" ||
+ id == "onresize" ||
+ id == "onratechange");
+ case 's' :
+ return (id == "onscroll" ||
+ id == "onselect" ||
+ id == "onsubmit" ||
+ id == "onseeked" ||
+ id == "onseeking" ||
+ id == "onstalled" ||
+ id == "onsuspend");
+ case 't':
+ return id == "ontimeupdate"
+ /*
+ // TODO: Make it work for mobile version
+ ||
+ (nsDOMTouchEvent::PrefEnabled() &&
+ (id == "ontouchstart" ||
+ id == "ontouchend" ||
+ id == "ontouchmove" ||
+ id == "ontouchenter" ||
+ id == "ontouchleave" ||
+ id == "ontouchcancel"))*/;
+
+ case 'u' :
+ return id == "onunload";
+ case 'v':
+ return id == "onvolumechange";
+ case 'w':
+ return id == "onwaiting";
+ }
+
+ return false;
+}
+
+// XrayWrappers miss some attributes.
+// Here is a list of functions that return a value when it detects a miss:
+const NODES_INDEXED_BY_NAME = ["IMG", "FORM", "APPLET", "EMBED", "OBJECT"];
+const xRayWrappersMissFixes = [
+
+ // Fix bug with XPCNativeWrapper on HTMLCollection
+ // We can only access array item once, then it's undefined :o
+ function (obj, name) {
+ let i = parseInt(name);
+ if (obj.toString().match(/HTMLCollection|NodeList/) &&
+ i >= 0 && i < obj.length) {
+ return wrap(XPCNativeWrapper(obj.wrappedJSObject[name]), obj, name);
+ }
+ return null;
+ },
+
+ // Trap access to document["form name"]
+ // that may refer to an existing form node
+ // http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#9285
+ function (obj, name) {
+ if ("nodeType" in obj && obj.nodeType == 9) {
+ let node = obj.wrappedJSObject[name];
+ // List of supported tag:
+ // http://mxr.mozilla.org/mozilla-central/source/content/html/content/src/nsGenericHTMLElement.cpp#1267
+ if (node && NODES_INDEXED_BY_NAME.indexOf(node.tagName) != -1)
+ return wrap(XPCNativeWrapper(node));
+ }
+ return null;
+ },
+
+ // Trap access to window["frame name"] and window.frames[i]
+ // that refer to an (i)frame internal window object
+ // http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#6824
+ function (obj, name) {
+ if (typeof obj == "object" && "document" in obj) {
+ // Ensure that we are on a window object
+ try {
+ obj.QueryInterface(Ci.nsIDOMWindow);
+ }
+ catch(e) {
+ return null;
+ }
+
+ // Integer case:
+ let i = parseInt(name);
+ if (i >= 0 && i in obj) {
+ return wrap(XPCNativeWrapper(obj[i]));
+ }
+
+ // String name case:
+ if (name in obj.wrappedJSObject) {
+ let win = obj.wrappedJSObject[name];
+ let nodes = obj.document.getElementsByName(name);
+ for (let i = 0, l = nodes.length; i < l; i++) {
+ let node = nodes[i];
+ if ("contentWindow" in node && node.contentWindow == win)
+ return wrap(node.contentWindow);
+ }
+ }
+ }
+ return null;
+ },
+
+ // Trap access to form["node name"]
+ // http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#9477
+ function (obj, name) {
+ if (typeof obj == "object" && "tagName" in obj && obj.tagName == "FORM") {
+ let match = obj.wrappedJSObject[name];
+ let nodes = obj.ownerDocument.getElementsByName(name);
+ for (let i = 0, l = nodes.length; i < l; i++) {
+ let node = nodes[i];
+ if (node == match)
+ return wrap(node);
+ }
+ }
+ return null;
+ }
+
+];
+
+// XrayWrappers have some buggy methods.
+// Here is the list of them with functions returning some replacement
+// for a given object `obj`:
+const xRayWrappersMethodsFixes = {
+ // postMessage method is checking the Javascript global
+ // and it expects it to be a window object.
+ // But in our case, the global object is our sandbox global object.
+ // See nsGlobalWindow::CallerInnerWindow():
+ // http://mxr.mozilla.org/mozilla-central/source/dom/base/nsGlobalWindow.cpp#5893
+ // nsCOMPtr<nsPIDOMWindow> win = do_QueryWrappedNative(wrapper);
+ // win is null
+ postMessage: function (obj) {
+ // Ensure that we are on a window object
+ try {
+ obj.QueryInterface(Ci.nsIDOMWindow);
+ }
+ catch(e) {
+ return null;
+ }
+
+ // Create a wrapper that is going to call `postMessage` through `eval`
+ let f = function postMessage(message, targetOrigin) {
+ let jscode = "this.postMessage(";
+ if (typeof message != "string")
+ jscode += JSON.stringify(message);
+ else
+ jscode += "'" + message.toString().replace(/['\\]/g,"\\$&") + "'";
+
+ targetOrigin = targetOrigin.toString().replace(/['\\]/g,"\\$&");
+
+ jscode += ", '" + targetOrigin + "')";
+ return this.wrappedJSObject.eval(jscode);
+ };
+ return getProxyForFunction(f, NativeFunctionWrapper(f));
+ },
+
+ // Fix mozMatchesSelector uses that is broken on XrayWrappers
+ // when we use document.documentElement.mozMatchesSelector.call(node, expr)
+ // It's only working if we call mozMatchesSelector on the node itself.
+ // SEE BUG 658909: mozMatchesSelector returns incorrect results with XrayWrappers
+ mozMatchesSelector: function (obj) {
+ // Ensure that we are on an object to expose this buggy method
+ try {
+ // Bug 707576 removed nsIDOMNSElement.
+ // Can be simplified as soon as Firefox 11 become the minversion
+ obj.QueryInterface("nsIDOMElement" in Ci ? Ci.nsIDOMElement :
+ Ci.nsIDOMNSElement);
+ }
+ catch(e) {
+ return null;
+ }
+ // We can't use `wrap` function as `f` is not a native function,
+ // so wrap it manually:
+ let f = function mozMatchesSelector(selectors) {
+ return this.mozMatchesSelector(selectors);
+ };
+
+ return getProxyForFunction(f, NativeFunctionWrapper(f));
+ },
+
+ // Bug 679054: History API doesn't work with Proxy objects. We have to pass
+ // regular JS objects on `pushState` and `replaceState` methods.
+ // In addition, the first argument has to come from the same compartment.
+ pushState: function (obj) {
+ // Ensure that we are on an object that expose History API
+ try {
+ obj.QueryInterface(Ci.nsIDOMHistory);
+ }
+ catch(e) {
+ return null;
+ }
+ let f = function fix() {
+ // Call native method with JSON objects
+ // (need to convert `arguments` to an array via `slice`)
+ return this.pushState.apply(this, JSON.parse(JSON.stringify(Array.slice(arguments))));
+ };
+
+ return getProxyForFunction(f, NativeFunctionWrapper(f));
+ },
+ replaceState: function (obj) {
+ // Ensure that we are on an object that expose History API
+ try {
+ obj.QueryInterface(Ci.nsIDOMHistory);
+ }
+ catch(e) {
+ return null;
+ }
+ let f = function fix() {
+ // Call native method with JSON objects
+ // (need to convert `arguments` to an array via `slice`)
+ return this.replaceState.apply(this, JSON.parse(JSON.stringify(Array.slice(arguments))));
+ };
+
+ return getProxyForFunction(f, NativeFunctionWrapper(f));
+ },
+
+ // Bug 769006: nsIDOMMutationObserver.observe fails with proxy as options
+ // attributes
+ observe: function observe(obj) {
+ // Ensure that we are on a DOMMutation object
+ try {
+ // nsIDOMMutationObserver starts with FF14
+ if ("nsIDOMMutationObserver" in Ci)
+ obj.QueryInterface(Ci.nsIDOMMutationObserver);
+ else
+ return null;
+ }
+ catch(e) {
+ return null;
+ }
+ return function nsIDOMMutationObserverObserveFix(target, options) {
+ // Gets native/unwrapped this
+ let self = this && typeof this.valueOf == "function" ?
+ this.valueOf(UNWRAP_ACCESS_KEY) : this;
+ // Unwrap the xraywrapper target out of JS proxy
+ let targetXray = unwrap(target);
+ // But do not wrap `options` through ContentScriptObjectWrapper
+ let result = wrap(self.observe(targetXray, options));
+ // Finally wrap result into JS proxies
+ return wrap(result);
+ };
+ }
+};
+
+/*
+ * Generate handler for proxy wrapper
+ */
+function handlerMaker(obj) {
+ // Overloaded attributes dictionary
+ let overload = {};
+ // Expando attributes dictionary (i.e. onclick, onfocus, on* ...)
+ let expando = {};
+ // Cache of methods overloaded to fix XrayWrapper bug
+ let methodFixes = {};
+ return {
+ // Fundamental traps
+ getPropertyDescriptor: function(name) {
+ return Object.getOwnPropertyDescriptor(obj, name);
+ },
+ defineProperty: function(name, desc) {
+ return Object.defineProperty(obj, name, desc);
+ },
+ getOwnPropertyNames: function () {
+ return Object.getOwnPropertyNames(obj);
+ },
+ delete: function(name) {
+ delete expando[name];
+ delete overload[name];
+ return delete obj[name];
+ },
+
+ // derived traps
+ has: function(name) {
+ if (name == "___proxy") return false;
+ if (isEventName(name)) {
+ // XrayWrappers throw exception when we try to access expando attributes
+ // even on "name in wrapper". So avoid doing it!
+ return name in expando;
+ }
+ return name in obj || name in overload || name == "__isWrappedProxy" ||
+ undefined !== this.get(null, name);
+ },
+ hasOwn: function(name) {
+ return Object.prototype.hasOwnProperty.call(obj, name);
+ },
+ get: function(receiver, name) {
+ if (name == "___proxy")
+ return undefined;
+
+ // Overload toString in order to avoid returning "[XrayWrapper [object HTMLElement]]"
+ // or "[object Function]" for function's Proxy
+ if (name == "toString") {
+ // Bug 714778: we should not pass obj.wrappedJSObject.toString
+ // in order to avoid sharing its proxy between two contents scripts.
+ // (not that `unwrappedObj` can be equal to `obj` when `obj` isn't
+ // an xraywrapper)
+ let unwrappedObj = XPCNativeWrapper.unwrap(obj);
+ return wrap(function () {
+ return unwrappedObj.toString.call(
+ this.valueOf(UNWRAP_ACCESS_KEY), arguments);
+ }, obj, name);
+ }
+
+ // Offer a way to retrieve XrayWrapper from a proxified node through `valueOf`
+ if (name == "valueOf")
+ return function (key) {
+ if (key === UNWRAP_ACCESS_KEY)
+ return obj;
+ return this;
+ };
+
+ // Return overloaded value if there is one.
+ // It allows to overload native methods like addEventListener that
+ // are not saved, even on the wrapper itself.
+ // (And avoid some methods like toSource from being returned here! [__proto__ test])
+ if (name in overload &&
+ overload[name] != Object.getPrototypeOf(overload)[name] &&
+ name != "__proto__") {
+ return overload[name];
+ }
+
+ // Catch exceptions thrown by XrayWrappers when we try to access on*
+ // attributes like onclick, onfocus, ...
+ if (isEventName(name)) {
+ //console.log("expando:"+obj+" - "+obj.nodeType);
+ return name in expando ? expando[name].original : undefined;
+ }
+
+ // Overload some XrayWrappers method in order to fix its bugs
+ if (name in methodFixes &&
+ methodFixes[name] != Object.getPrototypeOf(methodFixes)[name] &&
+ name != "__proto__")
+ return methodFixes[name];
+ if (Object.keys(xRayWrappersMethodsFixes).indexOf(name) !== -1) {
+ let fix = xRayWrappersMethodsFixes[name](obj);
+ if (fix)
+ return methodFixes[name] = fix;
+ }
+
+ let o = obj[name];
+
+ // XrayWrapper miss some attributes, try to catch these and return a value
+ if (!o) {
+ for each(let atttributeFixer in xRayWrappersMissFixes) {
+ let fix = atttributeFixer(obj, name);
+ if (fix)
+ return fix;
+ }
+ }
+
+ // Generic case
+ return wrap(o, obj, name);
+
+ },
+
+ set: function(receiver, name, val) {
+
+ if (isEventName(name)) {
+ //console.log("SET on* attribute : " + name + " / " + val + "/" + obj);
+ let shortName = name.replace(/^on/,"");
+
+ // Unregister previously set listener
+ if (expando[name]) {
+ obj.removeEventListener(shortName, expando[name], true);
+ delete expando[name];
+ }
+
+ // Only accept functions
+ if (typeof val != "function")
+ return false;
+
+ // Register a new listener
+ let original = val;
+ val = ContentScriptFunctionWrapper(val);
+ expando[name] = val;
+ val.original = original;
+ obj.addEventListener(name.replace(/^on/, ""), val, true);
+ return true;
+ }
+
+ obj[name] = val;
+
+ // Handle native method not overloaded on XrayWrappers:
+ // obj.addEventListener = val; -> obj.addEventlistener = native method
+ // And, XPCNativeWrapper bug where nested values appear to be wrapped:
+ // obj.customNestedAttribute = val -> obj.customNestedAttribute !== val
+ // obj.customNestedAttribute = "waive wrapper something"
+ // SEE BUG 658560: Fix identity problem with CrossOriginWrappers
+ // TODO: check that DOM can't be updated by the document itself and so overloaded value becomes wrong
+ // but I think such behavior is limited to primitive type
+ if ((typeof val == "function" || typeof val == "object") && name) {
+ overload[name] = val;
+ }
+
+ return true;
+ },
+
+ enumerate: function() {
+ var result = [];
+ for each (let name in Object.keys(obj)) {
+ result.push(name);
+ };
+ return result;
+ },
+
+ keys: function() {
+ return Object.keys(obj);
+ }
+ };
+};
+
+
+/*
+ * Wrap an object from the document to a proxy wrapper.
+ */
+function create(object) {
+ if ("wrappedJSObject" in object)
+ object = object.wrappedJSObject;
+ let xpcWrapper = XPCNativeWrapper(object);
+ // If we can't build an XPCNativeWrapper, it doesn't make sense to build
+ // a proxy. All proxy code is based on having such wrapper that store
+ // different JS attributes set.
+ // (we can't build XPCNativeWrapper when object is from the same
+ // principal/domain)
+ if (object === xpcWrapper) {
+ return object;
+ }
+ return getProxyForObject(xpcWrapper);
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/content/content-worker.js b/tools/addon-sdk-1.12/lib/sdk/content/content-worker.js
new file mode 100644
index 0000000..8cf9c5c
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/content/content-worker.js
@@ -0,0 +1,308 @@
+/* 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 ContentWorker = Object.freeze({
+ // TODO: Bug 727854 Use same implementation than common JS modules,
+ // i.e. EventEmitter module
+
+ /**
+ * Create an EventEmitter instance.
+ */
+ createEventEmitter: function createEventEmitter(emit) {
+ let listeners = Object.create(null);
+ let eventEmitter = Object.freeze({
+ emit: emit,
+ on: function on(name, callback) {
+ if (typeof callback !== "function")
+ return this;
+ if (!(name in listeners))
+ listeners[name] = [];
+ listeners[name].push(callback);
+ return this;
+ },
+ once: function once(name, callback) {
+ eventEmitter.on(name, function onceCallback() {
+ eventEmitter.removeListener(name, onceCallback);
+ callback.apply(callback, arguments);
+ });
+ },
+ removeListener: function removeListener(name, callback) {
+ if (!(name in listeners))
+ return;
+ let index = listeners[name].indexOf(callback);
+ if (index == -1)
+ return;
+ listeners[name].splice(index, 1);
+ }
+ });
+ function onEvent(name) {
+ if (!(name in listeners))
+ return [];
+ let args = Array.slice(arguments, 1);
+ let results = [];
+ for each (let callback in listeners[name]) {
+ results.push(callback.apply(null, args));
+ }
+ return results;
+ }
+ function hasListenerFor(name) {
+ if (!(name in listeners))
+ return false;
+ return listeners[name].length > 0;
+ }
+ return {
+ eventEmitter: eventEmitter,
+ emit: onEvent,
+ hasListenerFor: hasListenerFor
+ };
+ },
+
+ /**
+ * Create an EventEmitter instance to communicate with chrome module
+ * by passing only strings between compartments.
+ * This function expects `emitToChrome` function, that allows to send
+ * events to the chrome module. It returns the EventEmitter as `pipe`
+ * attribute, and, `onChromeEvent` a function that allows chrome module
+ * to send event into the EventEmitter.
+ *
+ * pipe.emit --> emitToChrome
+ * onChromeEvent --> callback registered through pipe.on
+ */
+ createPipe: function createPipe(emitToChrome) {
+ function onEvent() {
+ // Convert to real array
+ let args = Array.slice(arguments);
+ // JSON.stringify is buggy with cross-sandbox values,
+ // it may return "{}" on functions. Use a replacer to match them correctly.
+ function replacer(k, v) {
+ return typeof v === "function" ? undefined : v;
+ }
+ let str = JSON.stringify(args, replacer);
+ emitToChrome(str);
+ }
+
+ let { eventEmitter, emit, hasListenerFor } =
+ ContentWorker.createEventEmitter(onEvent);
+
+ return {
+ pipe: eventEmitter,
+ onChromeEvent: function onChromeEvent(array) {
+ // We either receive a stringified array, or a real array.
+ // We still allow to pass an array of objects, in WorkerSandbox.emitSync
+ // in order to allow sending DOM node reference between content script
+ // and modules (only used for context-menu API)
+ let args = typeof array == "string" ? JSON.parse(array) : array;
+ return emit.apply(null, args);
+ },
+ hasListenerFor: hasListenerFor
+ };
+ },
+
+ injectConsole: function injectConsole(exports, pipe) {
+ exports.console = Object.freeze({
+ log: pipe.emit.bind(null, "console", "log"),
+ info: pipe.emit.bind(null, "console", "info"),
+ warn: pipe.emit.bind(null, "console", "warn"),
+ error: pipe.emit.bind(null, "console", "error"),
+ debug: pipe.emit.bind(null, "console", "debug"),
+ exception: pipe.emit.bind(null, "console", "exception"),
+ trace: pipe.emit.bind(null, "console", "trace")
+ });
+ },
+
+ injectTimers: function injectTimers(exports, chromeAPI, pipe, console) {
+ // wrapped functions from `'timer'` module.
+ // Wrapper adds `try catch` blocks to the callbacks in order to
+ // emit `error` event on a symbiont if exception is thrown in
+ // the Worker global scope.
+ // @see http://www.w3.org/TR/workers/#workerutils
+
+ // List of all living timeouts/intervals
+ let _timers = Object.create(null);
+
+ // Keep a reference to original timeout functions
+ let {
+ setTimeout: chromeSetTimeout,
+ setInterval: chromeSetInterval,
+ clearTimeout: chromeClearTimeout,
+ clearInterval: chromeClearInterval
+ } = chromeAPI.timers;
+
+ function registerTimer(timer) {
+ let registerMethod = null;
+ if (timer.kind == "timeout")
+ registerMethod = chromeSetTimeout;
+ else if (timer.kind == "interval")
+ registerMethod = chromeSetInterval;
+ else
+ throw new Error("Unknown timer kind: " + timer.kind);
+ let id = registerMethod(onFire, timer.delay);
+ function onFire() {
+ try {
+ if (timer.kind == "timeout")
+ delete _timers[id];
+ timer.fun.apply(null, timer.args);
+ } catch(e) {
+ console.exception(e);
+ }
+ }
+ _timers[id] = timer;
+ return id;
+ }
+
+ function unregisterTimer(id) {
+ if (!(id in _timers))
+ return;
+ let { kind } = _timers[id];
+ delete _timers[id];
+ if (kind == "timeout")
+ chromeClearTimeout(id);
+ else if (kind == "interval")
+ chromeClearInterval(id);
+ else
+ throw new Error("Unknown timer kind: " + kind);
+ }
+
+ function disableAllTimers() {
+ Object.keys(_timers).forEach(unregisterTimer);
+ }
+
+ exports.setTimeout = function ContentScriptSetTimeout(callback, delay) {
+ return registerTimer({
+ kind: "timeout",
+ fun: callback,
+ delay: delay,
+ args: Array.slice(arguments, 2)
+ });
+ };
+ exports.clearTimeout = function ContentScriptClearTimeout(id) {
+ unregisterTimer(id);
+ };
+
+ exports.setInterval = function ContentScriptSetInterval(callback, delay) {
+ return registerTimer({
+ kind: "interval",
+ fun: callback,
+ delay: delay,
+ args: Array.slice(arguments, 2)
+ });
+ };
+ exports.clearInterval = function ContentScriptClearInterval(id) {
+ unregisterTimer(id);
+ };
+
+ // On page-hide, save a list of all existing timers before disabling them,
+ // in order to be able to restore them on page-show.
+ // These events are fired when the page goes in/out of bfcache.
+ // https://developer.mozilla.org/En/Working_with_BFCache
+ let frozenTimers = [];
+ pipe.on("pageshow", function onPageShow() {
+ frozenTimers.forEach(registerTimer);
+ });
+ pipe.on("pagehide", function onPageHide() {
+ frozenTimers = [];
+ for (let id in _timers)
+ frozenTimers.push(_timers[id]);
+ disableAllTimers();
+ // Some other pagehide listeners may register some timers that won't be
+ // frozen as this particular pagehide listener is called first.
+ // So freeze these timers on next cycle.
+ chromeSetTimeout(function () {
+ for (let id in _timers)
+ frozenTimers.push(_timers[id]);
+ disableAllTimers();
+ }, 0);
+ });
+
+ // Unregister all timers when the page is destroyed
+ // (i.e. when it is removed from bfcache)
+ pipe.on("detach", function clearTimeouts() {
+ disableAllTimers();
+ _timers = {};
+ frozenTimers = [];
+ });
+ },
+
+ injectMessageAPI: function injectMessageAPI(exports, pipe) {
+
+ let { eventEmitter: port, emit : portEmit } =
+ ContentWorker.createEventEmitter(pipe.emit.bind(null, "event"));
+ pipe.on("event", portEmit);
+
+ let self = {
+ port: port,
+ postMessage: pipe.emit.bind(null, "message"),
+ on: pipe.on.bind(null),
+ once: pipe.once.bind(null),
+ removeListener: pipe.removeListener.bind(null),
+ };
+ Object.defineProperty(exports, "self", {
+ value: self
+ });
+
+ // Deprecated use of on/postMessage from globals
+ exports.postMessage = function deprecatedPostMessage() {
+ console.error("DEPRECATED: The global `postMessage()` function in " +
+ "content scripts is deprecated in favor of the " +
+ "`self.postMessage()` function, which works the same. " +
+ "Replace calls to `postMessage()` with calls to " +
+ "`self.postMessage()`." +
+ "For more info on `self.on`, see " +
+ "<https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/addon-development/web-content.html>.");
+ return self.postMessage.apply(null, arguments);
+ };
+ exports.on = function deprecatedOn() {
+ console.error("DEPRECATED: The global `on()` function in content " +
+ "scripts is deprecated in favor of the `self.on()` " +
+ "function, which works the same. Replace calls to `on()` " +
+ "with calls to `self.on()`" +
+ "For more info on `self.on`, see " +
+ "<https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/addon-development/web-content.html>.");
+ return self.on.apply(null, arguments);
+ };
+
+ // Deprecated use of `onMessage` from globals
+ let onMessage = null;
+ Object.defineProperty(exports, "onMessage", {
+ get: function () onMessage,
+ set: function (v) {
+ if (onMessage)
+ self.removeListener("message", onMessage);
+ console.error("DEPRECATED: The global `onMessage` function in content" +
+ "scripts is deprecated in favor of the `self.on()` " +
+ "function. Replace `onMessage = function (data){}` " +
+ "definitions with calls to `self.on('message', " +
+ "function (data){})`. " +
+ "For more info on `self.on`, see " +
+ "<https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/addon-development/web-content.html>.");
+ onMessage = v;
+ if (typeof onMessage == "function")
+ self.on("message", onMessage);
+ }
+ });
+ },
+
+ injectOptions: function (exports, options) {
+ Object.defineProperty( exports.self, "options", { value: JSON.parse( options ) });
+ },
+
+ inject: function (exports, chromeAPI, emitToChrome, options) {
+ let { pipe, onChromeEvent, hasListenerFor } =
+ ContentWorker.createPipe(emitToChrome);
+
+ ContentWorker.injectConsole(exports, pipe);
+ ContentWorker.injectTimers(exports, chromeAPI, pipe, exports.console);
+ ContentWorker.injectMessageAPI(exports, pipe);
+ if ( options !== undefined ) {
+ ContentWorker.injectOptions(exports, options);
+ }
+
+ Object.freeze( exports.self );
+
+ return {
+ emitToContent: onChromeEvent,
+ hasListenerFor: hasListenerFor
+ };
+ }
+});
diff --git a/tools/addon-sdk-1.12/lib/sdk/content/content.js b/tools/addon-sdk-1.12/lib/sdk/content/content.js
new file mode 100644
index 0000000..4bffd45
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/content/content.js
@@ -0,0 +1,15 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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"
+};
+
+exports.Loader = require('./loader').Loader;
+exports.Symbiont = require('./symbiont').Symbiont;
+exports.Worker = require('./worker').Worker;
+
diff --git a/tools/addon-sdk-1.12/lib/sdk/content/loader.js b/tools/addon-sdk-1.12/lib/sdk/content/loader.js
new file mode 100644
index 0000000..b01675c
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/content/loader.js
@@ -0,0 +1,204 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 { validateOptions } = require('../deprecated/api-utils');
+const { URL } = require('../url');
+const file = require('../io/file');
+
+const LOCAL_URI_SCHEMES = ['resource', 'data'];
+
+// Returns `null` if `value` is `null` or `undefined`, otherwise `value`.
+function ensureNull(value) {
+ return value == null ? null : value;
+}
+
+// map of property validations
+const valid = {
+ contentURL: {
+ ok: function (value) {
+ try {
+ URL(value);
+ }
+ catch(e) {
+ return false;
+ }
+ return true;
+ },
+ msg: 'The `contentURL` option must be a valid URL.'
+ },
+ contentScriptFile: {
+ is: ['undefined', 'null', 'string', 'array'],
+ map: ensureNull,
+ ok: function(value) {
+ if (value === null)
+ return true;
+
+ value = [].concat(value);
+
+ // Make sure every item is a local file URL.
+ return value.every(function (item) {
+ try {
+ return ~LOCAL_URI_SCHEMES.indexOf(URL(item).scheme);
+ }
+ catch(e) {
+ return false;
+ }
+ });
+
+ },
+ msg: 'The `contentScriptFile` option must be a local URL or an array of URLs.'
+ },
+ contentScript: {
+ is: ['undefined', 'null', 'string', 'array'],
+ map: ensureNull,
+ ok: function(value) {
+ return !Array.isArray(value) || value.every(
+ function(item) { return typeof item === 'string' }
+ );
+ },
+ msg: 'The `contentScript` option must be a string or an array of strings.'
+ },
+ contentScriptWhen: {
+ is: ['string'],
+ ok: function(value) { return ~['start', 'ready', 'end'].indexOf(value) },
+ map: function(value) {
+ return value || 'end';
+ },
+ msg: 'The `contentScriptWhen` option must be either "start", "ready" or "end".'
+ },
+ contentScriptOptions: {
+ ok: function(value) {
+ if ( value === undefined ) { return true; }
+ try { JSON.parse( JSON.stringify( value ) ); } catch(e) { return false; }
+ return true;
+ },
+ map: function(value) 'undefined' === getTypeOf(value) ? null : value,
+ msg: 'The contentScriptOptions should be a jsonable value.'
+ }
+};
+exports.validationAttributes = valid;
+
+/**
+ * Shortcut function to validate property with validation.
+ * @param {Object|Number|String} suspect
+ * value to validate
+ * @param {Object} validation
+ * validation rule passed to `api-utils`
+ */
+function validate(suspect, validation) validateOptions(
+ { $: suspect },
+ { $: validation }
+).$
+
+function Allow(script) ({
+ get script() script,
+ set script(value) script = !!value
+})
+
+/**
+ * Trait is intended to be used in some composition. It provides set of core
+ * properties and bounded validations to them. Trait is useful for all the
+ * compositions providing high level APIs for interaction with content.
+ * Property changes emit `"propertyChange"` events on instances.
+ */
+const Loader = EventEmitter.compose({
+ /**
+ * Permissions for the content, with the following keys:
+ * @property {Object} [allow = { script: true }]
+ * @property {Boolean} [allow.script = true]
+ * Whether or not to execute script in the content. Defaults to true.
+ */
+ get allow() this._allow || (this._allow = Allow(true)),
+ set allow(value) this.allow.script = value && value.script,
+ _allow: null,
+ /**
+ * The content to load. Either a string of HTML or a URL.
+ * @type {String}
+ */
+ get contentURL() this._contentURL,
+ set contentURL(value) {
+ value = validate(value, valid.contentURL);
+ if (this._contentURL != value) {
+ this._emit('propertyChange', {
+ contentURL: this._contentURL = value
+ });
+ }
+ },
+ _contentURL: null,
+ /**
+ * When to load the content scripts.
+ * Possible values are "end" (default), which loads them once all page
+ * contents have been loaded, "ready", which loads them once DOM nodes are
+ * ready (ie like DOMContentLoaded event), and "start", which loads them once
+ * the `window` object for the page has been created, but before any scripts
+ * specified by the page have been loaded.
+ * Property change emits `propertyChange` event on instance with this key
+ * and new value.
+ * @type {'start'|'ready'|'end'}
+ */
+ get contentScriptWhen() this._contentScriptWhen,
+ set contentScriptWhen(value) {
+ value = validate(value, valid.contentScriptWhen);
+ if (value !== this._contentScriptWhen) {
+ this._emit('propertyChange', {
+ contentScriptWhen: this._contentScriptWhen = value
+ });
+ }
+ },
+ _contentScriptWhen: 'end',
+ /**
+ * Options avalaible from the content script as `self.options`.
+ * The value of options can be of any type (object, array, string, etc.)
+ * but only jsonable values will be available as frozen objects from the
+ * content script.
+ * Property change emits `propertyChange` event on instance with this key
+ * and new value.
+ * @type {Object}
+ */
+ get contentScriptOptions() this._contentScriptOptions,
+ set contentScriptOptions(value) this._contentScriptOptions = value,
+ _contentScriptOptions: null,
+ /**
+ * The URLs of content scripts.
+ * Property change emits `propertyChange` event on instance with this key
+ * and new value.
+ * @type {String[]}
+ */
+ get contentScriptFile() this._contentScriptFile,
+ set contentScriptFile(value) {
+ value = validate(value, valid.contentScriptFile);
+ if (value != this._contentScriptFile) {
+ this._emit('propertyChange', {
+ contentScriptFile: this._contentScriptFile = value
+ });
+ }
+ },
+ _contentScriptFile: null,
+ /**
+ * The texts of content script.
+ * Property change emits `propertyChange` event on instance with this key
+ * and new value.
+ * @type {String|undefined}
+ */
+ get contentScript() this._contentScript,
+ set contentScript(value) {
+ value = validate(value, valid.contentScript);
+ if (value != this._contentScript) {
+ this._emit('propertyChange', {
+ contentScript: this._contentScript = value
+ });
+ }
+ },
+ _contentScript: null
+});
+exports.Loader = Loader;
diff --git a/tools/addon-sdk-1.12/lib/sdk/content/symbiont.js b/tools/addon-sdk-1.12/lib/sdk/content/symbiont.js
new file mode 100644
index 0000000..186e9d3
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/content/symbiont.js
@@ -0,0 +1,197 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 { Worker } = require('./worker');
+const { Loader } = require('./loader');
+const hiddenFrames = require('../frame/hidden-frame');
+const observers = require('../deprecated/observer-service');
+const unload = require('../system/unload');
+
+const assetsURI = require('../self').data.url();
+
+/**
+ * This trait is layered on top of `Worker` and in contrast to symbiont
+ * Worker constructor requires `content` option that represents content
+ * that will be loaded in the provided frame, if frame is not provided
+ * Worker will create hidden one.
+ */
+const Symbiont = Worker.resolve({
+ constructor: '_initWorker',
+ destroy: '_workerDestroy'
+ }).compose(Loader, {
+
+ /**
+ * The constructor requires all the options that are required by
+ * `require('content').Worker` with the difference that the `frame` option
+ * is optional. If `frame` is not provided, `contentURL` is expected.
+ * @param {Object} options
+ * @param {String} options.contentURL
+ * URL of a content to load into `this._frame` and create worker for.
+ * @param {Element} [options.frame]
+ * iframe element that is used to load `options.contentURL` into.
+ * if frame is not provided hidden iframe will be created.
+ */
+ constructor: function Symbiont(options) {
+ options = options || {};
+
+ if ('contentURL' in options)
+ this.contentURL = options.contentURL;
+ if ('contentScriptWhen' in options)
+ this.contentScriptWhen = options.contentScriptWhen;
+ if ('contentScriptOptions' in options)
+ this.contentScriptOptions = options.contentScriptOptions;
+ if ('contentScriptFile' in options)
+ this.contentScriptFile = options.contentScriptFile;
+ if ('contentScript' in options)
+ this.contentScript = options.contentScript;
+ if ('allow' in options)
+ this.allow = options.allow;
+ if ('onError' in options)
+ this.on('error', options.onError);
+ if ('onMessage' in options)
+ this.on('message', options.onMessage);
+ if ('frame' in options) {
+ this._initFrame(options.frame);
+ }
+ else {
+ let self = this;
+ this._hiddenFrame = hiddenFrames.HiddenFrame({
+ onReady: function onFrame() {
+ self._initFrame(this.element);
+ },
+ onUnload: function onUnload() {
+ // Bug 751211: Remove reference to _frame when hidden frame is
+ // automatically removed on unload, otherwise we are going to face
+ // "dead object" exception
+ self.destroy();
+ }
+ });
+ hiddenFrames.add(this._hiddenFrame);
+ }
+
+ unload.ensure(this._public, "destroy");
+ },
+
+ destroy: function destroy() {
+ this._workerDestroy();
+ this._unregisterListener();
+ this._frame = null;
+ if (this._hiddenFrame) {
+ hiddenFrames.remove(this._hiddenFrame);
+ this._hiddenFrame = null;
+ }
+ },
+
+ /**
+ * XUL iframe or browser elements with attribute `type` being `content`.
+ * Used to create `ContentSymbiont` from.
+ * @type {nsIFrame|nsIBrowser}
+ */
+ _frame: null,
+
+ /**
+ * Listener to the `'frameReady"` event (emitted when `iframe` is ready).
+ * Removes listener, sets right permissions to the frame and loads content.
+ */
+ _initFrame: function _initFrame(frame) {
+ if (this._loadListener)
+ this._unregisterListener();
+
+ this._frame = frame;
+ frame.docShell.allowJavascript = this.allow.script;
+ frame.setAttribute("src", this._contentURL);
+
+ // Inject `addon` object in document if we load a document from
+ // one of our addon folder and if no content script are defined. bug 612726
+ let isDataResource =
+ typeof this._contentURL == "string" &&
+ this._contentURL.indexOf(assetsURI) == 0;
+ let hasContentScript =
+ (Array.isArray(this.contentScript) ? this.contentScript.length > 0
+ : !!this.contentScript) ||
+ (Array.isArray(this.contentScriptFile) ? this.contentScriptFile.length > 0
+ : !!this.contentScriptFile);
+ // If we have to inject `addon` we have to do it before document
+ // script execution, so during `start`:
+ this._injectInDocument = isDataResource && !hasContentScript;
+ if (this._injectInDocument)
+ this.contentScriptWhen = "start";
+
+ if ((frame.contentDocument.readyState == "complete" ||
+ (frame.contentDocument.readyState == "interactive" &&
+ this.contentScriptWhen != 'end' )) &&
+ frame.contentDocument.location == this._contentURL) {
+ // In some cases src doesn't change and document is already ready
+ // (for ex: when the user moves a widget while customizing toolbars.)
+ this._onInit();
+ return;
+ }
+
+ let self = this;
+
+ if ('start' == this.contentScriptWhen) {
+ this._loadEvent = 'start';
+ observers.add('document-element-inserted',
+ this._loadListener = function onStart(doc) {
+
+ let window = doc.defaultView;
+ if (window && window == frame.contentWindow) {
+ self._unregisterListener();
+ self._onInit();
+ }
+
+ });
+ return;
+ }
+
+ let eventName = 'end' == this.contentScriptWhen ? 'load' : 'DOMContentLoaded';
+ let self = this;
+ this._loadEvent = eventName;
+ frame.addEventListener(eventName,
+ this._loadListener = function _onReady(event) {
+
+ if (event.target != frame.contentDocument)
+ return;
+ self._unregisterListener();
+
+ self._onInit();
+
+ }, true);
+
+ },
+
+ /**
+ * Unregister listener that watchs for document being ready to be injected.
+ * This listener is registered in `Symbiont._initFrame`.
+ */
+ _unregisterListener: function _unregisterListener() {
+ if (!this._loadListener)
+ return;
+ if (this._loadEvent == "start") {
+ observers.remove('document-element-inserted', this._loadListener);
+ }
+ else {
+ this._frame.removeEventListener(this._loadEvent, this._loadListener,
+ true);
+ }
+ this._loadListener = null;
+ },
+
+ /**
+ * Called by Symbiont itself when the frame is ready to load
+ * content scripts according to contentScriptWhen. Overloaded by Panel.
+ */
+ _onInit: function () {
+ this._initWorker({ window: this._frame.contentWindow });
+ }
+
+});
+exports.Symbiont = Symbiont;
diff --git a/tools/addon-sdk-1.12/lib/sdk/content/thumbnail.js b/tools/addon-sdk-1.12/lib/sdk/content/thumbnail.js
new file mode 100644
index 0000000..9e57274
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/content/thumbnail.js
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+module.metadata = {
+ 'stability': 'unstable'
+};
+
+const { Cc, Ci, Cu } = require('chrome');
+const AppShellService = Cc['@mozilla.org/appshell/appShellService;1'].
+ getService(Ci.nsIAppShellService);
+
+const NS = 'http://www.w3.org/1999/xhtml';
+const COLOR = 'rgb(255,255,255)';
+
+/**
+ * Creates canvas element with a thumbnail of the passed window.
+ * @param {Window} window
+ * @returns {Element}
+ */
+function getThumbnailCanvasForWindow(window) {
+ let aspectRatio = 0.5625; // 16:9
+ let thumbnail = AppShellService.hiddenDOMWindow.document
+ .createElementNS(NS, 'canvas');
+ thumbnail.mozOpaque = true;
+ thumbnail.width = Math.ceil(window.screen.availWidth / 5.75);
+ thumbnail.height = Math.round(thumbnail.width * aspectRatio);
+ let ctx = thumbnail.getContext('2d');
+ let snippetWidth = window.innerWidth * .6;
+ let scale = thumbnail.width / snippetWidth;
+ ctx.scale(scale, scale);
+ ctx.drawWindow(window, window.scrollX, window.scrollY, snippetWidth,
+ snippetWidth * aspectRatio, COLOR);
+ return thumbnail;
+}
+exports.getThumbnailCanvasForWindow = getThumbnailCanvasForWindow;
+
+/**
+ * Creates Base64 encoded data URI of the thumbnail for the passed window.
+ * @param {Window} window
+ * @returns {String}
+ */
+exports.getThumbnailURIForWindow = function getThumbnailURIForWindow(window) {
+ return getThumbnailCanvasForWindow(window).toDataURL()
+};
diff --git a/tools/addon-sdk-1.12/lib/sdk/content/worker.js b/tools/addon-sdk-1.12/lib/sdk/content/worker.js
new file mode 100644
index 0000000..b2f204c
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/content/worker.js
@@ -0,0 +1,582 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 { Trait } = require('../deprecated/traits');
+const { EventEmitter, EventEmitterTrait } = require('../deprecated/events');
+const { Ci, Cu, Cc } = require('chrome');
+const timer = require('../timers');
+const { URL } = require('../url');
+const unload = require('../system/unload');
+const observers = require('../deprecated/observer-service');
+const { Cortex } = require('../deprecated/cortex');
+const { sandbox, evaluate, load } = require("../loader/sandbox");
+const { merge } = require('../util/object');
+const xulApp = require("../system/xul-app");
+const USE_JS_PROXIES = !xulApp.versionInRange(xulApp.platformVersion,
+ "17.0a2", "*");
+const { getTabForWindow } = require('../tabs/helpers');
+
+/* Trick the linker in order to ensure shipping these files in the XPI.
+ require('./content-proxy.js');
+ require('./content-worker.js');
+ Then, retrieve URL of these files in the XPI:
+*/
+let prefix = module.uri.split('worker.js')[0];
+const CONTENT_PROXY_URL = prefix + 'content-proxy.js';
+const CONTENT_WORKER_URL = prefix + 'content-worker.js';
+
+const JS_VERSION = '1.8';
+
+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.";
+
+/**
+ * This key is not exported and should only be used for proxy tests.
+ * The following `PRIVATE_KEY` is used in addon module scope in order to tell
+ * Worker API to expose `UNWRAP_ACCESS_KEY` in content script.
+ * This key allows test-content-proxy.js to unwrap proxy with valueOf:
+ * let xpcWrapper = proxyWrapper.valueOf(UNWRAP_ACCESS_KEY);
+ */
+const PRIVATE_KEY = {};
+
+
+const WorkerSandbox = EventEmitter.compose({
+
+ /**
+ * Emit a message to the worker content sandbox
+ */
+ emit: function emit() {
+ // First ensure having a regular array
+ // (otherwise, `arguments` would be mapped to an object by `stringify`)
+ let array = Array.slice(arguments);
+ // JSON.stringify is buggy with cross-sandbox values,
+ // it may return "{}" on functions. Use a replacer to match them correctly.
+ function replacer(k, v) {
+ return typeof v === "function" ? undefined : v;
+ }
+ // Ensure having an asynchronous behavior
+ let self = this;
+ timer.setTimeout(function () {
+ self._emitToContent(JSON.stringify(array, replacer));
+ }, 0);
+ },
+
+ /**
+ * Synchronous version of `emit`.
+ * /!\ Should only be used when it is strictly mandatory /!\
+ * Doesn't ensure passing only JSON values.
+ * Mainly used by context-menu in order to avoid breaking it.
+ */
+ emitSync: function emitSync() {
+ let args = Array.slice(arguments);
+ // Bug 732716: Ensure wrapping xrays sent to the content script
+ // otherwise it will have access to raw xraywrappers and content script
+ // will assume it is an user object coming from the content script sandbox
+ if ("_wrap" in this)
+ args = args.map(this._wrap);
+ return this._emitToContent(args);
+ },
+
+ /**
+ * Tells if content script has at least one listener registered for one event,
+ * through `self.on('xxx', ...)`.
+ * /!\ Shouldn't be used. Implemented to avoid breaking context-menu API.
+ */
+ hasListenerFor: function hasListenerFor(name) {
+ return this._hasListenerFor(name);
+ },
+
+ /**
+ * Method called by the worker sandbox when it needs to send a message
+ */
+ _onContentEvent: function onContentEvent(args) {
+ // As `emit`, we ensure having an asynchronous behavior
+ let self = this;
+ timer.setTimeout(function () {
+ // We emit event to chrome/addon listeners
+ self._emit.apply(self, JSON.parse(args));
+ }, 0);
+ },
+
+ /**
+ * Configures sandbox and loads content scripts into it.
+ * @param {Worker} worker
+ * content worker
+ */
+ constructor: function WorkerSandbox(worker) {
+ this._addonWorker = worker;
+
+ // Ensure that `emit` has always the right `this`
+ this.emit = this.emit.bind(this);
+ this.emitSync = this.emitSync.bind(this);
+
+ // We receive a wrapped window, that may be an xraywrapper if it's content
+ let window = worker._window;
+ let proto = window;
+
+ // Instantiate trusted code in another Sandbox in order to prevent content
+ // script from messing with standard classes used by proxy and API code.
+ let apiSandbox = sandbox(window, { wantXrays: true });
+
+ // Build content proxies only if the document has a non-system principal
+ // And only on old firefox versions that doesn't ship bug 738244
+ if (USE_JS_PROXIES && XPCNativeWrapper.unwrap(window) !== window) {
+ apiSandbox.console = console;
+ // Execute the proxy code
+ load(apiSandbox, CONTENT_PROXY_URL);
+ // Get a reference of the window's proxy
+ proto = apiSandbox.create(window);
+ // Keep a reference to `wrap` function for `emitSync` usage
+ this._wrap = apiSandbox.wrap;
+ }
+
+ // Create the sandbox and bind it to window in order for content scripts to
+ // have access to all standard globals (window, document, ...)
+ let content = this._sandbox = sandbox(window, {
+ sandboxPrototype: proto,
+ wantXrays: true
+ });
+ // We have to ensure that window.top and window.parent are the exact same
+ // object than window object, i.e. the sandbox global object. But not
+ // always, in case of iframes, top and parent are another window object.
+ let top = window.top === window ? content : content.top;
+ let parent = window.parent === window ? content : content.parent;
+ merge(content, {
+ // We need "this === window === top" to be true in toplevel scope:
+ get window() content,
+ get top() top,
+ get parent() parent,
+ // Use the Greasemonkey naming convention to provide access to the
+ // unwrapped window object so the content script can access document
+ // JavaScript values.
+ // NOTE: this functionality is experimental and may change or go away
+ // at any time!
+ get unsafeWindow() window.wrappedJSObject
+ });
+
+ // Load trusted code that will inject content script API.
+ // We need to expose JS objects defined in same principal in order to
+ // avoid having any kind of wrapper.
+ load(apiSandbox, CONTENT_WORKER_URL);
+
+ // prepare a clean `self.options`
+ let options = 'contentScriptOptions' in worker ?
+ JSON.stringify( worker.contentScriptOptions ) :
+ undefined;
+
+ // Then call `inject` method and communicate with this script
+ // by trading two methods that allow to send events to the other side:
+ // - `onEvent` called by content script
+ // - `result.emitToContent` called by addon script
+ // Bug 758203: We have to explicitely define `__exposedProps__` in order
+ // to allow access to these chrome object attributes from this sandbox with
+ // content priviledges
+ // https://developer.mozilla.org/en/XPConnect_wrappers#Other_security_wrappers
+ let chromeAPI = {
+ timers: {
+ setTimeout: timer.setTimeout,
+ setInterval: timer.setInterval,
+ clearTimeout: timer.clearTimeout,
+ clearInterval: timer.clearInterval,
+ __exposedProps__: {
+ setTimeout: 'r',
+ setInterval: 'r',
+ clearTimeout: 'r',
+ clearInterval: 'r'
+ }
+ },
+ __exposedProps__: {
+ timers: 'r'
+ }
+ };
+ let onEvent = this._onContentEvent.bind(this);
+ // `ContentWorker` is defined in CONTENT_WORKER_URL file
+ let result = apiSandbox.ContentWorker.inject(content, chromeAPI, onEvent, options);
+ this._emitToContent = result.emitToContent;
+ this._hasListenerFor = result.hasListenerFor;
+
+ // Handle messages send by this script:
+ let self = this;
+ // console.xxx calls
+ this.on("console", function consoleListener(kind) {
+ console[kind].apply(console, Array.slice(arguments, 1));
+ });
+
+ // self.postMessage calls
+ this.on("message", function postMessage(data) {
+ // destroyed?
+ if (self._addonWorker)
+ self._addonWorker._emit('message', data);
+ });
+
+ // self.port.emit calls
+ this.on("event", function portEmit(name, args) {
+ // destroyed?
+ if (self._addonWorker)
+ self._addonWorker._onContentScriptEvent.apply(self._addonWorker, arguments);
+ });
+
+ // Internal feature that is only used by SDK tests:
+ // Expose unlock key to content script context.
+ // See `PRIVATE_KEY` definition for more information.
+ if (apiSandbox && worker._expose_key)
+ content.UNWRAP_ACCESS_KEY = apiSandbox.UNWRAP_ACCESS_KEY;
+
+ // Inject `addon` global into target document if document is trusted,
+ // `addon` in document is equivalent to `self` in content script.
+ if (worker._injectInDocument) {
+ let win = window.wrappedJSObject ? window.wrappedJSObject : window;
+ Object.defineProperty(win, "addon", {
+ value: content.self
+ }
+ );
+ }
+
+ // The order of `contentScriptFile` and `contentScript` evaluation is
+ // intentional, so programs can load libraries like jQuery from script URLs
+ // and use them in scripts.
+ let contentScriptFile = ('contentScriptFile' in worker) ? worker.contentScriptFile
+ : null,
+ contentScript = ('contentScript' in worker) ? worker.contentScript : null;
+
+ if (contentScriptFile) {
+ if (Array.isArray(contentScriptFile))
+ this._importScripts.apply(this, contentScriptFile);
+ else
+ this._importScripts(contentScriptFile);
+ }
+ if (contentScript) {
+ this._evaluate(
+ Array.isArray(contentScript) ? contentScript.join(';\n') : contentScript
+ );
+ }
+ },
+ destroy: function destroy() {
+ this.emitSync("detach");
+ this._sandbox = null;
+ this._addonWorker = null;
+ this._wrap = null;
+ },
+
+ /**
+ * JavaScript sandbox where all the content scripts are evaluated.
+ * {Sandbox}
+ */
+ _sandbox: null,
+
+ /**
+ * Reference to the addon side of the worker.
+ * @type {Worker}
+ */
+ _addonWorker: null,
+
+ /**
+ * Evaluates code in the sandbox.
+ * @param {String} code
+ * JavaScript source to evaluate.
+ * @param {String} [filename='javascript:' + code]
+ * Name of the file
+ */
+ _evaluate: function(code, filename) {
+ try {
+ evaluate(this._sandbox, code, filename || 'javascript:' + code);
+ }
+ catch(e) {
+ this._addonWorker._emit('error', e);
+ }
+ },
+ /**
+ * Imports scripts to the sandbox by reading files under urls and
+ * evaluating its source. If exception occurs during evaluation
+ * `"error"` event is emitted on the worker.
+ * This is actually an analog to the `importScript` method in web
+ * workers but in our case it's not exposed even though content
+ * scripts may be able to do it synchronously since IO operation
+ * takes place in the UI process.
+ */
+ _importScripts: function _importScripts(url) {
+ let urls = Array.slice(arguments, 0);
+ for each (let contentScriptFile in urls) {
+ try {
+ let uri = URL(contentScriptFile);
+ if (uri.scheme === 'resource')
+ load(this._sandbox, String(uri));
+ else
+ throw Error("Unsupported `contentScriptFile` url: " + String(uri));
+ }
+ catch(e) {
+ this._addonWorker._emit('error', e);
+ }
+ }
+ }
+});
+
+/**
+ * Message-passing facility for communication between code running
+ * in the content and add-on process.
+ * @see https://jetpack.mozillalabs.com/sdk/latest/docs/#module/api-utils/content/worker
+ */
+const Worker = EventEmitter.compose({
+ on: Trait.required,
+ _removeAllListeners: Trait.required,
+
+ /**
+ * 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 postMessage(data) {
+ if (!this._contentWorker)
+ throw new Error(ERR_DESTROYED);
+ if (this._frozen)
+ throw new Error(ERR_FROZEN);
+
+ this._contentWorker.emit("message", data);
+ },
+
+ /**
+ * EventEmitter, that behaves (calls listeners) asynchronously.
+ * A way to send customized messages to / from the worker.
+ * Events from in the worker can be observed / emitted via
+ * worker.on / worker.emit.
+ */
+ get port() {
+ // We generate dynamically this attribute as it needs to be accessible
+ // before Worker.constructor gets called. (For ex: Panel)
+
+ // create an event emitter that receive and send events from/to the worker
+ let self = this;
+ this._port = EventEmitterTrait.create({
+ emit: function () self._emitEventToContent(Array.slice(arguments))
+ });
+
+ // expose wrapped port, that exposes only public properties:
+ // We need to destroy this getter in order to be able to set the
+ // final value. We need to update only public port attribute as we never
+ // try to access port attribute from private API.
+ delete this._public.port;
+ this._public.port = Cortex(this._port);
+ // Replicate public port to the private object
+ delete this.port;
+ this.port = this._public.port;
+
+ return this._port;
+ },
+
+ /**
+ * Same object than this.port but private API.
+ * Allow access to _emit, in order to send event to port.
+ */
+ _port: null,
+
+ /**
+ * Emit a custom event to the content script,
+ * i.e. emit this event on `self.port`
+ */
+ _emitEventToContent: function _emitEventToContent(args) {
+ // We need to save events that are emitted before the worker is
+ // initialized
+ if (!this._inited) {
+ this._earlyEvents.push(args);
+ return;
+ }
+
+ if (this._frozen)
+ throw new Error(ERR_FROZEN);
+
+ // We throw exception when the worker has been destroyed
+ if (!this._contentWorker) {
+ throw new Error(ERR_DESTROYED);
+ }
+
+ // Forward the event to the WorkerSandbox object
+ this._contentWorker.emit.apply(null, ["event"].concat(args));
+ },
+
+ // 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,
+
+ // List of custom events fired before worker is initialized
+ get _earlyEvents() {
+ delete this._earlyEvents;
+ this._earlyEvents = [];
+ return this._earlyEvents;
+ },
+
+ constructor: function Worker(options) {
+ options = options || {};
+
+ if ('window' in options)
+ this._window = options.window;
+ 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 ('onError' in options)
+ this.on('error', options.onError);
+ if ('onMessage' in options)
+ this.on('message', options.onMessage);
+ if ('onDetach' in options)
+ this.on('detach', options.onDetach);
+
+ // Internal feature that is only used by SDK unit tests.
+ // See `PRIVATE_KEY` definition for more information.
+ if ('exposeUnlockKey' in options && options.exposeUnlockKey === PRIVATE_KEY)
+ this._expose_key = true;
+
+ // 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
+ this._windowID = this._window.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).
+ currentInnerWindowID;
+ observers.add("inner-window-destroyed",
+ this._documentUnload = this._documentUnload.bind(this));
+
+ // Listen to pagehide event in order to freeze the content script
+ // while the document is frozen in bfcache:
+ this._window.addEventListener("pageshow",
+ this._pageShow = this._pageShow.bind(this),
+ true);
+ this._window.addEventListener("pagehide",
+ this._pageHide = this._pageHide.bind(this),
+ true);
+
+ unload.ensure(this._public, "destroy");
+
+ // Ensure that worker._port is initialized for contentWorker to be able
+ // to send use event during WorkerSandbox(this)
+ this.port;
+
+ // will set this._contentWorker pointing to the private API:
+ this._contentWorker = WorkerSandbox(this);
+
+ // Mainly enable worker.port.emit to send event to the content worker
+ this._inited = true;
+ this._frozen = false;
+
+ // Flush all events that have been fired before the worker is initialized.
+ this._earlyEvents.forEach((function (args) this._emitEventToContent(args)).
+ bind(this));
+ },
+
+ _documentUnload: function _documentUnload(subject, topic, data) {
+ let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
+ if (innerWinID != this._windowID) return false;
+ this._workerCleanup();
+ return true;
+ },
+
+ _pageShow: function _pageShow() {
+ this._contentWorker.emitSync("pageshow");
+ this._emit("pageshow");
+ this._frozen = false;
+ },
+
+ _pageHide: function _pageHide() {
+ this._contentWorker.emitSync("pagehide");
+ this._emit("pagehide");
+ this._frozen = true;
+ },
+
+ get url() {
+ // this._window will be null after detach
+ return this._window ? this._window.document.location.href : null;
+ },
+
+ get tab() {
+ // this._window will be null after detach
+ if (this._window)
+ return getTabForWindow(this._window);
+ return null;
+ },
+
+ /**
+ * Tells content worker to unload itself and
+ * removes all the references from itself.
+ */
+ destroy: function destroy() {
+ this._workerCleanup();
+ this._removeAllListeners();
+ },
+
+ /**
+ * Remove all internal references to the attached document
+ * Tells _port to unload itself and removes all the references from itself.
+ */
+ _workerCleanup: function _workerCleanup() {
+ // maybe unloaded before content side is created
+ // As Symbiont call worker.constructor on document load
+ if (this._contentWorker)
+ this._contentWorker.destroy();
+ this._contentWorker = null;
+ if (this._window) {
+ this._window.removeEventListener("pageshow", this._pageShow, true);
+ this._window.removeEventListener("pagehide", this._pageHide, true);
+ }
+ this._window = null;
+ // This method may be called multiple times,
+ // avoid dispatching `detach` event more than once
+ if (this._windowID) {
+ this._windowID = null;
+ observers.remove("inner-window-destroyed", this._documentUnload);
+ this._earlyEvents.slice(0, this._earlyEvents.length);
+ this._emit("detach");
+ }
+ },
+
+ /**
+ * Receive an event from the content script that need to be sent to
+ * worker.port. Provide a way for composed object to catch all events.
+ */
+ _onContentScriptEvent: function _onContentScriptEvent() {
+ this._port._emit.apply(this._port, arguments);
+ },
+
+ /**
+ * 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,
+
+ /**
+ * Flag to enable `addon` object injection in document. (bug 612726)
+ * @type {Boolean}
+ */
+ _injectInDocument: false
+});
+exports.Worker = Worker;
diff --git a/tools/addon-sdk-1.12/lib/sdk/context-menu.js b/tools/addon-sdk-1.12/lib/sdk/context-menu.js
new file mode 100644
index 0000000..9d20f0b
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/context-menu.js
@@ -0,0 +1,1494 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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": "stable"
+};
+
+const {Ci} = require("chrome");
+
+if (!require("./system/xul-app").is("Firefox")) {
+ throw new Error([
+ "The context-menu module currently supports only Firefox. In the future ",
+ "we would like it to support other applications, however. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=560716 for more information."
+ ].join(""));
+}
+
+const apiUtils = require("./deprecated/api-utils");
+const collection = require("./util/collection");
+const { Worker } = require("./content/worker");
+const { URL } = require("./url");
+const { MatchPattern } = require("./page-mod/match-pattern");
+const { EventEmitterTrait: EventEmitter } = require("./deprecated/events");
+const observerServ = require("./deprecated/observer-service");
+const jpSelf = require("./self");
+const { WindowTracker } = require("./deprecated/window-utils");
+const { getInnerId, isBrowser } = require("./window/utils");
+const { Trait } = require("./deprecated/light-traits");
+const { Cortex } = require("./deprecated/cortex");
+const timer = require("./timers");
+
+// All user items we add have this class name.
+const ITEM_CLASS = "jetpack-context-menu-item";
+
+// Items in the top-level context menu also have this class.
+const TOPLEVEL_ITEM_CLASS = "jetpack-context-menu-item-toplevel";
+
+// Items in the overflow submenu also have this class.
+const OVERFLOW_ITEM_CLASS = "jetpack-context-menu-item-overflow";
+
+// The ID of the menu separator that separates standard context menu items from
+// our user items.
+const SEPARATOR_ID = "jetpack-context-menu-separator";
+
+// If more than this number of items are added to the context menu, all items
+// overflow into a "Jetpack" submenu.
+const OVERFLOW_THRESH_DEFAULT = 10;
+const OVERFLOW_THRESH_PREF =
+ "extensions.addon-sdk.context-menu.overflowThreshold";
+
+// The label of the overflow sub-xul:menu.
+//
+// TODO: Localize this.
+const OVERFLOW_MENU_LABEL = "Add-ons";
+
+// The ID of the overflow sub-xul:menu.
+const OVERFLOW_MENU_ID = "jetpack-content-menu-overflow-menu";
+
+// The ID of the overflow submenu's xul:menupopup.
+const OVERFLOW_POPUP_ID = "jetpack-content-menu-overflow-popup";
+
+// These are used by PageContext.isCurrent below. If the popupNode or any of
+// its ancestors is one of these, Firefox uses a tailored context menu, and so
+// the page context doesn't apply.
+const NON_PAGE_CONTEXT_ELTS = [
+ Ci.nsIDOMHTMLAnchorElement,
+ Ci.nsIDOMHTMLAppletElement,
+ Ci.nsIDOMHTMLAreaElement,
+ Ci.nsIDOMHTMLButtonElement,
+ Ci.nsIDOMHTMLCanvasElement,
+ Ci.nsIDOMHTMLEmbedElement,
+ Ci.nsIDOMHTMLImageElement,
+ Ci.nsIDOMHTMLInputElement,
+ Ci.nsIDOMHTMLMapElement,
+ Ci.nsIDOMHTMLMediaElement,
+ Ci.nsIDOMHTMLMenuElement,
+ Ci.nsIDOMHTMLObjectElement,
+ Ci.nsIDOMHTMLOptionElement,
+ Ci.nsIDOMHTMLSelectElement,
+ Ci.nsIDOMHTMLTextAreaElement,
+];
+
+// This is used to access private properties of Item and Menu instances.
+const PRIVATE_PROPS_KEY = {
+ valueOf: function valueOf() "private properties key"
+};
+
+// Used as an internal ID for items and as part of a public ID for item DOM
+// elements. Careful: This number is not necessarily unique to any one instance
+// of the module. For each module instance, when the first item is created this
+// number will be 0, when the second is created it will be 1, and so on.
+let nextItemID = 0;
+
+// The number of items that haven't finished initializing yet. See
+// AIT__finishActiveItemInit().
+let numItemsWithUnfinishedInit = 0;
+
+exports.Item = Item;
+exports.Menu = Menu;
+exports.Separator = Separator;
+
+
+// A word about traits and privates. `this` inside of traits methods is an
+// object private to the implementation. It should never be publicly leaked.
+// We use Cortex in the exported menu item constructors to create public
+// reflections of the private objects that hide private properties -- those
+// prefixed with an underscore. Public reflections are attached to the private
+// objects via the `_public` property.
+//
+// All item objects passed into the implementation by the client will be public
+// reflections, not private objects. Likewise, all item objects passed out of
+// the implementation to the client must be public, not private. Mixing up
+// public and private is bad and easy to do, so not only are private objects
+// restricted to the implementation, but as much as possible we try to restrict
+// them to the Item, Menu, and Separator traits and constructors. Everybody
+// else in the implementation should expect to be passed public reflections, and
+// they must specifically request private objects via privateItem().
+
+// Item, Menu, and Separator are composed of this trait.
+const ItemBaseTrait = Trait({
+
+ _initBase: function IBT__initBase(opts, optRules, optsToNotSet) {
+ this._optRules = optRules;
+ for (let optName in optRules)
+ if (optsToNotSet.indexOf(optName) < 0)
+ this[optName] = opts[optName];
+ optsToNotSet.forEach(function (opt) validateOpt(opts[opt], optRules[opt]));
+ this._isInited = true;
+
+ this._id = nextItemID++;
+ this._parentMenu = null;
+
+ // This makes the private properties accessible to anyone with access to
+ // PRIVATE_PROPS_KEY. Barring loader tricks, only this file has has access
+ // to it, so only this file has access to the private properties.
+ const self = this;
+ this.valueOf = function IBT_valueOf(key) {
+ return key === PRIVATE_PROPS_KEY ? self : self._public;
+ };
+ },
+
+ destroy: function IBT_destroy() {
+ if (this._wasDestroyed)
+ return;
+ if (this.parentMenu)
+ this.parentMenu.removeItem(this._public);
+ else if (!(this instanceof Separator) && this._hasFinishedInit)
+ browserManager.removeTopLevelItem(this._public);
+ browserManager.unregisterItem(this._public);
+ this._wasDestroyed = true;
+ },
+
+ get parentMenu() {
+ return this._parentMenu;
+ },
+
+ set parentMenu(val) {
+ throw new Error("The 'parentMenu' property is not intended to be set. " +
+ "Use menu.addItem(item) instead.");
+ },
+
+ set _isTopLevel(val) {
+ if (val)
+ this._workerReg = new WorkerRegistry(this._public);
+ else {
+ this._workerReg.destroy();
+ delete this._workerReg;
+ }
+ },
+
+ get _topLevelItem() {
+ let topLevelItem = this._public;
+ let parentMenu = this.parentMenu;
+ while (parentMenu) {
+ topLevelItem = parentMenu;
+ parentMenu = parentMenu.parentMenu;
+ }
+ return topLevelItem;
+ }
+});
+
+// Item and Menu are composed of this trait.
+const ActiveItemTrait = Trait.compose(ItemBaseTrait, EventEmitter, Trait({
+
+ _initActiveItem: function AIT__initActiveItem(opts, optRules, optsToNotSet) {
+ this._initBase(opts, optRules,
+ optsToNotSet.concat(["onMessage", "context"]));
+
+ if ("onMessage" in opts)
+ this.on("message", opts.onMessage);
+
+ // When a URL context is removed (by calling context.remove(urlContext)), we
+ // may need to create workers for windows containing pages that the item now
+ // matches. Likewise, when a URL context is added, we need to destroy
+ // workers for windows containing pages that the item now does not match.
+ //
+ // collection doesn't provide a way to listen for removals. utils/registry
+ // does, but it doesn't allow its elements to be enumerated. So as a hack,
+ // use a collection for item.context and replace its add and remove methods.
+ collection.addCollectionProperty(this, "context");
+ if (opts.context)
+ this.context.add(opts.context);
+
+ const self = this;
+
+ let add = this.context.add;
+ this.context.add = function itemContextAdd() {
+ let args = Array.slice(arguments);
+ add.apply(self.context, args);
+ if (self._workerReg && args.some(function (a) a instanceof URLContext))
+ self._workerReg.destroyUnneededWorkers();
+ };
+
+ let remove = this.context.remove;
+ this.context.remove = function itemContextRemove() {
+ let args = Array.slice(arguments);
+ remove.apply(self.context, args);
+ if (self._workerReg && args.some(function (a) a instanceof URLContext))
+ self._workerReg.createNeededWorkers();
+ };
+ },
+
+ // Workers are only created for top-level menu items. When a top-level item
+ // is later added to a Menu, its workers are destroyed. Well, all items start
+ // out as top-level because there is, unfortunately, no contextMenu.add(). So
+ // when an item is created and immediately added to a Menu, workers for it are
+ // needlessly created and destroyed. The point of this timeout is to avoid
+ // that. Items that are created and added to Menus in the same turn of the
+ // event loop won't have workers created for them.
+ _finishActiveItemInit: function AIT__finishActiveItemInit() {
+ numItemsWithUnfinishedInit++;
+ const self = this;
+ timer.setTimeout(function AIT__finishActiveItemInitTimeout() {
+ if (!self.parentMenu && !self._wasDestroyed)
+ browserManager.addTopLevelItem(self._public);
+ self._hasFinishedInit = true;
+ numItemsWithUnfinishedInit--;
+ }, 0);
+ },
+
+ get label() {
+ return this._label;
+ },
+
+ set label(val) {
+ this._label = validateOpt(val, this._optRules.label);
+ if (this._isInited)
+ browserManager.setItemLabel(this, this._label);
+ return this._label;
+ },
+
+ get image() {
+ return this._image;
+ },
+
+ set image(val) {
+ this._image = validateOpt(val, this._optRules.image);
+ if (this._isInited)
+ browserManager.setItemImage(this, this._image);
+ return this._image;
+ },
+
+ get contentScript() {
+ return this._contentScript;
+ },
+
+ set contentScript(val) {
+ this._contentScript = validateOpt(val, this._optRules.contentScript);
+ return this._contentScript;
+ },
+
+ get contentScriptFile() {
+ return this._contentScriptFile;
+ },
+
+ set contentScriptFile(val) {
+ this._contentScriptFile =
+ validateOpt(val, this._optRules.contentScriptFile);
+ return this._contentScriptFile;
+ }
+}));
+
+// Item is composed of this trait.
+const ItemTrait = Trait.compose(ActiveItemTrait, Trait({
+
+ _initItem: function IT__initItem(opts, optRules) {
+ this._initActiveItem(opts, optRules, []);
+ },
+
+ get data() {
+ return this._data;
+ },
+
+ set data(val) {
+ this._data = validateOpt(val, this._optRules.data);
+ if (this._isInited)
+ browserManager.setItemData(this, this._data);
+ return this._data;
+ },
+
+ toString: function IT_toString() {
+ return '[object Item "' + this.label + '"]';
+ }
+}));
+
+// The exported Item constructor.
+function Item(options) {
+ let optRules = optionsRules();
+ optRules.data = {
+ map: function (v) v.toString(),
+ is: ["string", "undefined"]
+ };
+
+ let item = ItemTrait.create(Item.prototype);
+ item._initItem(options, optRules);
+
+ item._public = Cortex(item);
+ browserManager.registerItem(item._public);
+ item._finishActiveItemInit();
+
+ return item._public;
+}
+
+// Menu is composed of this trait.
+const MenuTrait = Trait.compose(
+ ActiveItemTrait.resolve({ destroy: "_destroyThisItem" }),
+ Trait({
+
+ _initMenu: function MT__initMenu(opts, optRules, optsToNotSet) {
+ this._items = [];
+ this._initActiveItem(opts, optRules, optsToNotSet);
+ },
+
+ destroy: function MT_destroy() {
+ while (this.items.length)
+ this.items[0].destroy();
+ this._destroyThisItem();
+ },
+
+ get items() {
+ return this._items;
+ },
+
+ set items(val) {
+ let newItems = validateOpt(val, this._optRules.items);
+ while (this.items.length)
+ this.items[0].destroy();
+ newItems.forEach(function (i) this.addItem(i), this);
+ return newItems;
+ },
+
+ addItem: function MT_addItem(item) {
+ // First, remove the item from its current parent.
+ let privates = privateItem(item);
+ if (item.parentMenu)
+ item.parentMenu.removeItem(item);
+ else if (!(item instanceof Separator) && privates._hasFinishedInit)
+ browserManager.removeTopLevelItem(item);
+
+ // Now add the item to this menu.
+ this._items.push(item);
+ privates._parentMenu = this._public;
+ browserManager.addItemToMenu(item, this._public);
+ },
+
+ removeItem: function MT_removeItem(item) {
+ let idx = this._items.indexOf(item);
+ if (idx < 0)
+ return;
+ this._items.splice(idx, 1);
+ privateItem(item)._parentMenu = null;
+ browserManager.removeItemFromMenu(item, this._public);
+ },
+
+ toString: function MT_toString() {
+ return '[object Menu "' + this.label + '"]';
+ }
+}));
+
+// The exported Menu constructor.
+function Menu(options) {
+ let optRules = optionsRules();
+ optRules.items = {
+ is: ["array"],
+ ok: function (v) {
+ return v.every(function (item) {
+ return (item instanceof Item) ||
+ (item instanceof Menu) ||
+ (item instanceof Separator);
+ });
+ },
+ msg: "items must be an array, and each element in the array must be an " +
+ "Item, Menu, or Separator."
+ };
+
+ let menu = MenuTrait.create(Menu.prototype);
+
+ // We can't rely on _initBase to set the `items` property, because the menu
+ // needs to be registered with and added to the browserManager before any
+ // child items are added to it.
+ menu._initMenu(options, optRules, ["items"]);
+
+ menu._public = Cortex(menu);
+ browserManager.registerItem(menu._public);
+ menu.items = options.items;
+ menu._finishActiveItemInit();
+
+ return menu._public;
+}
+
+// The exported Separator constructor.
+function Separator() {
+ let sep = ItemBaseTrait.create(Separator.prototype);
+ sep._initBase({}, {}, []);
+
+ sep._public = Cortex(sep);
+ browserManager.registerItem(sep._public);
+ sep._hasFinishedInit = true;
+ return sep._public;
+}
+
+
+function Context() {}
+
+function PageContext() {
+ this.isCurrent = function PageContext_isCurrent(popupNode) {
+ let win = popupNode.ownerDocument.defaultView;
+ if (win && !win.getSelection().isCollapsed)
+ return false;
+
+ let cursor = popupNode;
+ while (cursor && !(cursor instanceof Ci.nsIDOMHTMLHtmlElement)) {
+ if (NON_PAGE_CONTEXT_ELTS.some(function (iface) cursor instanceof iface))
+ return false;
+ cursor = cursor.parentNode;
+ }
+ return true;
+ };
+}
+
+PageContext.prototype = new Context();
+
+function SelectorContext(selector) {
+ let opts = apiUtils.validateOptions({ selector: selector }, {
+ selector: {
+ is: ["string"],
+ msg: "selector must be a string."
+ }
+ });
+
+ this.adjustPopupNode = function SelectorContext_adjustPopupNode(node) {
+ return closestMatchingAncestor(node);
+ };
+
+ this.isCurrent = function SelectorContext_isCurrent(popupNode) {
+ return !!closestMatchingAncestor(popupNode);
+ };
+
+ // Returns node if it matches selector, or the closest ancestor of node that
+ // matches, or null if node and none of its ancestors matches.
+ function closestMatchingAncestor(node) {
+ let cursor = node;
+ while (cursor) {
+ if (cursor.mozMatchesSelector(selector))
+ return cursor;
+ if (cursor instanceof Ci.nsIDOMHTMLHtmlElement)
+ break;
+ cursor = cursor.parentNode;
+ }
+ return null;
+ }
+}
+
+SelectorContext.prototype = new Context();
+
+function SelectionContext() {
+ this.isCurrent = function SelectionContext_isCurrent(popupNode) {
+ let win = popupNode.ownerDocument.defaultView;
+ if (!win)
+ return false;
+
+ let hasSelection = !win.getSelection().isCollapsed;
+ if (!hasSelection) {
+ // window.getSelection doesn't return a selection for text selected in a
+ // form field (see bug 85686), so before returning false we want to check
+ // if the popupNode is a text field.
+ let { selectionStart, selectionEnd } = popupNode;
+ hasSelection = !isNaN(selectionStart) &&
+ !isNaN(selectionEnd) &&
+ selectionStart !== selectionEnd;
+ }
+ return hasSelection;
+ };
+}
+
+SelectionContext.prototype = new Context();
+
+function URLContext(patterns) {
+ let opts = apiUtils.validateOptions({ patterns: patterns }, {
+ patterns: {
+ map: function (v) apiUtils.getTypeOf(v) === "array" ? v : [v],
+ ok: function (v) v.every(function (p) typeof(p) === "string"),
+ msg: "patterns must be a string or an array of strings."
+ }
+ });
+ try {
+ patterns = opts.patterns.map(function (p) new MatchPattern(p));
+ }
+ catch (err) {
+ console.error("Error creating URLContext match pattern:");
+ throw err;
+ }
+
+ const self = this;
+
+ this.isCurrent = function URLContext_isCurrent(popupNode) {
+ return self.isCurrentForURL(popupNode.ownerDocument.URL);
+ };
+
+ this.isCurrentForURL = function URLContext_isCurrentForURL(url) {
+ return patterns.some(function (p) p.test(url));
+ };
+}
+
+URLContext.prototype = new Context();
+
+exports.PageContext = apiUtils.publicConstructor(PageContext);
+exports.SelectorContext = apiUtils.publicConstructor(SelectorContext);
+exports.SelectionContext = apiUtils.publicConstructor(SelectionContext);
+exports.URLContext = apiUtils.publicConstructor(URLContext);
+
+
+// Returns a version of opt validated against the given rule.
+function validateOpt(opt, rule) {
+ return apiUtils.validateOptions({ opt: opt }, { opt: rule }).opt;
+}
+
+// Returns rules for apiUtils.validateOptions() common to Item and Menu.
+function optionsRules() {
+ return {
+ context: {
+ is: ["undefined", "object", "array"],
+ ok: function (v) {
+ if (!v)
+ return true;
+ let arr = apiUtils.getTypeOf(v) === "array" ? v : [v];
+ return arr.every(function (o) o instanceof Context);
+ },
+ msg: "The 'context' option must be a Context object or an array of " +
+ "Context objects."
+ },
+ label: {
+ map: function (v) v.toString(),
+ is: ["string"],
+ ok: function (v) !!v,
+ msg: "The item must have a non-empty string label."
+ },
+ image: {
+ map: function (v) v.toString(),
+ is: ["string", "undefined", "null"]
+ },
+ contentScript: {
+ is: ["string", "array", "undefined"],
+ ok: function (v) {
+ return apiUtils.getTypeOf(v) !== "array" ||
+ v.every(function (s) typeof(s) === "string");
+ }
+ },
+ contentScriptFile: {
+ is: ["string", "array", "undefined"],
+ ok: function (v) {
+ if (!v)
+ return true;
+ let arr = apiUtils.getTypeOf(v) === "array" ? v : [v];
+ try {
+ return arr.every(function (s) {
+ return apiUtils.getTypeOf(s) === "string" &&
+ URL(s).scheme === 'resource';
+ });
+ }
+ catch (err) {}
+ return false;
+ },
+ msg: "The 'contentScriptFile' option must be a local file URL or " +
+ "an array of local file URLs."
+ },
+ onMessage: {
+ is: ["function", "undefined"]
+ }
+ };
+}
+
+// Does a binary search on elts, a NodeList, and returns the DOM element
+// before which an item with targetLabel should be inserted. null is returned
+// if the new item should be inserted at the end.
+function insertionPoint(targetLabel, elts) {
+ let from = 0;
+ let to = elts.length - 1;
+
+ while (from <= to) {
+ let i = Math.floor((from + to) / 2);
+ let comp = targetLabel.localeCompare(elts[i].getAttribute("label"));
+ if (comp < 0)
+ to = i - 1;
+ else if (comp > 0)
+ from = i + 1;
+ else
+ return elts[i];
+ }
+ return elts[from] || null;
+}
+
+// Builds an ID suitable for a DOM element from the given item ID.
+// isInOverflowSubtree should be true if the returned element will be inserted
+// into the DOM subtree rooted at the overflow menu.
+function domEltIDFromItemID(itemID, isInOverflowSubtree) {
+ let suffix = isInOverflowSubtree ? "-overflow" : "";
+ return jpSelf.id + "-context-menu-item-" + itemID + suffix;
+}
+
+// Parses the item ID out of the given DOM element ID and returns it. If the
+// element's ID is malformed or it indicates that the element was not created by
+// the instance of the module calling this function, returns -1.
+function itemIDFromDOMEltID(domEltID) {
+ let match = /^(.+?)-context-menu-item-([0-9]+)[-a-z]*$/.exec(domEltID);
+ return !match || match[1] !== jpSelf.id ? -1 : match[2];
+}
+
+// Returns the private version of the given public reflection.
+function privateItem(publicItem) {
+ return publicItem.valueOf(PRIVATE_PROPS_KEY);
+}
+
+
+// A type of Worker tailored to our uses.
+const ContextMenuWorker = Worker.compose({
+ destroy: Worker.required,
+
+ // Returns true if any context listeners are defined in the worker's port.
+ anyContextListeners: function CMW_anyContextListeners() {
+ return this._contentWorker.hasListenerFor("context");
+ },
+
+ // Returns the first string or truthy value returned by a context listener in
+ // the worker's port. If none return a string or truthy value or if there are
+ // no context listeners, returns false. popupNode is the node that was
+ // context-clicked.
+ isAnyContextCurrent: function CMW_isAnyContextCurrent(popupNode) {
+ let results = this._contentWorker.emitSync("context", popupNode);
+ for (let i = 0; i < results.length; i++) {
+ let val = results[i];
+ if (typeof val === "string" || val)
+ return val;
+ }
+ return false;
+ },
+
+ // Emits a click event in the worker's port. popupNode is the node that was
+ // context-clicked, and clickedItemData is the data of the item that was
+ // clicked.
+ fireClick: function CMW_fireClick(popupNode, clickedItemData) {
+ this._contentWorker.emitSync("click", popupNode, clickedItemData);
+ }
+});
+
+
+// This class creates and stores content workers for pairs of menu items and
+// content windows. Use one instance for each item. Not all pairs need a
+// worker: if an item has a URL context that does not match a window's page,
+// then no worker is created for the pair.
+function WorkerRegistry(item) {
+ this.item = item;
+
+ // inner window ID => { win, worker }
+ this.winWorkers = {};
+
+ // inner window ID => content window
+ this.winsWithoutWorkers = {};
+}
+
+WorkerRegistry.prototype = {
+
+ // Registers a content window, creating a worker for it if it needs one.
+ registerContentWin: function WR_registerContentWin(win) {
+ let innerWinID = getInnerId(win);
+ if ((innerWinID in this.winWorkers) ||
+ (innerWinID in this.winsWithoutWorkers))
+ return;
+ if (this._doesURLNeedWorker(win.document.URL))
+ this.winWorkers[innerWinID] = { win: win, worker: this._makeWorker(win) };
+ else
+ this.winsWithoutWorkers[innerWinID] = win;
+ },
+
+ // Unregisters a content window, destroying its related worker if it has one.
+ unregisterContentWin: function WR_unregisterContentWin(innerWinID) {
+ if (innerWinID in this.winWorkers) {
+ let winWorker = this.winWorkers[innerWinID];
+ winWorker.worker.destroy();
+ delete winWorker.worker;
+ delete winWorker.win;
+ delete this.winWorkers[innerWinID];
+ }
+ else
+ delete this.winsWithoutWorkers[innerWinID];
+ },
+
+ // Creates a worker for each window that needs a worker but doesn't have one.
+ createNeededWorkers: function WR_createNeededWorkers() {
+ for (let innerWinID in this.winsWithoutWorkers) {
+ let win = this.winsWithoutWorkers[innerWinID]
+ delete this.winsWithoutWorkers[innerWinID];
+ this.registerContentWin(win);
+ }
+ },
+
+ // Destroys the worker for each window that has a worker but doesn't need it.
+ destroyUnneededWorkers: function WR_destroyUnneededWorkers() {
+ for (let innerWinID in this.winWorkers) {
+ let winWorker = this.winWorkers[innerWinID];
+ if (!this._doesURLNeedWorker(winWorker.win.document.URL)) {
+ this.unregisterContentWin(innerWinID);
+ this.winsWithoutWorkers[innerWinID] = winWorker.win;
+ }
+ }
+ },
+
+ // Returns the worker for the item-window pair or null if none exists.
+ find: function WR_find(contentWin) {
+ let innerWinID = getInnerId(contentWin);
+ return (innerWinID in this.winWorkers) ?
+ this.winWorkers[innerWinID].worker :
+ null;
+ },
+
+ // Unregisters all content windows from the registry, which destroys all
+ // workers.
+ destroy: function WR_destroy() {
+ for (let innerWinID in this.winWorkers)
+ this.unregisterContentWin(innerWinID);
+ for (let innerWinID in this.winsWithoutWorkers)
+ this.unregisterContentWin(innerWinID);
+ },
+
+ // Returns false if the item has a URL context that does not match the given
+ // URL.
+ _doesURLNeedWorker: function WR__doesURLNeedWorker(url) {
+ for (let ctxt in this.item.context)
+ if ((ctxt instanceof URLContext) && !ctxt.isCurrentForURL(url))
+ return false;
+ return true;
+ },
+
+ _makeWorker: function WR__makeWorker(win) {
+ let worker = ContextMenuWorker({
+ window: win,
+ contentScript: this.item.contentScript,
+ contentScriptFile: this.item.contentScriptFile,
+ onError: function (err) console.exception(err)
+ });
+ let item = this.item;
+ worker.on("message", function workerOnMessage(msg) {
+ try {
+ privateItem(item)._emitOnObject(item, "message", msg);
+ }
+ catch (err) {
+ console.exception(err);
+ }
+ });
+ return worker;
+ }
+};
+
+
+// Mirrors state across all browser windows. Also responsible for detecting
+// all content window loads and unloads.
+let browserManager = {
+ topLevelItems: [],
+ browserWins: [],
+
+ // inner window ID => content window
+ contentWins: {},
+
+ // Call this when a new item is created, top-level or not.
+ registerItem: function BM_registerItem(item) {
+ this.browserWins.forEach(function (w) w.registerItem(item));
+ },
+
+ // Call this when an item is destroyed and won't be used again, top-level or
+ // not.
+ unregisterItem: function BM_unregisterItem(item) {
+ this.browserWins.forEach(function (w) w.unregisterItem(item));
+ },
+
+ addTopLevelItem: function BM_addTopLevelItem(item) {
+ this.topLevelItems.push(item);
+ this.browserWins.forEach(function (w) w.addTopLevelItem(item));
+
+ // Create the item's worker registry and register all currently loaded
+ // content windows with it.
+ let privates = privateItem(item);
+ privates._isTopLevel = true;
+ for each (let win in this.contentWins)
+ privates._workerReg.registerContentWin(win);
+ },
+
+ removeTopLevelItem: function BM_removeTopLevelItem(item) {
+ let idx = this.topLevelItems.indexOf(item);
+ if (idx < 0)
+ throw new Error("Internal error: item not in top-level menu: " + item);
+ this.topLevelItems.splice(idx, 1);
+ this.browserWins.forEach(function (w) w.removeTopLevelItem(item));
+ privateItem(item)._isTopLevel = false;
+ },
+
+ addItemToMenu: function BM_addItemToMenu(item, parentMenu) {
+ this.browserWins.forEach(function (w) w.addItemToMenu(item, parentMenu));
+ },
+
+ removeItemFromMenu: function BM_removeItemFromMenu(item, parentMenu) {
+ this.browserWins.forEach(function (w) w.removeItemFromMenu(item,
+ parentMenu));
+ },
+
+ setItemLabel: function BM_setItemLabel(item, label) {
+ this.browserWins.forEach(function (w) w.setItemLabel(item, label));
+ },
+
+ setItemImage: function BM_setItemImage(item, imageURL) {
+ this.browserWins.forEach(function (w) w.setItemImage(item, imageURL));
+ },
+
+ setItemData: function BM_setItemData(item, data) {
+ this.browserWins.forEach(function (w) w.setItemData(item, data));
+ },
+
+ // Note that calling this method will cause onTrack to be called immediately
+ // for each currently open browser window.
+ init: function BM_init() {
+ require("./system/unload").ensure(this);
+ let windowTracker = WindowTracker(this);
+
+ // Register content windows on content-document-global-created and
+ // unregister them on inner-window-destroyed. For rationale, see bug 667957
+ // for the former and bug 642004 for the latter.
+ observerServ.add("content-document-global-created",
+ this._onDocGlobalCreated, this);
+ observerServ.add("inner-window-destroyed",
+ this._onInnerWinDestroyed, this);
+ },
+
+ _onDocGlobalCreated: function BM__onDocGlobalCreated(contentWin) {
+ let doc = contentWin.document;
+ if (doc.readyState == "loading") {
+ const self = this;
+ doc.addEventListener("readystatechange", function onReadyStateChange(e) {
+ if (e.target != doc || doc.readyState != "complete")
+ return;
+ doc.removeEventListener("readystatechange", onReadyStateChange, false);
+ self._registerContentWin(contentWin);
+ }, false);
+ }
+ else if (doc.readyState == "complete")
+ this._registerContentWin(contentWin);
+ },
+
+ _onInnerWinDestroyed: function BM__onInnerWinDestroyed(subj) {
+ this._unregisterContentWin(
+ subj.QueryInterface(Ci.nsISupportsPRUint64).data);
+ },
+
+ // Stores the given content window with the manager and registers it with each
+ // top-level item's worker registry.
+ _registerContentWin: function BM__registerContentWin(win) {
+ let innerID = getInnerId(win);
+
+ // It's an error to call this method for the same window more than once, but
+ // we allow it in one case: when onTrack races _onDocGlobalCreated. (See
+ // the comment in onTrack.) Make sure the window is registered only once.
+ if (innerID in this.contentWins)
+ return;
+
+ this.contentWins[innerID] = win;
+ this.topLevelItems.forEach(function (item) {
+ privateItem(item)._workerReg.registerContentWin(win);
+ });
+ },
+
+ // Removes the given content window from the manager and unregisters it from
+ // each top-level item's worker registry.
+ _unregisterContentWin: function BM__unregisterContentWin(innerID) {
+ delete this.contentWins[innerID];
+ this.topLevelItems.forEach(function (item) {
+ privateItem(item)._workerReg.unregisterContentWin(innerID);
+ });
+ },
+
+ unload: function BM_unload() {
+ // The window tracker is unloaded at the same time this method is called,
+ // which causes onUntrack to be called for each open browser window, so
+ // there's no need to clean up browser windows here.
+
+ while (this.topLevelItems.length) {
+ let item = this.topLevelItems[0];
+ this.removeTopLevelItem(item);
+ this.unregisterItem(item);
+ }
+ delete this.contentWins;
+ },
+
+ // Registers a browser window with the manager. This is a WindowTracker
+ // callback. Note that this is called in two cases: for each newly opened
+ // chrome window, and for each chrome window that is open when the loader
+ // loads this module.
+ onTrack: function BM_onTrack(window) {
+ if (!isBrowser(window))
+ return;
+
+ let browserWin = new BrowserWindow(window);
+ this.browserWins.push(browserWin);
+
+ // Register all loaded content windows in the browser window. Be sure to
+ // include frames and iframes. If onTrack is called as a result of a new
+ // browser window being opened, as opposed to the module being loaded, then
+ // this will race the content-document-global-created notification. That's
+ // OK, since _registerContentWin will not register the same content window
+ // more than once.
+ window.gBrowser.browsers.forEach(function (browser) {
+ let topContentWin = browser.contentWindow;
+ let allContentWins = Array.slice(topContentWin.frames);
+ allContentWins.push(topContentWin);
+ allContentWins.forEach(function (contentWin) {
+ if (contentWin.document.readyState == "complete")
+ this._registerContentWin(contentWin);
+ }, this);
+ }, this);
+
+ // Add all top-level items and, recursively, their child items to the new
+ // browser window.
+ function addItemTree(item, parentMenu) {
+ browserWin.registerItem(item);
+ if (parentMenu)
+ browserWin.addItemToMenu(item, parentMenu);
+ else
+ browserWin.addTopLevelItem(item);
+ if (item instanceof Menu)
+ item.items.forEach(function (subitem) addItemTree(subitem, item));
+ }
+ this.topLevelItems.forEach(function (item) addItemTree(item, null));
+ },
+
+ // Unregisters a browser window from the manager. This is a WindowTracker
+ // callback. Note that this is called in two cases: for each newly closed
+ // chrome window, and for each chrome window that is open when this module is
+ // unloaded.
+ onUntrack: function BM_onUntrack(window) {
+ if (!isBrowser(window))
+ return;
+
+ // Remove the window from the window list.
+ let idx = 0;
+ for (; idx < this.browserWins.length; idx++)
+ if (this.browserWins[idx].window == window)
+ break;
+ if (idx == this.browserWins.length)
+ throw new Error("Internal error: browser window not found");
+ let browserWin = this.browserWins.splice(idx, 1)[0];
+
+ // Remove all top-level items from the window.
+ this.topLevelItems.forEach(function (i) browserWin.removeTopLevelItem(i));
+ browserWin.destroy();
+ }
+};
+
+
+// Responsible for creating and managing context menu item DOM elements for a
+// browser window. Also responsible for providing a description of the window's
+// current context and determining whether an item matches the current context.
+//
+// TODO: If other apps besides Firefox want to support the context menu in
+// whatever way is appropriate for them, plugging in a substitute for or an
+// adapter to this class should be the way to do it. Make it easy for them.
+// See bug 560716.
+function BrowserWindow(window) {
+ this.window = window;
+ this.doc = window.document;
+
+ let popupDOMElt = this.doc.getElementById("contentAreaContextMenu");
+ if (!popupDOMElt)
+ throw new Error("Internal error: Context menu popup not found.");
+ this.contextMenuPopup = new ContextMenuPopup(popupDOMElt, this);
+
+ // item ID => { item, domElt, overflowDOMElt, popup, overflowPopup }
+ // item may or may not be top-level. domElt is the item's DOM element
+ // contained in the subtree rooted in the top-level context menu.
+ // overflowDOMElt is the item's DOM element contained in the subtree rooted in
+ // the overflow submenu. popup and overflowPopup are only defined if the item
+ // is a Menu; they're the Popup instances containing the Menu's child items,
+ // with the aforementioned distinction between top-level and overflow
+ // subtrees.
+ this.items = {};
+}
+
+BrowserWindow.prototype = {
+
+ // Creates and stores DOM elements for the given item, top-level or not.
+ registerItem: function BW_registerItem(item) {
+ // this.items[id] is referenced by _makeMenu, so it needs to be defined
+ // before _makeDOMElt is called.
+ let props = { item: item };
+ this.items[privateItem(item)._id] = props;
+ props.domElt = this._makeDOMElt(item, false);
+ props.overflowDOMElt = this._makeDOMElt(item, true);
+ },
+
+ // Removes the given item's DOM elements from the store.
+ unregisterItem: function BW_unregisterItem(item) {
+ delete this.items[privateItem(item)._id];
+ },
+
+ addTopLevelItem: function BW_addTopLevelItem(item) {
+ this.contextMenuPopup.addItem(item);
+ },
+
+ removeTopLevelItem: function BW_removeTopLevelItem(item) {
+ this.contextMenuPopup.removeItem(item);
+ },
+
+ addItemToMenu: function BW_addItemToMenu(item, parentMenu) {
+ let { popup, overflowPopup } = this.items[privateItem(parentMenu)._id];
+ popup.addItem(item);
+ overflowPopup.addItem(item);
+ },
+
+ removeItemFromMenu: function BW_removeItemFromMenu(item, parentMenu) {
+ let { popup, overflowPopup } = this.items[privateItem(parentMenu)._id];
+ popup.removeItem(item);
+ overflowPopup.removeItem(item);
+ },
+
+ setItemLabel: function BW_setItemLabel(item, label) {
+ let privates = privateItem(item);
+ let { domElt, overflowDOMElt } = this.items[privates._id];
+ this._setDOMEltLabel(domElt, label);
+ this._setDOMEltLabel(overflowDOMElt, label);
+ if (!item.parentMenu && privates._hasFinishedInit)
+ this.contextMenuPopup.itemLabelDidChange(item);
+ },
+
+ _setDOMEltLabel: function BW__setDOMEltLabel(domElt, label) {
+ domElt.setAttribute("label", label);
+ },
+
+ setItemImage: function BW_setItemImage(item, imageURL) {
+ let { domElt, overflowDOMElt } = this.items[privateItem(item)._id];
+ let isMenu = item instanceof Menu;
+ this._setDOMEltImage(domElt, imageURL, isMenu);
+ this._setDOMEltImage(overflowDOMElt, imageURL, isMenu);
+ },
+
+ _setDOMEltImage: function BW__setDOMEltImage(domElt, imageURL, isMenu) {
+ if (!imageURL) {
+ domElt.removeAttribute("image");
+ domElt.classList.remove("menu-iconic");
+ domElt.classList.remove("menuitem-iconic");
+ }
+ else {
+ domElt.setAttribute("image", imageURL);
+ domElt.classList.add(isMenu ? "menu-iconic" : "menuitem-iconic");
+ }
+ },
+
+ setItemData: function BW_setItemData(item, data) {
+ let { domElt, overflowDOMElt } = this.items[privateItem(item)._id];
+ this._setDOMEltData(domElt, data);
+ this._setDOMEltData(overflowDOMElt, data);
+ },
+
+ _setDOMEltData: function BW__setDOMEltData(domElt, data) {
+ domElt.setAttribute("value", data);
+ },
+
+ // The context specified for a top-level item may not match exactly the real
+ // context that triggers it. For example, if the user context-clicks a span
+ // inside an anchor, we want items that specify an anchor context to be
+ // triggered, but the real context will indicate that the span was clicked,
+ // not the anchor. Where the real context and an item's context conflict,
+ // clients should be given the item's context, and this method can be used to
+ // make such adjustments. Returns an adjusted popupNode.
+ adjustPopupNode: function BW_adjustPopupNode(popupNode, topLevelItem) {
+ for (let ctxt in topLevelItem.context) {
+ if (typeof(ctxt.adjustPopupNode) === "function") {
+ let ctxtNode = ctxt.adjustPopupNode(popupNode);
+ if (ctxtNode) {
+ popupNode = ctxtNode;
+ break;
+ }
+ }
+ }
+ return popupNode;
+ },
+
+ // Returns true if all of item's contexts are current in the window.
+ areAllContextsCurrent: function BW_areAllContextsCurrent(item, popupNode) {
+ let win = popupNode.ownerDocument.defaultView;
+ let worker = privateItem(item)._workerReg.find(win);
+
+ // If the worker for the item-window pair doesn't exist (e.g., because the
+ // page hasn't loaded yet), we can't really make a good decision since the
+ // content script may have a context listener. So just don't show the item
+ // at all.
+ if (!worker)
+ return false;
+
+ // If there are no contexts given at all, the page context applies.
+ let hasContentContext = worker.anyContextListeners();
+ if (!hasContentContext && !item.context.length)
+ return new PageContext().isCurrent(popupNode);
+
+ // Otherwise, determine if all given contexts are current. Evaluate the
+ // declarative contexts first and the worker's context listeners last. That
+ // way the listener might be able to avoid some work.
+ let curr = true;
+ for (let ctxt in item.context) {
+ curr = curr && ctxt.isCurrent(popupNode);
+ if (!curr)
+ return false;
+ }
+ return !hasContentContext || worker.isAnyContextCurrent(popupNode);
+ },
+
+ // Returns the node the user context-clicked to invoke the context menu.
+ // For Gecko 2.0 and later, triggerNode is this node;
+ // if it's falsey, document.popupNode is used.
+ capturePopupNode: function BW_capturePopupNode(triggerNode) {
+ var popupNode = triggerNode || this.doc.popupNode;
+ if (!popupNode)
+ console.warn("popupNode is null.");
+ return popupNode;
+ },
+
+ destroy: function BW_destroy() {
+ this.contextMenuPopup.destroy();
+ delete this.window;
+ delete this.doc;
+ delete this.items;
+ },
+
+ // Emits a click event in the port of the content worker related to given top-
+ // level item and popupNode's content window. Listeners will be passed
+ // popupNode and clickedItemData.
+ fireClick: function BW_fireClick(topLevelItem, popupNode, clickedItemData) {
+ let win = popupNode.ownerDocument.defaultView;
+ let worker = privateItem(topLevelItem)._workerReg.find(win);
+ if (worker)
+ worker.fireClick(popupNode, clickedItemData);
+ },
+
+ _makeDOMElt: function BW__makeDOMElt(item, isInOverflowSubtree) {
+ let elt = item instanceof Item ? this._makeMenuitem(item) :
+ item instanceof Menu ? this._makeMenu(item, isInOverflowSubtree) :
+ item instanceof Separator ? this._makeSeparator() :
+ null;
+ if (!elt)
+ throw new Error("Internal error: can't make element, unknown item type");
+
+ elt.id = domEltIDFromItemID(privateItem(item)._id, isInOverflowSubtree);
+ elt.classList.add(ITEM_CLASS);
+ return elt;
+ },
+
+ // Returns a new xul:menu representing the menu.
+ _makeMenu: function BW__makeMenu(menu, isInOverflowSubtree) {
+ let menuElt = this.doc.createElement("menu");
+ this._setDOMEltLabel(menuElt, menu.label);
+ if (menu.image)
+ this._setDOMEltImage(menuElt, menu.image, true);
+ let popupElt = this.doc.createElement("menupopup");
+ menuElt.appendChild(popupElt);
+
+ let popup = new Popup(popupElt, this, isInOverflowSubtree);
+ let props = this.items[privateItem(menu)._id];
+ if (isInOverflowSubtree)
+ props.overflowPopup = popup;
+ else
+ props.popup = popup;
+
+ return menuElt;
+ },
+
+ // Returns a new xul:menuitem representing the item.
+ _makeMenuitem: function BW__makeMenuitem(item) {
+ let elt = this.doc.createElement("menuitem");
+ this._setDOMEltLabel(elt, item.label);
+ if (item.image)
+ this._setDOMEltImage(elt, item.image, false);
+ if (item.data)
+ this._setDOMEltData(elt, item.data);
+ return elt;
+ },
+
+ // Returns a new xul:menuseparator.
+ _makeSeparator: function BW__makeSeparator() {
+ return this.doc.createElement("menuseparator");
+ }
+};
+
+
+// Responsible for adding DOM elements to and removing them from poupDOMElt.
+function Popup(popupDOMElt, browserWin, isInOverflowSubtree) {
+ this.popupDOMElt = popupDOMElt;
+ this.browserWin = browserWin;
+ this.isInOverflowSubtree = isInOverflowSubtree;
+}
+
+Popup.prototype = {
+
+ addItem: function Popup_addItem(item) {
+ let props = this.browserWin.items[privateItem(item)._id];
+ let elt = this.isInOverflowSubtree ? props.overflowDOMElt : props.domElt;
+ this.popupDOMElt.appendChild(elt);
+ },
+
+ removeItem: function Popup_removeItem(item) {
+ let props = this.browserWin.items[privateItem(item)._id];
+ let elt = this.isInOverflowSubtree ? props.overflowDOMElt : props.domElt;
+ this.popupDOMElt.removeChild(elt);
+ }
+};
+
+
+// Represents a browser window's context menu popup. Responsible for hiding and
+// showing items according to the browser window's current context and for
+// handling item clicks.
+function ContextMenuPopup(popupDOMElt, browserWin) {
+ this.popupDOMElt = popupDOMElt;
+ this.browserWin = browserWin;
+ this.doc = popupDOMElt.ownerDocument;
+
+ // item ID => item
+ // Calling this variable "topLevelItems" is redundant, since Popup and
+ // ContextMenuPopup are only responsible for their child items, not all their
+ // descendant items. But calling it "items" might encourage one to believe
+ // otherwise, so topLevelItems it is.
+ this.topLevelItems = {};
+
+ popupDOMElt.addEventListener("popupshowing", this, false);
+ popupDOMElt.addEventListener("command", this, false);
+}
+
+ContextMenuPopup.prototype = {
+
+ addItem: function CMP_addItem(item) {
+ this._ensureStaticEltsExist();
+ let itemID = privateItem(item)._id;
+ this.topLevelItems[itemID] = item;
+ let props = this.browserWin.items[itemID];
+ props.domElt.classList.add(TOPLEVEL_ITEM_CLASS);
+ props.overflowDOMElt.classList.add(OVERFLOW_ITEM_CLASS);
+ this._insertItemInSortedOrder(item);
+ },
+
+ removeItem: function CMP_removeItem(item) {
+ let itemID = privateItem(item)._id;
+ delete this.topLevelItems[itemID];
+ let { domElt, overflowDOMElt } = this.browserWin.items[itemID];
+ domElt.classList.remove(TOPLEVEL_ITEM_CLASS);
+ overflowDOMElt.classList.remove(OVERFLOW_ITEM_CLASS);
+ this.popupDOMElt.removeChild(domElt);
+ this._overflowPopup.removeChild(overflowDOMElt);
+ },
+
+ // Call this after the item's label changes. This re-inserts the item into
+ // the popup so that it remains in sorted order.
+ itemLabelDidChange: function CMP_itemLabelDidChange(item) {
+ let itemID = privateItem(item)._id;
+ let { domElt, overflowDOMElt } = this.browserWin.items[itemID];
+ this.popupDOMElt.removeChild(domElt);
+ this._overflowPopup.removeChild(overflowDOMElt);
+ this._insertItemInSortedOrder(item);
+ },
+
+ destroy: function CMP_destroy() {
+ // If there are no more items from any instance of the module, remove the
+ // separator and overflow submenu, if they exist.
+ let elts = this._topLevelElts;
+ if (!elts.length) {
+ let submenu = this._overflowMenu;
+ if (submenu)
+ this.popupDOMElt.removeChild(submenu);
+
+ let sep = this._separator;
+ if (sep)
+ this.popupDOMElt.removeChild(sep);
+ }
+
+ this.popupDOMElt.removeEventListener("popupshowing", this, false);
+ this.popupDOMElt.removeEventListener("command", this, false);
+ },
+
+ handleEvent: function CMP_handleEvent(event) {
+ try {
+ if (event.type === "command")
+ this._handleClick(event.target);
+ else if (event.type === "popupshowing" &&
+ event.target === this.popupDOMElt)
+ this._handlePopupShowing();
+ }
+ catch (err) {
+ console.exception(err);
+ }
+ },
+
+ // command events bubble to the context menu's top-level xul:menupopup and are
+ // caught here.
+ _handleClick: function CMP__handleClick(clickedDOMElt) {
+ if (!clickedDOMElt.classList.contains(ITEM_CLASS))
+ return;
+ let itemID = itemIDFromDOMEltID(clickedDOMElt.id);
+ if (itemID < 0)
+ return;
+ let { item, domElt, overflowDOMElt } = this.browserWin.items[itemID];
+
+ // Bail if the DOM element was not created by this module instance. In
+ // real-world add-ons, the itemID < 0 check above is sufficient, but for the
+ // unit test the JID never changes, making this necessary.
+ if (clickedDOMElt != domElt && clickedDOMElt != overflowDOMElt)
+ return;
+
+ let topLevelItem = privateItem(item)._topLevelItem;
+ let popupNode = this.browserWin.adjustPopupNode(this._getPopupNode(),
+ topLevelItem);
+ this.browserWin.fireClick(topLevelItem, popupNode, item.data);
+ },
+
+ _getPopupNode: function CMP__getPopupNode() {
+ // popupDOMElt.triggerNode was added in Gecko 2.0 by bug 383930. The || is
+ // to avoid a Spidermonkey strict warning on earlier versions.
+ let triggerNode = this.popupDOMElt.triggerNode || undefined;
+ return this.browserWin.capturePopupNode(triggerNode);
+ },
+
+ // popupshowing is used to show top-level items that match the browser
+ // window's current context and hide items that don't. Each module instance
+ // is responsible for showing and hiding the items it owns.
+ _handlePopupShowing: function CMP__handlePopupShowing() {
+
+ // If there are items queued up to finish initializing, let them go first.
+ // Otherwise the overflow submenu and menu separator may end up in an
+ // inappropriate state when those items are later added to the menu.
+ if (numItemsWithUnfinishedInit) {
+ const self = this;
+ timer.setTimeout(function popupShowingTryAgain() {
+ self._handlePopupShowing();
+ }, 0);
+ return;
+ }
+
+ let popupNode = this._getPopupNode();
+ // Show and hide items. Set a "jetpackContextCurrent" property on the
+ // DOM elements to signal which of our items match the current context.
+ for (let itemID in this.topLevelItems) {
+ let item = this.topLevelItems[itemID]
+ let areContextsCurr =
+ this.browserWin.areAllContextsCurrent(item, popupNode);
+
+ // Change the item's label if the return value was a string.
+ if (typeof(areContextsCurr) === "string") {
+ item.label = areContextsCurr;
+ areContextsCurr = true;
+ }
+
+ let { domElt, overflowDOMElt } = this.browserWin.items[itemID];
+ domElt.jetpackContextCurrent = areContextsCurr;
+ domElt.hidden = !areContextsCurr;
+ overflowDOMElt.jetpackContextCurrent = areContextsCurr;
+ overflowDOMElt.hidden = !areContextsCurr;
+ }
+
+ // Get the total number of items that match the current context. It's a
+ // little tricky: There may be other instances of this module loaded,
+ // each hiding and showing their items. So we can't base this number on
+ // only our items, or on the hidden state of items. That's why we set
+ // the jetpackContextCurrent property above. The last instance to run
+ // will leave the menupopup in the correct state.
+ let elts = this._topLevelElts;
+ let numShown = Array.reduce(elts, function (total, elt) {
+ return total + (elt.jetpackContextCurrent ? 1 : 0);
+ }, 0);
+
+ // If too many items are shown, show the submenu and hide the top-level
+ // items. Otherwise, hide the submenu and show the top-level items.
+ let overflow = numShown > this._overflowThreshold;
+ if (overflow)
+ Array.forEach(elts, function (e) e.hidden = true);
+
+ let submenu = this._overflowMenu;
+ if (submenu)
+ submenu.hidden = !overflow;
+
+ // If no items are shown, hide the menu separator.
+ let sep = this._separator;
+ if (sep)
+ sep.hidden = numShown === 0;
+ },
+
+ // Adds the menu separator and overflow submenu if they don't exist.
+ _ensureStaticEltsExist: function CMP__ensureStaticEltsExist() {
+ let sep = this._separator;
+ if (!sep) {
+ sep = this._makeSeparator();
+ this.popupDOMElt.appendChild(sep);
+ }
+
+ let submenu = this._overflowMenu;
+ if (!submenu) {
+ submenu = this._makeOverflowMenu();
+ submenu.hidden = true;
+ this.popupDOMElt.insertBefore(submenu, sep.nextSibling);
+ }
+ },
+
+ // Inserts the given item's DOM element into the popup in sorted order.
+ _insertItemInSortedOrder: function CMP__insertItemInSortedOrder(item) {
+ let props = this.browserWin.items[privateItem(item)._id];
+ this.popupDOMElt.insertBefore(
+ props.domElt, insertionPoint(item.label, this._topLevelElts));
+ this._overflowPopup.insertBefore(
+ props.overflowDOMElt, insertionPoint(item.label, this._overflowElts));
+ },
+
+ // Creates and returns the xul:menu that's shown when too many items are added
+ // to the popup.
+ _makeOverflowMenu: function CMP__makeOverflowMenu() {
+ let submenu = this.doc.createElement("menu");
+ submenu.id = OVERFLOW_MENU_ID;
+ submenu.setAttribute("label", OVERFLOW_MENU_LABEL);
+ let popup = this.doc.createElement("menupopup");
+ popup.id = OVERFLOW_POPUP_ID;
+ submenu.appendChild(popup);
+ return submenu;
+ },
+
+ // Creates and returns the xul:menuseparator that separates the standard
+ // context menu items from our items.
+ _makeSeparator: function CMP__makeSeparator() {
+ let elt = this.doc.createElement("menuseparator");
+ elt.id = SEPARATOR_ID;
+ return elt;
+ },
+
+ // Returns the item elements contained in the overflow menu, a NodeList.
+ get _overflowElts() {
+ return this._overflowPopup.getElementsByClassName(OVERFLOW_ITEM_CLASS);
+ },
+
+ // Returns the overflow xul:menu.
+ get _overflowMenu() {
+ return this.doc.getElementById(OVERFLOW_MENU_ID);
+ },
+
+ // Returns the overflow xul:menupopup.
+ get _overflowPopup() {
+ return this.doc.getElementById(OVERFLOW_POPUP_ID);
+ },
+
+ // Returns the OVERFLOW_THRESH_PREF pref value if it exists or
+ // OVERFLOW_THRESH_DEFAULT if it doesn't.
+ get _overflowThreshold() {
+ let prefs = require("./preferences/service");
+ return prefs.get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT);
+ },
+
+ // Returns the xul:menuseparator.
+ get _separator() {
+ return this.doc.getElementById(SEPARATOR_ID);
+ },
+
+ // Returns the item elements contained in the top-level menu, a NodeList.
+ get _topLevelElts() {
+ return this.popupDOMElt.getElementsByClassName(TOPLEVEL_ITEM_CLASS);
+ }
+};
+
+
+// Init the browserManager only after setting prototypes and such above, because
+// it will cause browserManager.onTrack to be called immediately if there are
+// open windows.
+browserManager.init();
diff --git a/tools/addon-sdk-1.12/lib/sdk/core/heritage.js b/tools/addon-sdk-1.12/lib/sdk/core/heritage.js
new file mode 100644
index 0000000..1c62e6c
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/core/heritage.js
@@ -0,0 +1,146 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* 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"
+};
+
+var getPrototypeOf = Object.getPrototypeOf;
+var getNames = Object.getOwnPropertyNames;
+var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
+var create = Object.create;
+var freeze = Object.freeze;
+var unbind = Function.call.bind(Function.bind, Function.call);
+
+// This shortcut makes sure that we do perform desired operations, even if
+// associated methods have being overridden on the used object.
+var owns = unbind(Object.prototype.hasOwnProperty);
+var apply = unbind(Function.prototype.apply);
+var slice = Array.slice || unbind(Array.prototype.slice);
+var reduce = Array.reduce || unbind(Array.prototype.reduce);
+var map = Array.map || unbind(Array.prototype.map);
+var concat = Array.concat || unbind(Array.prototype.concat);
+
+// Utility function to get own properties descriptor map.
+function getOwnPropertyDescriptors(object) {
+ return reduce(getNames(object), function(descriptor, name) {
+ descriptor[name] = getOwnPropertyDescriptor(object, name);
+ return descriptor;
+ }, {});
+}
+
+/**
+ * Takes `source` object as an argument and returns identical object
+ * with the difference that all own properties will be non-enumerable
+ */
+function obscure(source) {
+ var descriptor = reduce(getNames(source), function(descriptor, name) {
+ var property = getOwnPropertyDescriptor(source, name);
+ property.enumerable = false;
+ descriptor[name] = property;
+ return descriptor;
+ }, {});
+ return create(getPrototypeOf(source), descriptor);
+}
+exports.obscure = obscure;
+
+/**
+ * Takes arbitrary number of source objects and returns fresh one, that
+ * inherits from the same prototype as a first argument and implements all
+ * own properties of all argument objects. If two or more argument objects
+ * have own properties with the same name, the property is overridden, with
+ * precedence from right to left, implying, that properties of the object on
+ * the left are overridden by a same named property of the object on the right.
+ */
+var mix = function(source) {
+ var descriptor = reduce(slice(arguments), function(descriptor, source) {
+ return reduce(getNames(source), function(descriptor, name) {
+ descriptor[name] = getOwnPropertyDescriptor(source, name);
+ return descriptor;
+ }, descriptor);
+ }, {});
+
+ return create(getPrototypeOf(source), descriptor);
+};
+exports.mix = mix;
+
+/**
+ * Returns a frozen object with that inherits from the given `prototype` and
+ * implements all own properties of the given `properties` object.
+ */
+function extend(prototype, properties) {
+ return freeze(create(prototype, getOwnPropertyDescriptors(properties)));
+}
+exports.extend = extend;
+
+/**
+ * Returns a constructor function with a proper `prototype` setup. Returned
+ * constructor's `prototype` inherits from a given `options.extends` or
+ * `Class.prototype` if omitted and implements all the properties of the
+ * given `option`. If `options.implemens` array is passed, it's elements
+ * will be mixed into prototype as well. Also, `options.extends` can be
+ * a function or a prototype. If function than it's prototype is used as
+ * an ancestor of the prototype, if it's an object that it's used directly.
+ * Also `options.implements` may contain functions or objects, in case of
+ * functions their prototypes are used for mixing.
+ */
+var Class = new function() {
+ function prototypeOf(input) {
+ return typeof(input) === 'function' ? input.prototype : input;
+ }
+ var none = freeze([]);
+
+ return function Class(options) {
+ // Create descriptor with normalized `options.extends` and
+ // `options.implements`.
+ var descriptor = {
+ // Normalize extends property of `options.extends` to a prototype object
+ // in case it's constructor. If property is missing that fallback to
+ // `Type.prototype`.
+ extends: owns(options, 'extends') ?
+ prototypeOf(options.extends) : Class.prototype,
+ // Normalize `options.implements` to make sure that it's array of
+ // prototype objects instead of constructor functions.
+ implements: owns(options, 'implements') ?
+ freeze(map(options.implements, prototypeOf)) : none
+ };
+
+ // Create array of property descriptors who's properties will be defined
+ // on the resulting prototype. Note: Using reflection `concat` instead of
+ // method as it may be overridden.
+ var descriptors = concat(descriptor.implements, options, descriptor);
+ // Create `prototype` that inherits from given ancestor passed as
+ // `options.extends`, falling back to `Type.prototype`, implementing all
+ // properties of given `options.implements` and `options` itself.
+ var prototype = extend(descriptor.extends, mix.apply(mix, descriptors));
+
+ // Note: we use reflection `apply` in the constructor instead of method
+ // call since later may be overridden.
+ function constructor() {
+ return apply(prototype.constructor, create(prototype), arguments);
+ }
+ constructor.prototype = prototype;
+ return freeze(constructor);
+ };
+}
+Class.prototype = extend(null, obscure({
+ constructor: function constructor() {
+ this.initialize.apply(this, arguments);
+ return this;
+ },
+ initialize: function initialize() {
+ // Do your initialization logic here
+ },
+ // Copy useful properties from `Object.prototype`.
+ toString: Object.prototype.toString,
+ toLocaleString: Object.prototype.toLocaleString,
+ toSource: Object.prototype.toSource,
+ valueOf: Object.prototype.valueOf,
+ isPrototypeOf: Object.prototype.isPrototypeOf
+}));
+exports.Class = freeze(Class);
diff --git a/tools/addon-sdk-1.12/lib/sdk/core/namespace.js b/tools/addon-sdk-1.12/lib/sdk/core/namespace.js
new file mode 100644
index 0000000..3ceb73b
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/core/namespace.js
@@ -0,0 +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";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const create = Object.create;
+const prototypeOf = Object.getPrototypeOf;
+
+/**
+ * Returns a new namespace, function that may can be used to access an
+ * namespaced object of the argument argument. Namespaced object are associated
+ * with owner objects via weak references. Namespaced objects inherit from the
+ * owners ancestor namespaced object. If owner's ancestor is `null` then
+ * namespaced object inherits from given `prototype`. Namespaces can be used
+ * to define internal APIs that can be shared via enclosing `namespace`
+ * function.
+ * @examples
+ * const internals = ns();
+ * internals(object).secret = secret;
+ */
+function ns() {
+ const map = new WeakMap();
+ return function namespace(target) {
+ if (!target) // If `target` is not an object return `target` itself.
+ return target;
+ // If target has no namespaced object yet, create one that inherits from
+ // the target prototype's namespaced object.
+ if (!map.has(target))
+ map.set(target, create(namespace(prototypeOf(target) || null)));
+
+ return map.get(target);
+ };
+};
+
+// `Namespace` is a e4x function in the scope, so we export the function also as
+// `ns` as alias to avoid clashing.
+exports.ns = ns;
+exports.Namespace = ns;
diff --git a/tools/addon-sdk-1.12/lib/sdk/core/promise.js b/tools/addon-sdk-1.12/lib/sdk/core/promise.js
new file mode 100644
index 0000000..2506c80
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/core/promise.js
@@ -0,0 +1,230 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/*jshint asi: true undef: true es5: true node: true browser: true devel: true
+ forin: true latedef: false */
+/*global define: true, Cu: true, __URI__: true */
+;(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
+ factory(function require(uri) {
+ var imports = {};
+ this['Components'].utils.import(uri, imports);
+ return imports;
+ }, this, { uri: __URI__, id: id });
+ this.EXPORTED_SYMBOLS = Object.keys(this);
+ } else { // Browser or alike
+ var globals = this
+ factory(function require(id) {
+ return globals[id];
+ }, (globals[id] = {}), { uri: document.location.href + '#' + id, id: id });
+ }
+}).call(this, 'loader', function(require, exports, module) {
+
+'use strict';
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+function resolution(value) {
+ /**
+ Returns non-standard compliant (`then` does not returns a promise) promise
+ that resolves to a given `value`. Used just internally only.
+ **/
+ return { then: function then(resolve) { resolve(value) } }
+}
+
+function rejection(reason) {
+ /**
+ Returns non-standard compliant promise (`then` does not returns a promise)
+ that rejects with a given `reason`. This is used internally only.
+ **/
+ return { then: function then(resolve, reject) { reject(reason) } }
+}
+
+function attempt(f) {
+ /**
+ Returns wrapper function that delegates to `f`. If `f` throws then captures
+ error and returns promise that rejects with a thrown error. Otherwise returns
+ return value. (Internal utility)
+ **/
+ return function effort(options) {
+ try { return f(options) }
+ catch(error) { return rejection(error) }
+ }
+}
+
+function isPromise(value) {
+ /**
+ Returns true if given `value` is promise. Value is assumed to be promise if
+ it implements `then` method.
+ **/
+ return value && typeof(value.then) === 'function'
+}
+
+function defer(prototype) {
+ /**
+ Returns object containing following properties:
+ - `promise` Eventual value representation implementing CommonJS [Promises/A]
+ (http://wiki.commonjs.org/wiki/Promises/A) API.
+ - `resolve` Single shot function that resolves returned `promise` with a given
+ `value` argument.
+ - `reject` Single shot function that rejects returned `promise` with a given
+ `reason` argument.
+
+ Given `prototype` argument is used as a prototype of the returned `promise`
+ allowing one to implement additional API. If prototype is not passed then
+ it falls back to `Object.prototype`.
+
+ ## Examples
+
+ // Simple usage.
+ var deferred = defer()
+ deferred.promise.then(console.log, console.error)
+ deferred.resolve(value)
+
+ // Advanced usage
+ var prototype = {
+ get: function get(name) {
+ return this.then(function(value) {
+ return value[name];
+ })
+ }
+ }
+
+ var foo = defer(prototype)
+ deferred.promise.get('name').then(console.log)
+ deferred.resolve({ name: 'Foo' })
+ //=> 'Foo'
+ */
+ var pending = [], result
+ prototype = (prototype || prototype === null) ? prototype : Object.prototype
+
+ // Create an object implementing promise API.
+ var promise = Object.create(prototype, {
+ then: { value: function then(resolve, reject) {
+ // create a new deferred using a same `prototype`.
+ var deferred = defer(prototype)
+ // If `resolve / reject` callbacks are not provided.
+ resolve = resolve ? attempt(resolve) : resolution
+ reject = reject ? attempt(reject) : rejection
+
+ // Create a listeners for a enclosed promise resolution / rejection that
+ // delegate to an actual callbacks and resolve / reject returned promise.
+ function resolved(value) { deferred.resolve(resolve(value)) }
+ function rejected(reason) { deferred.resolve(reject(reason)) }
+
+ // If promise is pending register listeners. Otherwise forward them to
+ // resulting resolution.
+ if (pending) pending.push([ resolved, rejected ])
+ else result.then(resolved, rejected)
+
+ return deferred.promise
+ }}
+ })
+
+ var deferred = {
+ promise: promise,
+ resolve: function resolve(value) {
+ /**
+ Resolves associated `promise` to a given `value`, unless it's already
+ resolved or rejected.
+ **/
+ if (pending) {
+ // store resolution `value` as a promise (`value` itself may be a
+ // promise), so that all subsequent listeners can be forwarded to it,
+ // which either resolves immediately or forwards if `value` is
+ // a promise.
+ result = isPromise(value) ? value : resolution(value)
+ // forward all pending observers.
+ while (pending.length) result.then.apply(result, pending.shift())
+ // mark promise as resolved.
+ pending = null
+ }
+ },
+ reject: function reject(reason) {
+ /**
+ Rejects associated `promise` with a given `reason`, unless it's already
+ resolved / rejected.
+ **/
+ deferred.resolve(rejection(reason))
+ }
+ }
+
+ return deferred
+}
+exports.defer = defer
+
+function resolve(value, prototype) {
+ /**
+ Returns a promise resolved to a given `value`. Optionally second `prototype`
+ arguments my be provided to be used as a prototype for a returned promise.
+ **/
+ var deferred = defer(prototype)
+ deferred.resolve(value)
+ return deferred.promise
+}
+exports.resolve = resolve
+
+function reject(reason, prototype) {
+ /**
+ Returns a promise that is rejected with a given `reason`. Optionally second
+ `prototype` arguments my be provided to be used as a prototype for a returned
+ promise.
+ **/
+ var deferred = defer(prototype)
+ deferred.reject(reason)
+ return deferred.promise
+}
+exports.reject = reject
+
+var promised = (function() {
+ // Note: Define shortcuts and utility functions here in order to avoid
+ // slower property accesses and unnecessary closure creations on each
+ // call of this popular function.
+
+ var call = Function.call
+ var concat = Array.prototype.concat
+
+ // Utility function that does following:
+ // execute([ f, self, args...]) => f.apply(self, args)
+ function execute(args) { return call.apply(call, args) }
+
+ // Utility function that takes promise of `a` array and maybe promise `b`
+ // as arguments and returns promise for `a.concat(b)`.
+ function promisedConcat(promises, unknown) {
+ return promises.then(function(values) {
+ return resolve(unknown).then(function(value) {
+ return values.concat([ value ])
+ })
+ })
+ }
+
+ return function promised(f, prototype) {
+ /**
+ Returns a wrapped `f`, which when called returns a promise that resolves to
+ `f(...)` passing all the given arguments to it, which by the way may be
+ promises. Optionally second `prototype` argument may be provided to be used
+ a prototype for a returned promise.
+
+ ## Example
+
+ var promise = promised(Array)(1, promise(2), promise(3))
+ promise.then(console.log) // => [ 1, 2, 3 ]
+ **/
+
+ return function promised() {
+ // create array of [ f, this, args... ]
+ return concat.apply([ f, this ], arguments).
+ // reduce it via `promisedConcat` to get promised array of fulfillments
+ reduce(promisedConcat, resolve([], prototype)).
+ // finally map that to promise of `f.apply(this, args...)`
+ then(execute)
+ }
+ }
+})()
+exports.promised = promised
+
+})
diff --git a/tools/addon-sdk-1.12/lib/sdk/deprecated/api-utils.js b/tools/addon-sdk-1.12/lib/sdk/deprecated/api-utils.js
new file mode 100644
index 0000000..521ad6e
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/deprecated/api-utils.js
@@ -0,0 +1,158 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 memory = require("./memory");
+// The possible return values of getTypeOf.
+const VALID_TYPES = [
+ "array",
+ "boolean",
+ "function",
+ "null",
+ "number",
+ "object",
+ "string",
+ "undefined",
+];
+
+/**
+ * Returns a function C that creates instances of privateCtor. C may be called
+ * with or without the new keyword. The prototype of each instance returned
+ * from C is C.prototype, and C.prototype is an object whose prototype is
+ * privateCtor.prototype. Instances returned from C will therefore be instances
+ * of both C and privateCtor. Additionally, the constructor of each instance
+ * returned from C is C.
+ *
+ * @param privateCtor
+ * A constructor.
+ * @return A function that makes new instances of privateCtor.
+ */
+exports.publicConstructor = function publicConstructor(privateCtor) {
+ function PublicCtor() {
+ let obj = { constructor: PublicCtor, __proto__: PublicCtor.prototype };
+ memory.track(obj, privateCtor.name);
+ privateCtor.apply(obj, arguments);
+ return obj;
+ }
+ PublicCtor.prototype = { __proto__: privateCtor.prototype };
+ return PublicCtor;
+};
+
+/**
+ * Returns a validated options dictionary given some requirements. If any of
+ * the requirements are not met, an exception is thrown.
+ *
+ * @param options
+ * An object, the options dictionary to validate. It's not modified.
+ * If it's null or otherwise falsey, an empty object is assumed.
+ * @param requirements
+ * An object whose keys are the expected keys in options. Any key in
+ * options that is not present in requirements is ignored. Each value
+ * in requirements is itself an object describing the requirements of
+ * its key. There are four optional keys in this object:
+ * map: A function that's passed the value of the key in options.
+ * map's return value is taken as the key's value in the final
+ * validated options, is, and ok. If map throws an exception
+ * it's caught and discarded, and the key's value is its value in
+ * options.
+ * is: An array containing any number of the typeof type names. If
+ * the key's value is none of these types, it fails validation.
+ * Arrays and null are identified by the special type names
+ * "array" and "null"; "object" will not match either. No type
+ * coercion is done.
+ * ok: A function that's passed the key's value. If it returns
+ * false, the value fails validation.
+ * msg: If the key's value fails validation, an exception is thrown.
+ * This string will be used as its message. If undefined, a
+ * generic message is used, unless is is defined, in which case
+ * the message will state that the value needs to be one of the
+ * given types.
+ * @return An object whose keys are those keys in requirements that are also in
+ * options and whose values are the corresponding return values of map
+ * or the corresponding values in options. Note that any keys not
+ * shared by both requirements and options are not in the returned
+ * object.
+ */
+exports.validateOptions = function validateOptions(options, requirements) {
+ options = options || {};
+ let validatedOptions = {};
+ let mapThrew = false;
+
+ for (let key in requirements) {
+ let req = requirements[key];
+ let [optsVal, keyInOpts] = (key in options) ?
+ [options[key], true] :
+ [undefined, false];
+ if (req.map) {
+ try {
+ optsVal = req.map(optsVal);
+ }
+ catch (err) {
+ mapThrew = true;
+ }
+ }
+ if (req.is) {
+ // Sanity check the caller's type names.
+ req.is.forEach(function (typ) {
+ if (VALID_TYPES.indexOf(typ) < 0) {
+ let msg = 'Internal error: invalid requirement type "' + typ + '".';
+ throw new Error(msg);
+ }
+ });
+ if (req.is.indexOf(getTypeOf(optsVal)) < 0)
+ throw requirementError(key, req);
+ }
+ if (req.ok && !req.ok(optsVal))
+ throw requirementError(key, req);
+
+ if (keyInOpts || (req.map && !mapThrew))
+ validatedOptions[key] = optsVal;
+ }
+
+ return validatedOptions;
+};
+
+exports.addIterator = function addIterator(obj, keysValsGenerator) {
+ obj.__iterator__ = function(keysOnly, keysVals) {
+ let keysValsIterator = keysValsGenerator.call(this);
+
+ // "for (.. in ..)" gets only keys, "for each (.. in ..)" gets values,
+ // and "for (.. in Iterator(..))" gets [key, value] pairs.
+ let index = keysOnly ? 0 : 1;
+ while (true)
+ yield keysVals ? keysValsIterator.next() : keysValsIterator.next()[index];
+ };
+};
+
+// Similar to typeof, except arrays and null are identified by "array" and
+// "null", not "object".
+let getTypeOf = exports.getTypeOf = function getTypeOf(val) {
+ let typ = typeof(val);
+ if (typ === "object") {
+ if (!val)
+ return "null";
+ if (Array.isArray(val))
+ return "array";
+ }
+ return typ;
+}
+
+// Returns a new Error with a nice message.
+function requirementError(key, requirement) {
+ let msg = requirement.msg;
+ if (!msg) {
+ msg = 'The option "' + key + '" ';
+ msg += requirement.is ?
+ "must be one of the following types: " + requirement.is.join(", ") :
+ "is invalid.";
+ }
+ return new Error(msg);
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/deprecated/app-strings.js b/tools/addon-sdk-1.12/lib/sdk/deprecated/app-strings.js
new file mode 100644
index 0000000..e09f79e
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/deprecated/app-strings.js
@@ -0,0 +1,67 @@
+/* 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 {Cc,Ci} = require("chrome");
+const apiUtils = require("./api-utils");
+
+/**
+ * A bundle of strings.
+ *
+ * @param url {String}
+ * the URL of the string bundle
+ */
+exports.StringBundle = apiUtils.publicConstructor(function StringBundle(url) {
+
+ let stringBundle = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle(url);
+
+ this.__defineGetter__("url", function () url);
+
+ /**
+ * Get a string from the bundle.
+ *
+ * @param name {String}
+ * the name of the string to get
+ * @param args {array} [optional]
+ * an array of arguments that replace occurrences of %S in the string
+ *
+ * @returns {String} the value of the string
+ */
+ this.get = function strings_get(name, args) {
+ try {
+ if (args)
+ return stringBundle.formatStringFromName(name, args, args.length);
+ else
+ return stringBundle.GetStringFromName(name);
+ }
+ catch(ex) {
+ // f.e. "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE)
+ // [nsIStringBundle.GetStringFromName]"
+ throw new Error("String '" + name + "' could not be retrieved from the " +
+ "bundle due to an unknown error (it doesn't exist?).");
+ }
+ },
+
+ /**
+ * Iterate the strings in the bundle.
+ *
+ */
+ apiUtils.addIterator(
+ this,
+ function keysValsGen() {
+ let enumerator = stringBundle.getSimpleEnumeration();
+ while (enumerator.hasMoreElements()) {
+ let elem = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
+ yield [elem.key, elem.value];
+ }
+ }
+ );
+});
diff --git a/tools/addon-sdk-1.12/lib/sdk/deprecated/cortex.js b/tools/addon-sdk-1.12/lib/sdk/deprecated/cortex.js
new file mode 100644
index 0000000..8eef91c
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/deprecated/cortex.js
@@ -0,0 +1,113 @@
+/* vim:set ts=2 sw=2 sts=2
+ * 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"
+};
+
+// `var` is being used in the module in order to make it reusable in
+// environments in which `let` and `const` is not yet supported.
+
+// Returns `object`'s property value, where `name` is a name of the property.
+function get(object, name) {
+ return object[name];
+}
+
+// Assigns `value` to the `object`'s property, where `name` is the name of the
+// property.
+function set(object, name, value) {
+ return object[name] = value;
+}
+
+/**
+ * Given an `object` containing a property with the given `name`, create
+ * a property descriptor that can be used to define alias/proxy properties
+ * on other objects. A change in the value of an alias will propagate
+ * to the aliased property and vice versa.
+ */
+function createAliasProperty(object, name) {
+ // Getting own property descriptor of an `object` for the given `name` as
+ // we are going to create proxy analog.
+ var property = Object.getOwnPropertyDescriptor(object, name);
+ var descriptor = {
+ configurable: property.configurable,
+ enumerable: property.enumerable,
+ alias: true
+ };
+
+ // If the original property has a getter and/or setter, bind a
+ // corresponding getter/setter in the alias descriptor to the original
+ // object, so the `this` object in the getter/setter is the original object
+ // rather than the alias.
+ if ("get" in property && property.get)
+ descriptor.get = property.get.bind(object);
+ if ("set" in property && property.set)
+ descriptor.set = property.set.bind(object);
+
+ // If original property was a value property.
+ if ("value" in property) {
+ // If original property is a method using it's `object` bounded copy.
+ if (typeof property.value === "function") {
+ descriptor.value = property.value.bind(object);
+ // Also preserving writability of the original property.
+ descriptor.writable = property.writable;
+ }
+
+ // If the original property was just a data property, we create proxy
+ // accessors using our custom get/set functions to propagate changes to the
+ // original `object` and vice versa.
+ else {
+ descriptor.get = get.bind(null, object, name);
+ descriptor.set = set.bind(null, object, name);
+ }
+ }
+ return descriptor;
+}
+
+// Defines property on `object` object with a name `alias` if given if not
+// defaults to `name` that represents an alias of `source[name]`. If aliased
+// property was an assessor or a method `this` pseudo-variable will be `source`
+// when invoked. If aliased property was a data property changes on any of the
+// aliases will propagate to the `source[name]` and also other way round.
+function defineAlias(source, target, name, alias) {
+ return Object.defineProperty(target, alias || name,
+ createAliasProperty(source, name));
+}
+
+/**
+ * Function takes any `object` and returns a proxy for its own public
+ * properties. By default properties are considered to be public if they don't
+ * start with `"_"`, but default behavior can be overridden if needed, by
+ * passing array of public property `names` as a second argument. By default
+ * returned object will be direct decedent of the given `object`'s prototype,
+ * but this can be overridden by passing third optional argument, that will be
+ * used as `prototype` instead.
+ * @param {Object} object
+ * Object to create cortex for.
+ * @param {String[]} [names]
+ * Optional array of public property names.
+ * @param {Object} [prototype]
+ * Optional argument that will be used as `prototype` of the returned object,
+ * if not provided `Object.getPrototypeOf(object)` is used instead.
+ */
+exports.Cortex = function Cortex(object, names, prototype) {
+ // Creating a cortex object from the given `prototype`, if one was not
+ // provided then `prototype` of a given `object` is used. This allows
+ // consumer to define expected behavior `instanceof`. In common case
+ // `prototype` argument can be omitted to preserve same behavior of
+ // `instanceof` as on original `object`.
+ var cortex = Object.create(prototype || Object.getPrototypeOf(object));
+ // Creating alias properties on the `cortex` object for all the own
+ // properties of the original `object` that are contained in `names` array.
+ // If `names` array is not provided then all the properties that don't
+ // start with `"_"` are aliased.
+ Object.getOwnPropertyNames(object).forEach(function (name) {
+ if ((!names && "_" !== name.charAt(0)) || (names && ~names.indexOf(name)))
+ defineAlias(object, cortex, name);
+ });
+ return cortex;
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/deprecated/errors.js b/tools/addon-sdk-1.12/lib/sdk/deprecated/errors.js
new file mode 100644
index 0000000..b640d05
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/deprecated/errors.js
@@ -0,0 +1,64 @@
+/* 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"
+};
+
+function logToConsole(e) {
+ console.exception(e);
+}
+
+var catchAndLog = exports.catchAndLog = function(callback,
+ defaultResponse,
+ logException) {
+ if (!logException)
+ logException = logToConsole;
+
+ return function() {
+ try {
+ return callback.apply(this, arguments);
+ } catch (e) {
+ logException(e);
+ return defaultResponse;
+ }
+ };
+};
+
+exports.catchAndLogProps = function catchAndLogProps(object,
+ props,
+ defaultResponse,
+ logException) {
+ if (typeof(props) == "string")
+ props = [props];
+ props.forEach(
+ function(property) {
+ object[property] = catchAndLog(object[property],
+ defaultResponse,
+ logException);
+ });
+};
+
+/**
+ * Catch and return an exception while calling the callback. If the callback
+ * doesn't throw, return the return value of the callback in a way that makes it
+ * possible to distinguish between a return value and an exception.
+ *
+ * This function is useful when you need to pass the result of a call across
+ * a process boundary (across which exceptions don't propagate). It probably
+ * doesn't need to be factored out into this module, since it is only used by
+ * a single caller, but putting it here works around bug 625560.
+ */
+exports.catchAndReturn = function(callback) {
+ return function() {
+ try {
+ return { returnValue: callback.apply(this, arguments) };
+ }
+ catch (exception) {
+ return { exception: exception };
+ }
+ };
+};
diff --git a/tools/addon-sdk-1.12/lib/sdk/deprecated/events.js b/tools/addon-sdk-1.12/lib/sdk/deprecated/events.js
new file mode 100644
index 0000000..0c28a1a
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/deprecated/events.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";
+
+module.metadata = {
+ "stability": "deprecated"
+};
+
+const ERROR_TYPE = 'error',
+ UNCAUGHT_ERROR = 'An error event was dispatched for which there was'
+ + ' no listener.',
+ BAD_LISTENER = 'The event listener must be a function.';
+/**
+ * This object is used to create an `EventEmitter` that, useful for composing
+ * objects that emit events. It implements an interface like `EventTarget` from
+ * DOM Level 2, which is implemented by Node objects in implementations that
+ * support the DOM Event Model.
+ * @see http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget
+ * @see http://nodejs.org/api.html#EventEmitter
+ * @see http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/events/EventDispatcher.html
+ */
+const eventEmitter = {
+ /**
+ * Registers an event `listener` that is called every time events of
+ * specified `type` are emitted.
+ * @param {String} type
+ * The type of event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ * @example
+ * worker.on('message', function (data) {
+ * console.log('data received: ' + data)
+ * })
+ */
+ on: function on(type, listener) {
+ if ('function' !== typeof listener)
+ throw new Error(BAD_LISTENER);
+ let listeners = this._listeners(type);
+ if (0 > listeners.indexOf(listener))
+ listeners.push(listener);
+ // Use of `_public` is required by the legacy traits code that will go away
+ // once bug-637633 is fixed.
+ return this._public || this;
+ },
+
+ /**
+ * Registers an event `listener` that is called once the next time an event
+ * of the specified `type` is emitted.
+ * @param {String} type
+ * The type of the event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+ once: function once(type, listener) {
+ this.on(type, function selfRemovableListener() {
+ this.removeListener(type, selfRemovableListener);
+ listener.apply(this, arguments);
+ });
+ },
+
+ /**
+ * Unregister `listener` for the specified event type.
+ * @param {String} type
+ * The type of event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+ removeListener: function removeListener(type, listener) {
+ if ('function' !== typeof listener)
+ throw new Error(BAD_LISTENER);
+ let listeners = this._listeners(type),
+ index = listeners.indexOf(listener);
+ if (0 <= index)
+ listeners.splice(index, 1);
+ // Use of `_public` is required by the legacy traits code, that will go away
+ // once bug-637633 is fixed.
+ return this._public || this;
+ },
+
+ /**
+ * Hash of listeners on this EventEmitter.
+ */
+ _events: null,
+
+ /**
+ * Returns an array of listeners for the specified event `type`. This array
+ * can be manipulated, e.g. to remove listeners.
+ * @param {String} type
+ * The type of event.
+ */
+ _listeners: function listeners(type) {
+ let events = this._events || (this._events = {});
+ return events[type] || (events[type] = []);
+ },
+
+ /**
+ * Execute each of the listeners in order with the supplied arguments.
+ * Returns `true` if listener for this event was called, `false` if there are
+ * no listeners for this event `type`.
+ *
+ * All the exceptions that are thrown by listeners during the emit
+ * are caught and can be handled by listeners of 'error' event. Thrown
+ * exceptions are passed as an argument to an 'error' event listener.
+ * If no 'error' listener is registered exception will propagate to a
+ * caller of this method.
+ *
+ * **It's recommended to have a default 'error' listener in all the complete
+ * composition that in worst case may dump errors to the console.**
+ *
+ * @param {String} type
+ * The type of event.
+ * @params {Object|Number|String|Boolean}
+ * Arguments that will be passed to listeners.
+ * @returns {Boolean}
+ */
+ _emit: function _emit(type, event) {
+ let args = Array.slice(arguments);
+ // Use of `_public` is required by the legacy traits code that will go away
+ // once bug-637633 is fixed.
+ args.unshift(this._public || this);
+ return this._emitOnObject.apply(this, args);
+ },
+
+ /**
+ * A version of _emit that lets you specify the object on which listeners are
+ * called. This is a hack that is sometimes necessary when such an object
+ * (exports, for example) cannot be an EventEmitter for some reason, but other
+ * object(s) managing events for the object are EventEmitters. Once bug
+ * 577782 is fixed, this method shouldn't be necessary.
+ *
+ * @param {object} targetObj
+ * The object on which listeners will be called.
+ * @param {string} type
+ * The event name.
+ * @param {value} event
+ * The first argument to pass to listeners.
+ * @param {value} ...
+ * More arguments to pass to listeners.
+ * @returns {boolean}
+ */
+ _emitOnObject: function _emitOnObject(targetObj, type, event /* , ... */) {
+ let listeners = this._listeners(type).slice(0);
+ // If there is no 'error' event listener then throw.
+ if (type === ERROR_TYPE && !listeners.length)
+ console.exception(event);
+ if (!listeners.length)
+ return false;
+ let params = Array.slice(arguments, 2);
+ for each (let listener in listeners) {
+ try {
+ listener.apply(targetObj, params);
+ } catch(e) {
+ // Bug 726967: Ignore exceptions being throws while notifying the error
+ // in order to avoid infinite loops.
+ if (type !== ERROR_TYPE)
+ this._emit(ERROR_TYPE, e);
+ else
+ console.exception("Exception in error event listener " + e);
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Removes all the event listeners for the specified event `type`.
+ * @param {String} type
+ * The type of event.
+ */
+ _removeAllListeners: function _removeAllListeners(type) {
+ if (typeof type == "undefined") {
+ this._events = null;
+ return this;
+ }
+
+ this._listeners(type).splice(0);
+ return this;
+ }
+};
+exports.EventEmitter = require("./traits").Trait.compose(eventEmitter);
+exports.EventEmitterTrait = require('./light-traits').Trait(eventEmitter);
diff --git a/tools/addon-sdk-1.12/lib/sdk/deprecated/events/assembler.js b/tools/addon-sdk-1.12/lib/sdk/deprecated/events/assembler.js
new file mode 100644
index 0000000..fcde1e3
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/deprecated/events/assembler.js
@@ -0,0 +1,53 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 { 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({
+ /**
+ * 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,
+ /**
+ * Array of supported event names.
+ * @type {String[]}
+ */
+ supportedEventsTypes: Trait.required,
+ /**
+ * 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);
+ }, this);
+ },
+ /**
+ * Removes `eventTarget` from the list of observed `eventTarget`s. Listeners
+ * for all supported events will be unregistered from the given `eventTarget`.
+ * @param {EventTarget} eventTarget
+ */
+ ignore: function ignore(eventTarget) {
+ this.supportedEventsTypes.forEach(function(eventType) {
+ removeListener(eventTarget, eventType, this);
+ }, this);
+ }
+});
diff --git a/tools/addon-sdk-1.12/lib/sdk/deprecated/light-traits.js b/tools/addon-sdk-1.12/lib/sdk/deprecated/light-traits.js
new file mode 100644
index 0000000..b18dccb
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/deprecated/light-traits.js
@@ -0,0 +1,600 @@
+/* vim:ts=2:sts=2:sw=2:
+ * 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"
+};
+
+// `var` is being used in the module in order to make it reusable in
+// environments in which `let` is not yet supported.
+
+// Shortcut to `Object.prototype.hasOwnProperty.call`.
+// owns(object, name) would be the same as
+// Object.prototype.hasOwnProperty.call(object, name);
+var owns = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
+
+/**
+ * Whether or not given property descriptors are equivalent. They are
+ * equivalent either if both are marked as 'conflict' or 'required' property
+ * or if all the properties of descriptors are equal.
+ * @param {Object} actual
+ * @param {Object} expected
+ */
+function equivalentDescriptors(actual, expected) {
+ return (actual.conflict && expected.conflict) ||
+ (actual.required && expected.required) ||
+ equalDescriptors(actual, expected);
+}
+/**
+ * Whether or not given property descriptors define equal properties.
+ */
+function equalDescriptors(actual, expected) {
+ return actual.get === expected.get &&
+ actual.set === expected.set &&
+ actual.value === expected.value &&
+ !!actual.enumerable === !!expected.enumerable &&
+ !!actual.configurable === !!expected.configurable &&
+ !!actual.writable === !!expected.writable;
+}
+
+// Utilities that throwing exceptions for a properties that are marked
+// as "required" or "conflict" properties.
+function throwConflictPropertyError(name) {
+ throw new Error("Remaining conflicting property: `" + name + "`");
+}
+function throwRequiredPropertyError(name) {
+ throw new Error("Missing required property: `" + name + "`");
+}
+
+/**
+ * Generates custom **required** property descriptor. Descriptor contains
+ * non-standard property `required` that is equal to `true`.
+ * @param {String} name
+ * property name to generate descriptor for.
+ * @returns {Object}
+ * custom property descriptor
+ */
+function RequiredPropertyDescriptor(name) {
+ // Creating function by binding first argument to a property `name` on the
+ // `throwConflictPropertyError` function. Created function is used as a
+ // getter & setter of the created property descriptor. This way we ensure
+ // that we throw exception late (on property access) if object with
+ // `required` property was instantiated using built-in `Object.create`.
+ var accessor = throwRequiredPropertyError.bind(null, name);
+ return { get: accessor, set: accessor, required: true };
+}
+
+/**
+ * Generates custom **conflicting** property descriptor. Descriptor contains
+ * non-standard property `conflict` that is equal to `true`.
+ * @param {String} name
+ * property name to generate descriptor for.
+ * @returns {Object}
+ * custom property descriptor
+ */
+function ConflictPropertyDescriptor(name) {
+ // For details see `RequiredPropertyDescriptor` since idea is same.
+ var accessor = throwConflictPropertyError.bind(null, name);
+ return { get: accessor, set: accessor, conflict: true };
+}
+
+/**
+ * Tests if property is marked as `required` property.
+ */
+function isRequiredProperty(object, name) {
+ return !!object[name].required;
+}
+
+/**
+ * Tests if property is marked as `conflict` property.
+ */
+function isConflictProperty(object, name) {
+ return !!object[name].conflict;
+}
+
+/**
+ * Function tests whether or not method of the `source` object with a given
+ * `name` is inherited from `Object.prototype`.
+ */
+function isBuiltInMethod(name, source) {
+ var target = Object.prototype[name];
+
+ // If methods are equal then we know it's `true`.
+ return target == source ||
+ // If `source` object comes form a different sandbox `==` will evaluate
+ // to `false`, in that case we check if functions names and sources match.
+ (String(target) === String(source) && target.name === source.name);
+}
+
+/**
+ * Function overrides `toString` and `constructor` methods of a given `target`
+ * object with a same-named methods of a given `source` if methods of `target`
+ * object are inherited / copied from `Object.prototype`.
+ * @see create
+ */
+function overrideBuiltInMethods(target, source) {
+ if (isBuiltInMethod("toString", target.toString)) {
+ Object.defineProperty(target, "toString", {
+ value: source.toString,
+ configurable: true,
+ enumerable: false
+ });
+ }
+
+ if (isBuiltInMethod("constructor", target.constructor)) {
+ Object.defineProperty(target, "constructor", {
+ value: source.constructor,
+ configurable: true,
+ enumerable: false
+ });
+ }
+}
+
+/**
+ * Composes new trait with the same own properties as the original trait,
+ * except that all property names appearing in the first argument are replaced
+ * by "required" property descriptors.
+ * @param {String[]} keys
+ * Array of strings property names.
+ * @param {Object} trait
+ * A trait some properties of which should be excluded.
+ * @returns {Object}
+ * @example
+ * var newTrait = exclude(["name", ...], trait)
+ */
+function exclude(names, trait) {
+ var map = {};
+
+ Object.keys(trait).forEach(function(name) {
+
+ // If property is not excluded (the array of names does not contain it),
+ // or it is a "required" property, copy it to the property descriptor `map`
+ // that will be used for creation of resulting trait.
+ if (!~names.indexOf(name) || isRequiredProperty(trait, name))
+ map[name] = { value: trait[name], enumerable: true };
+
+ // For all the `names` in the exclude name array we create required
+ // property descriptors and copy them to the `map`.
+ else
+ map[name] = { value: RequiredPropertyDescriptor(name), enumerable: true };
+ });
+
+ return Object.create(Trait.prototype, map);
+}
+
+/**
+ * Composes new instance of `Trait` with a properties of a given `trait`,
+ * except that all properties whose name is an own property of `renames` will
+ * be renamed to `renames[name]` and a `"required"` property for name will be
+ * added instead.
+ *
+ * For each renamed property, a required property is generated. If
+ * the `renames` map two properties to the same name, a conflict is generated.
+ * If the `renames` map a property to an existing unrenamed property, a
+ * conflict is generated.
+ *
+ * @param {Object} renames
+ * An object whose own properties serve as a mapping from old names to new
+ * names.
+ * @param {Object} trait
+ * A new trait with renamed properties.
+ * @returns {Object}
+ * @example
+ *
+ * // Return trait with `bar` property equal to `trait.foo` and with
+ * // `foo` and `baz` "required" properties.
+ * var renamedTrait = rename({ foo: "bar", baz: null }), trait);
+ *
+ * // t1 and t2 are equivalent traits
+ * var t1 = rename({a: "b"}, t);
+ * var t2 = compose(exclude(["a"], t), { a: { required: true }, b: t[a] });
+ */
+function rename(renames, trait) {
+ var map = {};
+
+ // Loop over all the properties of the given `trait` and copy them to a
+ // property descriptor `map` that will be used for the creation of the
+ // resulting trait. Also, rename properties in the `map` as specified by
+ // `renames`.
+ Object.keys(trait).forEach(function(name) {
+ var alias;
+
+ // If the property is in the `renames` map, and it isn't a "required"
+ // property (which should never need to be aliased because "required"
+ // properties never conflict), then we must try to rename it.
+ if (owns(renames, name) && !isRequiredProperty(trait, name)) {
+ alias = renames[name];
+
+ // If the `map` already has the `alias`, and it isn't a "required"
+ // property, that means the `alias` conflicts with an existing name for a
+ // provided trait (that can happen if >=2 properties are aliased to the
+ // same name). In this case we mark it as a conflicting property.
+ // Otherwise, everything is fine, and we copy property with an `alias`
+ // name.
+ if (owns(map, alias) && !map[alias].value.required) {
+ map[alias] = {
+ value: ConflictPropertyDescriptor(alias),
+ enumerable: true
+ };
+ }
+ else {
+ map[alias] = {
+ value: trait[name],
+ enumerable: true
+ };
+ }
+
+ // Regardless of whether or not the rename was successful, we check to
+ // see if the original `name` exists in the map (such a property
+ // could exist if previous another property was aliased to this `name`).
+ // If it isn't, we mark it as "required", to make sure the caller
+ // provides another value for the old name, which methods of the trait
+ // might continue to reference.
+ if (!owns(map, name)) {
+ map[name] = {
+ value: RequiredPropertyDescriptor(name),
+ enumerable: true
+ };
+ }
+ }
+
+ // Otherwise, either the property isn't in the `renames` map (thus the
+ // caller is not trying to rename it) or it is a "required" property.
+ // Either way, we don't have to alias the property, we just have to copy it
+ // to the map.
+ else {
+ // The property isn't in the map yet, so we copy it over.
+ if (!owns(map, name)) {
+ map[name] = { value: trait[name], enumerable: true };
+ }
+
+ // The property is already in the map (that means another property was
+ // aliased with this `name`, which creates a conflict if the property is
+ // not marked as "required"), so we have to mark it as a "conflict"
+ // property.
+ else if (!isRequiredProperty(trait, name)) {
+ map[name] = {
+ value: ConflictPropertyDescriptor(name),
+ enumerable: true
+ };
+ }
+ }
+ });
+ return Object.create(Trait.prototype, map);
+}
+
+/**
+ * Composes new resolved trait, with all the same properties as the original
+ * `trait`, except that all properties whose name is an own property of
+ * `resolutions` will be renamed to `resolutions[name]`.
+ *
+ * If `resolutions[name]` is `null`, the value is mapped to a property
+ * descriptor that is marked as a "required" property.
+ */
+function resolve(resolutions, trait) {
+ var renames = {};
+ var exclusions = [];
+
+ // Go through each mapping in `resolutions` object and distribute it either
+ // to `renames` or `exclusions`.
+ Object.keys(resolutions).forEach(function(name) {
+
+ // If `resolutions[name]` is a truthy value then it's a mapping old -> new
+ // so we copy it to `renames` map.
+ if (resolutions[name])
+ renames[name] = resolutions[name];
+
+ // Otherwise it's not a mapping but an exclusion instead in which case we
+ // add it to the `exclusions` array.
+ else
+ exclusions.push(name);
+ });
+
+ // First `exclude` **then** `rename` and order is important since
+ // `exclude` and `rename` are not associative.
+ return rename(renames, exclude(exclusions, trait));
+}
+
+/**
+ * Create a Trait (a custom property descriptor map) that represents the given
+ * `object`'s own properties. Property descriptor map is a "custom", because it
+ * inherits from `Trait.prototype` and it's property descriptors may contain
+ * two attributes that is not part of the ES5 specification:
+ *
+ * - "required" (this property must be provided by another trait
+ * before an instance of this trait can be created)
+ * - "conflict" (when the trait is composed with another trait,
+ * a unique value for this property is provided by two or more traits)
+ *
+ * Data properties bound to the `Trait.required` singleton exported by
+ * this module will be marked as "required" properties.
+ *
+ * @param {Object} object
+ * Map of properties to compose trait from.
+ * @returns {Trait}
+ * Trait / Property descriptor map containing all the own properties of the
+ * given argument.
+ */
+function trait(object) {
+ var map;
+ var trait = object;
+
+ if (!(object instanceof Trait)) {
+ // If the passed `object` is not already an instance of `Trait`, we create
+ // a property descriptor `map` containing descriptors for the own properties
+ // of the given `object`. `map` is then used to create a `Trait` instance
+ // after all properties are mapped. Note that we can't create a trait and
+ // then just copy properties into it since that will fail for inherited
+ // read-only properties.
+ map = {};
+
+ // Each own property of the given `object` is mapped to a data property
+ // whose value is a property descriptor.
+ Object.keys(object).forEach(function (name) {
+
+ // If property of an `object` is equal to a `Trait.required`, it means
+ // that it was marked as "required" property, in which case we map it
+ // to "required" property.
+ if (Trait.required ==
+ Object.getOwnPropertyDescriptor(object, name).value) {
+ map[name] = {
+ value: RequiredPropertyDescriptor(name),
+ enumerable: true
+ };
+ }
+ // Otherwise property is mapped to it's property descriptor.
+ else {
+ map[name] = {
+ value: Object.getOwnPropertyDescriptor(object, name),
+ enumerable: true
+ };
+ }
+ });
+
+ trait = Object.create(Trait.prototype, map);
+ }
+ return trait;
+}
+
+/**
+ * Compose a property descriptor map that inherits from `Trait.prototype` and
+ * contains property descriptors for all the own properties of the passed
+ * traits.
+ *
+ * If two or more traits have own properties with the same name, the returned
+ * trait will contain a "conflict" property for that name. Composition is a
+ * commutative and associative operation, and the order of its arguments is
+ * irrelevant.
+ */
+function compose(trait1, trait2/*, ...*/) {
+ // Create a new property descriptor `map` to which all the own properties
+ // of the passed traits are copied. This map will be used to create a `Trait`
+ // instance that will be the result of this composition.
+ var map = {};
+
+ // Properties of each passed trait are copied to the composition.
+ Array.prototype.forEach.call(arguments, function(trait) {
+ // Copying each property of the given trait.
+ Object.keys(trait).forEach(function(name) {
+
+ // If `map` already owns a property with the `name` and it is not
+ // marked "required".
+ if (owns(map, name) && !map[name].value.required) {
+
+ // If the source trait's property with the `name` is marked as
+ // "required", we do nothing, as the requirement was already resolved
+ // by a property in the `map` (because it already contains a
+ // non-required property with that `name`). But if properties are just
+ // different, we have a name clash and we substitute it with a property
+ // that is marked "conflict".
+ if (!isRequiredProperty(trait, name) &&
+ !equivalentDescriptors(map[name].value, trait[name])
+ ) {
+ map[name] = {
+ value: ConflictPropertyDescriptor(name),
+ enumerable: true
+ };
+ }
+ }
+
+ // Otherwise, the `map` does not have an own property with the `name`, or
+ // it is marked "required". Either way, the trait's property is copied to
+ // the map (if the property of the `map` is marked "required", it is going
+ // to be resolved by the property that is being copied).
+ else {
+ map[name] = { value: trait[name], enumerable: true };
+ }
+ });
+ });
+
+ return Object.create(Trait.prototype, map);
+}
+
+/**
+ * `defineProperties` is like `Object.defineProperties`, except that it
+ * ensures that:
+ * - An exception is thrown if any property in a given `properties` map
+ * is marked as "required" property and same named property is not
+ * found in a given `prototype`.
+ * - An exception is thrown if any property in a given `properties` map
+ * is marked as "conflict" property.
+ * @param {Object} object
+ * Object to define properties on.
+ * @param {Object} properties
+ * Properties descriptor map.
+ * @returns {Object}
+ * `object` that was passed as a first argument.
+ */
+function defineProperties(object, properties) {
+
+ // Create a map into which we will copy each verified property from the given
+ // `properties` description map. We use it to verify that none of the
+ // provided properties is marked as a "conflict" property and that all
+ // "required" properties are resolved by a property of an `object`, so we
+ // can throw an exception before mutating object if that isn't the case.
+ var verifiedProperties = {};
+
+ // Coping each property from a given `properties` descriptor map to a
+ // verified map of property descriptors.
+ Object.keys(properties).forEach(function(name) {
+
+ // If property is marked as "required" property and we don't have a same
+ // named property in a given `object` we throw an exception. If `object`
+ // has same named property just skip this property since required property
+ // is was inherited and there for requirement was satisfied.
+ if (isRequiredProperty(properties, name)) {
+ if (!(name in object))
+ throwRequiredPropertyError(name);
+ }
+
+ // If property is marked as "conflict" property we throw an exception.
+ else if (isConflictProperty(properties, name)) {
+ throwConflictPropertyError(name);
+ }
+
+ // If property is not marked neither as "required" nor "conflict" property
+ // we copy it to verified properties map.
+ else {
+ verifiedProperties[name] = properties[name];
+ }
+ });
+
+ // If no exceptions were thrown yet, we know that our verified property
+ // descriptor map has no properties marked as "conflict" or "required",
+ // so we just delegate to the built-in `Object.defineProperties`.
+ return Object.defineProperties(object, verifiedProperties);
+}
+
+/**
+ * `create` is like `Object.create`, except that it ensures that:
+ * - An exception is thrown if any property in a given `properties` map
+ * is marked as "required" property and same named property is not
+ * found in a given `prototype`.
+ * - An exception is thrown if any property in a given `properties` map
+ * is marked as "conflict" property.
+ * @param {Object} prototype
+ * prototype of the composed object
+ * @param {Object} properties
+ * Properties descriptor map.
+ * @returns {Object}
+ * An object that inherits form a given `prototype` and implements all the
+ * properties defined by a given `properties` descriptor map.
+ */
+function create(prototype, properties) {
+
+ // Creating an instance of the given `prototype`.
+ var object = Object.create(prototype);
+
+ // Overriding `toString`, `constructor` methods if they are just inherited
+ // from `Object.prototype` with a same named methods of the `Trait.prototype`
+ // that will have more relevant behavior.
+ overrideBuiltInMethods(object, Trait.prototype);
+
+ // Trying to define given `properties` on the `object`. We use our custom
+ // `defineProperties` function instead of build-in `Object.defineProperties`
+ // that behaves exactly the same, except that it will throw if any
+ // property in the given `properties` descriptor is marked as "required" or
+ // "conflict" property.
+ return defineProperties(object, properties);
+}
+
+/**
+ * Composes new trait. If two or more traits have own properties with the
+ * same name, the new trait will contain a "conflict" property for that name.
+ * "compose" is a commutative and associative operation, and the order of its
+ * arguments is not significant.
+ *
+ * **Note:** Use `Trait.compose` instead of calling this function with more
+ * than one argument. The multiple-argument functionality is strictly for
+ * backward compatibility.
+ *
+ * @params {Object} trait
+ * Takes traits as an arguments
+ * @returns {Object}
+ * New trait containing the combined own properties of all the traits.
+ * @example
+ * var newTrait = compose(trait_1, trait_2, ..., trait_N)
+ */
+function Trait(trait1, trait2) {
+
+ // If the function was called with one argument, the argument should be
+ // an object whose properties are mapped to property descriptors on a new
+ // instance of Trait, so we delegate to the trait function.
+ // If the function was called with more than one argument, those arguments
+ // should be instances of Trait or plain property descriptor maps
+ // whose properties should be mixed into a new instance of Trait,
+ // so we delegate to the compose function.
+
+ return trait2 === undefined ? trait(trait1) : compose.apply(null, arguments);
+}
+
+Object.freeze(Object.defineProperties(Trait.prototype, {
+ toString: {
+ value: function toString() {
+ return "[object " + this.constructor.name + "]";
+ }
+ },
+
+ /**
+ * `create` is like `Object.create`, except that it ensures that:
+ * - An exception is thrown if this trait defines a property that is
+ * marked as required property and same named property is not
+ * found in a given `prototype`.
+ * - An exception is thrown if this trait contains property that is
+ * marked as "conflict" property.
+ * @param {Object}
+ * prototype of the compared object
+ * @returns {Object}
+ * An object with all of the properties described by the trait.
+ */
+ create: {
+ value: function createTrait(prototype) {
+ return create(undefined === prototype ? Object.prototype : prototype,
+ this);
+ },
+ enumerable: true
+ },
+
+ /**
+ * Composes a new resolved trait, with all the same properties as the original
+ * trait, except that all properties whose name is an own property of
+ * `resolutions` will be renamed to the value of `resolutions[name]`. If
+ * `resolutions[name]` is `null`, the property is marked as "required".
+ * @param {Object} resolutions
+ * An object whose own properties serve as a mapping from old names to new
+ * names, or to `null` if the property should be excluded.
+ * @returns {Object}
+ * New trait with the same own properties as the original trait but renamed.
+ */
+ resolve: {
+ value: function resolveTrait(resolutions) {
+ return resolve(resolutions, this);
+ },
+ enumerable: true
+ }
+}));
+
+/**
+ * @see compose
+ */
+Trait.compose = Object.freeze(compose);
+Object.freeze(compose.prototype);
+
+/**
+ * Constant singleton, representing placeholder for required properties.
+ * @type {Object}
+ */
+Trait.required = Object.freeze(Object.create(Object.prototype, {
+ toString: {
+ value: Object.freeze(function toString() {
+ return "<Trait.required>";
+ })
+ }
+}));
+Object.freeze(Trait.required.toString.prototype);
+
+exports.Trait = Object.freeze(Trait);
diff --git a/tools/addon-sdk-1.12/lib/sdk/deprecated/list.js b/tools/addon-sdk-1.12/lib/sdk/deprecated/list.js
new file mode 100644
index 0000000..5de69e7
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/deprecated/list.js
@@ -0,0 +1,118 @@
+/* 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 { Trait } = require('../deprecated/traits');
+
+/**
+ * @see https://jetpack.mozillalabs.com/sdk/latest/docs/#module/api-utils/list
+ */
+const Iterable = Trait.compose({
+ /**
+ * Hash map of key-values to iterate over.
+ * Note: That this property can be a getter if you need dynamic behavior.
+ * @type {Object}
+ */
+ _keyValueMap: Trait.required,
+ /**
+ * Custom iterator providing `Iterable`s enumeration behavior.
+ * @param {Boolean} onKeys
+ */
+ __iterator__: function __iterator__(onKeys, onKeyValue) {
+ let map = this._keyValueMap;
+ for (let key in map)
+ yield onKeyValue ? [key, map[key]] : onKeys ? key : map[key];
+ }
+});
+exports.Iterable = Iterable;
+
+/**
+ * An ordered collection (also known as a sequence) disallowing duplicate
+ * elements. List is composed out of `Iterable` there for it provides custom
+ * enumeration behavior that is similar to array (enumerates only on the
+ * elements of the list). List is a base trait and is meant to be a part of
+ * composition, since all of it's API is private except length property.
+ */
+const List = Trait.resolve({ toString: null }).compose({
+ _keyValueMap: null,
+ /**
+ * List constructor can take any number of element to populate itself.
+ * @params {Object|String|Number} element
+ * @example
+ * List(1,2,3).length == 3 // true
+ */
+ constructor: function List() {
+ this._keyValueMap = [];
+ for (let i = 0, ii = arguments.length; i < ii; i++)
+ this._add(arguments[i]);
+ },
+ /**
+ * Number of elements in this list.
+ * @type {Number}
+ */
+ get length() this._keyValueMap.length,
+ /**
+ * Returns a string representing this list.
+ * @returns {String}
+ */
+ toString: function toString() 'List(' + this._keyValueMap + ')',
+ /**
+ * Returns `true` if this list contains the specified `element`.
+ * @param {Object|Number|String} element
+ * @returns {Boolean}
+ */
+ _has: function _has(element) 0 <= this._keyValueMap.indexOf(element),
+ /**
+ * Appends the specified `element` to the end of this list, if it doesn't
+ * contains it. Ignores the call if `element` is already contained.
+ * @param {Object|Number|String} element
+ */
+ _add: function _add(element) {
+ let list = this._keyValueMap,
+ index = list.indexOf(element);
+ if (0 > index)
+ list.push(this._public[list.length] = element);
+ },
+ /**
+ * Removes specified `element` from this list, if it contains it.
+ * Ignores the call if `element` is not contained.
+ * @param {Object|Number|String} element
+ */
+ _remove: function _remove(element) {
+ let list = this._keyValueMap,
+ index = list.indexOf(element);
+ if (0 <= index) {
+ delete this._public[list.length - 1];
+ list.splice(index, 1);
+ for (let length = list.length; index < length; index++)
+ this._public[index] = list[index];
+ }
+ },
+ /**
+ * Removes all of the elements from this list.
+ */
+ _clear: function _clear() {
+ for (let i = 0, ii = this._keyValueMap.length; i < ii; i ++)
+ delete this._public[i];
+ this._keyValueMap.splice(0);
+ },
+ /**
+ * Custom iterator providing `List`s enumeration behavior.
+ * We cant reuse `_iterator` that is defined by `Iterable` since it provides
+ * iteration in an arbitrary order.
+ * @see https://developer.mozilla.org/en/JavaScript/Reference/Statements/for...in
+ * @param {Boolean} onKeys
+ */
+ __iterator__: function __iterator__(onKeys, onKeyValue) {
+ let array = this._keyValueMap.slice(0),
+ i = -1;
+ for each(let element in array)
+ yield onKeyValue ? [++i, element] : onKeys ? ++i : element;
+ }
+});
+exports.List = List;
diff --git a/tools/addon-sdk-1.12/lib/sdk/deprecated/memory.js b/tools/addon-sdk-1.12/lib/sdk/deprecated/memory.js
new file mode 100644
index 0000000..70bcffc
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/deprecated/memory.js
@@ -0,0 +1,118 @@
+/* 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 {Cc,Ci,Cu,components} = require("chrome");
+var trackedObjects = {};
+
+var Compacter = {
+ INTERVAL: 5000,
+ notify: function(timer) {
+ var newTrackedObjects = {};
+ for (let name in trackedObjects) {
+ var oldBin = trackedObjects[name];
+ var newBin = [];
+ var strongRefs = [];
+ for (var i = 0; i < oldBin.length; i++) {
+ var strongRef = oldBin[i].weakref.get();
+ if (strongRef && strongRefs.indexOf(strongRef) == -1) {
+ strongRefs.push(strongRef);
+ newBin.push(oldBin[i]);
+ }
+ }
+ if (newBin.length)
+ newTrackedObjects[name] = newBin;
+ }
+ trackedObjects = newTrackedObjects;
+ }
+};
+
+var timer = Cc["@mozilla.org/timer;1"]
+ .createInstance(Ci.nsITimer);
+
+timer.initWithCallback(Compacter,
+ Compacter.INTERVAL,
+ Ci.nsITimer.TYPE_REPEATING_SLACK);
+
+var track = exports.track = function track(object, bin, stackFrameNumber) {
+ var frame = components.stack.caller;
+ var weakref = Cu.getWeakReference(object);
+ if (!bin && 'constructor' in object)
+ bin = object.constructor.name;
+ if (bin == "Object")
+ bin = frame.name;
+ if (!bin)
+ bin = "generic";
+ if (!(bin in trackedObjects))
+ trackedObjects[bin] = [];
+
+ if (stackFrameNumber > 0)
+ for (var i = 0; i < stackFrameNumber; i++)
+ frame = frame.caller;
+
+ trackedObjects[bin].push({weakref: weakref,
+ created: new Date(),
+ filename: frame.filename,
+ lineNo: frame.lineNumber,
+ bin: bin});
+};
+
+var getBins = exports.getBins = function getBins() {
+ var names = [];
+ for (let name in trackedObjects)
+ names.push(name);
+ return names;
+};
+
+var getObjects = exports.getObjects = function getObjects(bin) {
+ function getLiveObjectsInBin(bin, array) {
+ for (var i = 0; i < bin.length; i++) {
+ var object = bin[i].weakref.get();
+ if (object)
+ array.push(bin[i]);
+ }
+ }
+
+ var results = [];
+ if (bin) {
+ if (bin in trackedObjects)
+ getLiveObjectsInBin(trackedObjects[bin], results);
+ } else
+ for (let name in trackedObjects)
+ getLiveObjectsInBin(trackedObjects[name], results);
+ return results;
+};
+
+var gc = exports.gc = function gc() {
+ // Components.utils.forceGC() doesn't currently perform
+ // cycle collection, which means that e.g. DOM elements
+ // won't be collected by it. Fortunately, there are
+ // other ways...
+
+ var window = Cc["@mozilla.org/appshell/appShellService;1"]
+ .getService(Ci.nsIAppShellService)
+ .hiddenDOMWindow;
+ var test_utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ test_utils.garbageCollect();
+ Compacter.notify();
+
+ // Not sure why, but sometimes it appears that we don't get
+ // them all with just one CC, so let's do it again.
+ test_utils.garbageCollect();
+};
+
+require("../system/unload").when(
+ function() {
+ trackedObjects = {};
+ if (timer) {
+ timer.cancel();
+ timer = null;
+ }
+ });
diff --git a/tools/addon-sdk-1.12/lib/sdk/deprecated/observer-service.js b/tools/addon-sdk-1.12/lib/sdk/deprecated/observer-service.js
new file mode 100644
index 0000000..6a25109
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/deprecated/observer-service.js
@@ -0,0 +1,134 @@
+/* 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 { Cc, Ci } = require("chrome");
+const { when: unload } = require("../system/unload");
+const { ns } = require("../core/namespace");
+const { on, off, emit, once } = require("../system/events");
+const { id } = require("../self");
+
+const subscribers = ns();
+const cache = [];
+
+/**
+ * Topics specifically available to Jetpack-generated extensions.
+ *
+ * Using these predefined consts instead of the platform strings is good:
+ * - allows us to scope topics specifically for Jetpacks
+ * - addons aren't dependent on strings nor behavior of core platform topics
+ * - the core platform topics are not clearly named
+ *
+ */
+exports.topics = {
+ /**
+ * A topic indicating that the application is in a state usable
+ * by add-ons.
+ */
+ APPLICATION_READY: id + "_APPLICATION_READY"
+};
+
+function Listener(callback, target) {
+ return function listener({ subject, data }) {
+ callback.call(target || callback, subject, data);
+ }
+}
+
+/**
+ * Register the given callback as an observer of the given topic.
+ *
+ * @param topic {String}
+ * the topic to observe
+ *
+ * @param callback {Object}
+ * the callback; an Object that implements nsIObserver or a Function
+ * that gets called when the notification occurs
+ *
+ * @param target {Object} [optional]
+ * the object to use as |this| when calling a Function callback
+ *
+ * @returns the observer
+ */
+function add(topic, callback, target) {
+ let listeners = subscribers(callback);
+ if (!(topic in listeners)) {
+ let listener = Listener(callback, target);
+ listeners[topic] = listener;
+
+ // Cache callback unless it's already cached.
+ if (!~cache.indexOf(callback))
+ cache.push(callback);
+
+ on(topic, listener);
+ }
+};
+exports.add = add;
+
+/**
+ * Unregister the given callback as an observer of the given topic.
+ *
+ * @param topic {String}
+ * the topic being observed
+ *
+ * @param callback {Object}
+ * the callback doing the observing
+ *
+ * @param target {Object} [optional]
+ * the object being used as |this| when calling a Function callback
+ */
+function remove(topic, callback, target) {
+ let listeners = subscribers(callback);
+ if (topic in listeners) {
+ let listener = listeners[topic];
+ delete listeners[topic];
+
+ // If no more observers are registered and callback is still in cache
+ // then remove it.
+ let index = cache.indexOf(callback);
+ if (~index && !Object.keys(listeners).length)
+ cache.splice(index, 1)
+
+ off(topic, listener);
+ }
+};
+exports.remove = remove;
+
+/**
+ * Notify observers about something.
+ *
+ * @param topic {String}
+ * the topic to notify observers about
+ *
+ * @param subject {Object} [optional]
+ * some information about the topic; can be any JS object or primitive
+ *
+ * @param data {String} [optional] [deprecated]
+ * some more information about the topic; deprecated as the subject
+ * is sufficient to pass all needed information to the JS observers
+ * that this module targets; if you have multiple values to pass to
+ * the observer, wrap them in an object and pass them via the subject
+ * parameter (i.e.: { foo: 1, bar: "some string", baz: myObject })
+ */
+function notify(topic, subject, data) {
+ emit(topic, {
+ subject: subject === undefined ? null : subject,
+ data: data === undefined ? null : data
+ });
+}
+exports.notify = notify;
+
+unload(function() {
+ // Make a copy of cache first, since cache will be changing as we
+ // iterate through it.
+ cache.slice().forEach(function(callback) {
+ Object.keys(subscribers(callback)).forEach(function(topic) {
+ remove(topic, callback);
+ });
+ });
+})
diff --git a/tools/addon-sdk-1.12/lib/sdk/deprecated/tab-browser.js b/tools/addon-sdk-1.12/lib/sdk/deprecated/tab-browser.js
new file mode 100644
index 0000000..068471c
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/deprecated/tab-browser.js
@@ -0,0 +1,731 @@
+/* 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 {Cc,Ci,Cu} = require('chrome');
+var NetUtil = {};
+Cu.import('resource://gre/modules/NetUtil.jsm', NetUtil);
+NetUtil = NetUtil.NetUtil;
+const errors = require('./errors');
+const windowUtils = require('./window-utils');
+const apiUtils = require('./api-utils');
+const collection = require('../util/collection');
+const { getMostRecentBrowserWindow } = require('../window/utils');
+const { getSelectedTab } = require('../tabs/utils');
+
+// TODO: The hard-coding of app-specific info here isn't very nice;
+// ideally such app-specific info should be more decoupled, and the
+// module should be extensible, allowing for support of new apps at
+// runtime, perhaps by inspecting supported packages (e.g. via
+// dynamically-named modules or package-defined extension points).
+
+if (!require("../system/xul-app").is("Firefox")) {
+ throw new Error([
+ "The tab-browser module currently supports only Firefox. In the future ",
+ "it will support other applications. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=560716 for more information."
+ ].join(""));
+}
+
+function onBrowserLoad(callback, event) {
+ if (event.target && event.target.defaultView == this) {
+ this.removeEventListener("load", onBrowserLoad, true);
+ try {
+ require("../timers").setTimeout(function () {
+ callback(event);
+ }, 10);
+ } catch (e) { console.exception(e); }
+ }
+}
+
+// Utility function to open a new browser window.
+function openBrowserWindow(callback, url) {
+ let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+ getService(Ci.nsIWindowWatcher);
+ let urlString = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ urlString.data = url;
+ let window = ww.openWindow(null, "chrome://browser/content/browser.xul",
+ "_blank", "chrome,all,dialog=no", urlString);
+ if (callback)
+ window.addEventListener("load", onBrowserLoad.bind(window, callback), true);
+
+ return window;
+}
+
+// Open a URL in a new tab
+exports.addTab = function addTab(url, options) {
+ if (!options)
+ options = {};
+ options.url = url;
+
+ options = apiUtils.validateOptions(options, {
+ // TODO: take URL object instead of string (bug 564524)
+ url: {
+ is: ["string"],
+ ok: function (v) !!v,
+ msg: "The url parameter must have be a non-empty string."
+ },
+ inNewWindow: {
+ is: ["undefined", "null", "boolean"]
+ },
+ inBackground: {
+ is: ["undefined", "null", "boolean"]
+ },
+ onLoad: {
+ is: ["undefined", "null", "function"]
+ },
+ isPinned: {
+ is: ["undefined", "boolean"]
+ }
+ });
+
+ var win = getMostRecentBrowserWindow();
+ if (!win || options.inNewWindow) {
+ openBrowserWindow(function(e) {
+ if(options.isPinned) {
+ //get the active tab in the recently created window
+ let mainWindow = e.target.defaultView;
+ mainWindow.gBrowser.pinTab(getSelectedTab(mainWindow));
+ }
+ require("./errors").catchAndLog(function(e) options.onLoad(e))(e);
+ }, options.url);
+ }
+ else {
+ let tab = win.gBrowser.addTab(options.url);
+ if (!options.inBackground)
+ win.gBrowser.selectedTab = tab;
+ if (options.onLoad) {
+ let tabBrowser = win.gBrowser.getBrowserForTab(tab);
+ tabBrowser.addEventListener("load", function onLoad(e) {
+ if (e.target.defaultView.content.location == "about:blank")
+ return;
+ // remove event handler from addTab - don't want notified
+ // for subsequent loads in same tab.
+ tabBrowser.removeEventListener("load", onLoad, true);
+ require("./errors").catchAndLog(function(e) options.onLoad(e))(e);
+ }, true);
+ }
+ }
+}
+
+// Iterate over a window's tabbrowsers
+function tabBrowserIterator(window) {
+ var browsers = window.document.querySelectorAll("tabbrowser");
+ for (var i = 0; i < browsers.length; i++)
+ yield browsers[i];
+}
+
+// Iterate over a tabbrowser's tabs
+function tabIterator(tabbrowser) {
+ var tabs = tabbrowser.tabContainer;
+ for (var i = 0; i < tabs.children.length; i++) {
+ yield tabs.children[i];
+ }
+}
+
+// Tracker for all tabbrowsers across all windows,
+// or a single tabbrowser if the window is given.
+function Tracker(delegate, window) {
+ this._delegate = delegate;
+ this._browsers = [];
+ this._window = window;
+ this._windowTracker = windowUtils.WindowTracker(this);
+
+ require("../system/unload").ensure(this);
+}
+Tracker.prototype = {
+ __iterator__: function __iterator__() {
+ for (var i = 0; i < this._browsers.length; i++)
+ yield this._browsers[i];
+ },
+ get: function get(index) {
+ return this._browsers[index];
+ },
+ onTrack: function onTrack(window) {
+ if (this._window && window != this._window)
+ return;
+
+ for (let browser in tabBrowserIterator(window))
+ this._browsers.push(browser);
+ if (this._delegate)
+ for (let browser in tabBrowserIterator(window))
+ this._delegate.onTrack(browser);
+ },
+ onUntrack: function onUntrack(window) {
+ if (this._window && window != this._window)
+ return;
+
+ for (let browser in tabBrowserIterator(window)) {
+ let index = this._browsers.indexOf(browser);
+ if (index != -1)
+ this._browsers.splice(index, 1);
+ else
+ console.error("internal error: browser tab not found");
+ }
+ if (this._delegate)
+ for (let browser in tabBrowserIterator(window))
+ this._delegate.onUntrack(browser);
+ },
+ get length() {
+ return this._browsers.length;
+ },
+ unload: function unload() {
+ this._windowTracker.unload();
+ }
+};
+exports.Tracker = apiUtils.publicConstructor(Tracker);
+
+// Tracker for all tabs across all windows,
+// or a single window if it's given.
+function TabTracker(delegate, window) {
+ this._delegate = delegate;
+ this._tabs = [];
+ this._tracker = new Tracker(this, window);
+ require("../system/unload").ensure(this);
+}
+TabTracker.prototype = {
+ _TAB_EVENTS: ["TabOpen", "TabClose"],
+ _safeTrackTab: function safeTrackTab(tab) {
+ this._tabs.push(tab);
+ try {
+ this._delegate.onTrack(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ _safeUntrackTab: function safeUntrackTab(tab) {
+ var index = this._tabs.indexOf(tab);
+ if (index == -1)
+ console.error("internal error: tab not found");
+ this._tabs.splice(index, 1);
+ try {
+ this._delegate.onUntrack(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ handleEvent: function handleEvent(event) {
+ switch (event.type) {
+ case "TabOpen":
+ this._safeTrackTab(event.target);
+ break;
+ case "TabClose":
+ this._safeUntrackTab(event.target);
+ break;
+ default:
+ throw new Error("internal error: unknown event type: " +
+ event.type);
+ }
+ },
+ onTrack: function onTrack(tabbrowser) {
+ for (let tab in tabIterator(tabbrowser))
+ this._safeTrackTab(tab);
+ var self = this;
+ this._TAB_EVENTS.forEach(
+ function(eventName) {
+ tabbrowser.tabContainer.addEventListener(eventName, self, true);
+ });
+ },
+ onUntrack: function onUntrack(tabbrowser) {
+ for (let tab in tabIterator(tabbrowser))
+ this._safeUntrackTab(tab);
+ var self = this;
+ this._TAB_EVENTS.forEach(
+ function(eventName) {
+ tabbrowser.tabContainer.removeEventListener(eventName, self, true);
+ });
+ },
+ unload: function unload() {
+ this._tracker.unload();
+ }
+};
+exports.TabTracker = apiUtils.publicConstructor(TabTracker);
+
+exports.whenContentLoaded = function whenContentLoaded(callback) {
+ var cb = require("./errors").catchAndLog(function eventHandler(event) {
+ if (event.target && event.target.defaultView)
+ callback(event.target.defaultView);
+ });
+
+ var tracker = new Tracker({
+ onTrack: function(tabBrowser) {
+ tabBrowser.addEventListener("DOMContentLoaded", cb, false);
+ },
+ onUntrack: function(tabBrowser) {
+ tabBrowser.removeEventListener("DOMContentLoaded", cb, false);
+ }
+ });
+
+ return tracker;
+};
+
+Object.defineProperty(exports, 'activeTab', {
+ get: function() {
+ return getSelectedTab(getMostRecentBrowserWindow());
+ }
+});
+
+/******************* TabModule *********************/
+
+// Supported tab events
+const events = [
+ "onActivate",
+ "onDeactivate",
+ "onOpen",
+ "onClose",
+ "onReady",
+ "onLoad",
+ "onPaint"
+];
+exports.tabEvents = events;
+
+/**
+ * TabModule
+ *
+ * Constructor for a module that implements the tabs API
+ */
+let TabModule = exports.TabModule = function TabModule(window) {
+ let self = this;
+ /**
+ * Tab
+ *
+ * Safe object representing a tab.
+ */
+ let tabConstructor = apiUtils.publicConstructor(function(element) {
+ if (!element)
+ throw new Error("no tab element.");
+ let win = element.ownerDocument.defaultView;
+ if (!win)
+ throw new Error("element has no window.");
+ if (window && win != window)
+ throw new Error("module's window and element's window don't match.");
+ let browser = win.gBrowser.getBrowserForTab(element);
+
+ this.__defineGetter__("title", function() browser.contentDocument.title);
+ this.__defineGetter__("location", function() browser.contentDocument.location);
+ this.__defineSetter__("location", function(val) browser.contentDocument.location = val);
+ this.__defineGetter__("contentWindow", function() browser.contentWindow);
+ this.__defineGetter__("contentDocument", function() browser.contentDocument);
+ this.__defineGetter__("favicon", function() {
+ let pageURI = NetUtil.newURI(browser.contentDocument.location);
+ let fs = Cc["@mozilla.org/browser/favicon-service;1"].
+ getService(Ci.nsIFaviconService);
+ let faviconURL;
+ try {
+ let faviconURI = fs.getFaviconForPage(pageURI);
+ faviconURL = fs.getFaviconDataAsDataURL(faviconURI);
+ } catch(ex) {
+ let data = getChromeURLContents("chrome://mozapps/skin/places/defaultFavicon.png");
+ let encoded = browser.contentWindow.btoa(data);
+ faviconURL = "data:image/png;base64," + encoded;
+ }
+ return faviconURL;
+ });
+ this.__defineGetter__("style", function() null); // TODO
+ this.__defineGetter__("index", function() win.gBrowser.getBrowserIndexForDocument(browser.contentDocument));
+ this.__defineGetter__("thumbnail", function() getThumbnailCanvasForTab(element, browser.contentWindow));
+
+ this.close = function() win.gBrowser.removeTab(element);
+ this.move = function(index) {
+ win.gBrowser.moveTabTo(element, index);
+ };
+
+ this.__defineGetter__("isPinned", function() element.pinned);
+ this.pin = function() win.gBrowser.pinTab(element);
+ this.unpin = function() win.gBrowser.unpinTab(element);
+
+ // Set up the event handlers
+ let tab = this;
+ events.filter(function(e) e != "onOpen").forEach(function(e) {
+ // create a collection for each event
+ collection.addCollectionProperty(tab, e);
+ // make tabs setter for each event, for adding via property assignment
+ tab.__defineSetter__(e, function(val) tab[e].add(val));
+ });
+
+ // listen for events, filtered on this tab
+ eventsTabDelegate.addTabDelegate(this);
+ });
+
+ /**
+ * tabs.activeTab
+ */
+ this.__defineGetter__("activeTab", function() {
+ try {
+ return window ? tabConstructor(getSelectedTab(window))
+ : tabConstructor(exports.activeTab);
+ }
+ catch (e) { }
+ return null;
+ });
+ this.__defineSetter__("activeTab", function(tab) {
+ let [tabElement, win] = getElementAndWindowForTab(tab, window);
+ if (tabElement) {
+ // set as active tab
+ win.gBrowser.selectedTab = tabElement;
+ // focus the window
+ win.focus();
+ }
+ });
+
+ this.open = function TM_open(options) {
+ open(options, tabConstructor, window);
+ }
+
+ // Set up the event handlers
+ events.forEach(function(eventHandler) {
+ // create a collection for each event
+ collection.addCollectionProperty(self, eventHandler);
+ // make tabs setter for each event, for adding via property assignment
+ self.__defineSetter__(eventHandler, function(val) self[eventHandler].add(val));
+ });
+
+ // Tracker that listens for tab events, and proxies
+ // them to registered event listeners.
+ let eventsTabDelegate = {
+ selectedTab: null,
+ tabs: [],
+ addTabDelegate: function TETT_addTabDelegate(tabObj) {
+ this.tabs.push(tabObj);
+ },
+ pushTabEvent: function TETT_pushTabEvent(event, tab) {
+ for (let callback in self[event]) {
+ require("./errors").catchAndLog(function(tab) {
+ callback(new tabConstructor(tab));
+ })(tab);
+ }
+
+ if (event != "onOpen") {
+ this.tabs.forEach(function(tabObj) {
+ if (tabObj[event].length) {
+ let [tabEl,] = getElementAndWindowForTab(tabObj, window);
+ if (tabEl == tab) {
+ for (let callback in tabObj[event])
+ require("./errors").catchAndLog(function() callback())();
+ }
+ }
+ // if being closed, remove the tab object from the cache
+ // of tabs to notify about events.
+ if (event == "onClose")
+ this.tabs.splice(this.tabs.indexOf(tabObj), 1);
+ }, this);
+ }
+ },
+ unload: function() {
+ this.selectedTab = null;
+ this.tabs.splice(0);
+ }
+ };
+ require("../system/unload").ensure(eventsTabDelegate);
+
+ let eventsTabTracker = new ModuleTabTracker({
+ onTrack: function TETT_onTrack(tab) {
+ eventsTabDelegate.pushTabEvent("onOpen", tab);
+ },
+ onUntrack: function TETT_onUntrack(tab) {
+ eventsTabDelegate.pushTabEvent("onClose", tab);
+ },
+ onSelect: function TETT_onSelect(tab) {
+ if (eventsTabDelegate.selectedTab)
+ eventsTabDelegate.pushTabEvent("onDeactivate", tab);
+
+ eventsTabDelegate.selectedTab = new tabConstructor(tab);
+
+ eventsTabDelegate.pushTabEvent("onActivate", tab);
+ },
+ onReady: function TETT_onReady(tab) {
+ eventsTabDelegate.pushTabEvent("onReady", tab);
+ },
+ onLoad: function TETT_onLoad(tab) {
+ eventsTabDelegate.pushTabEvent("onLoad", tab);
+ },
+ onPaint: function TETT_onPaint(tab) {
+ eventsTabDelegate.pushTabEvent("onPaint", tab);
+ }
+ }, window);
+ require("../system/unload").ensure(eventsTabTracker);
+
+ // Iterator for all tabs
+ this.__iterator__ = function tabsIterator() {
+ for (let i = 0; i < eventsTabTracker._tabs.length; i++)
+ yield tabConstructor(eventsTabTracker._tabs[i]);
+ }
+
+ this.__defineGetter__("length", function() eventsTabTracker._tabs.length);
+
+ // Cleanup when unloaded
+ this.unload = function TM_unload() {
+ // Unregister tabs event listeners
+ events.forEach(function(e) self[e] = []);
+ }
+ require("../system/unload").ensure(this);
+
+} // End of TabModule constructor
+
+/**
+ * tabs.open - open a URL in a new tab
+ */
+function open(options, tabConstructor, window) {
+ if (typeof options === "string")
+ options = { url: options };
+
+ options = apiUtils.validateOptions(options, {
+ url: {
+ is: ["string"]
+ },
+ inNewWindow: {
+ is: ["undefined", "boolean"]
+ },
+ inBackground: {
+ is: ["undefined", "boolean"]
+ },
+ isPinned: {
+ is: ["undefined", "boolean"]
+ },
+ onOpen: {
+ is: ["undefined", "function"]
+ }
+ });
+
+ if (window)
+ options.inNewWindow = false;
+
+ let win = window || require("./window-utils").activeBrowserWindow;
+
+ if (!win || options.inNewWindow)
+ openURLInNewWindow(options, tabConstructor);
+ else
+ openURLInNewTab(options, win, tabConstructor);
+}
+
+function openURLInNewWindow(options, tabConstructor) {
+ let addTabOptions = {
+ inNewWindow: true
+ };
+ if (options.onOpen) {
+ addTabOptions.onLoad = function(e) {
+ let win = e.target.defaultView;
+ let tabEl = win.gBrowser.tabContainer.childNodes[0];
+ let tabBrowser = win.gBrowser.getBrowserForTab(tabEl);
+ tabBrowser.addEventListener("load", function onLoad(e) {
+ tabBrowser.removeEventListener("load", onLoad, true);
+ let tab = tabConstructor(tabEl);
+ require("./errors").catchAndLog(function(e) options.onOpen(e))(tab);
+ }, true);
+ };
+ }
+ if (options.isPinned) {
+ addTabOptions.isPinned = true;
+ }
+ exports.addTab(options.url.toString(), addTabOptions);
+}
+
+function openURLInNewTab(options, window, tabConstructor) {
+ window.focus();
+ let tabEl = window.gBrowser.addTab(options.url.toString());
+ if (!options.inBackground)
+ window.gBrowser.selectedTab = tabEl;
+ if (options.isPinned)
+ window.gBrowser.pinTab(tabEl);
+ if (options.onOpen) {
+ let tabBrowser = window.gBrowser.getBrowserForTab(tabEl);
+ tabBrowser.addEventListener("load", function onLoad(e) {
+ // remove event handler from addTab - don't want to be notified
+ // for subsequent loads in same tab.
+ tabBrowser.removeEventListener("load", onLoad, true);
+ let tab = tabConstructor(tabEl);
+ require("../timers").setTimeout(function() {
+ require("./errors").catchAndLog(function(tab) options.onOpen(tab))(tab);
+ }, 10);
+ }, true);
+ }
+}
+
+function getElementAndWindowForTab(tabObj, window) {
+ // iterate over open windows, or use single window if provided
+ let windowIterator = window ? function() { yield window; }
+ : require("./window-utils").windowIterator;
+ for (let win in windowIterator()) {
+ if (win.gBrowser) {
+ // find the tab element at tab.index
+ let index = win.gBrowser.getBrowserIndexForDocument(tabObj.contentDocument);
+ if (index > -1)
+ return [win.gBrowser.tabContainer.getItemAtIndex(index), win];
+ }
+ }
+ return [null, null];
+}
+
+// Tracker for all tabs across all windows
+// This is tab-browser.TabTracker, but with
+// support for additional events added.
+function ModuleTabTracker(delegate, window) {
+ this._delegate = delegate;
+ this._tabs = [];
+ this._tracker = new Tracker(this, window);
+ require("../system/unload").ensure(this);
+}
+ModuleTabTracker.prototype = {
+ _TAB_EVENTS: ["TabOpen", "TabClose", "TabSelect", "DOMContentLoaded",
+ "load", "MozAfterPaint"],
+ _safeTrackTab: function safeTrackTab(tab) {
+ tab.addEventListener("load", this, false);
+ tab.linkedBrowser.addEventListener("MozAfterPaint", this, false);
+ this._tabs.push(tab);
+ try {
+ this._delegate.onTrack(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ _safeUntrackTab: function safeUntrackTab(tab) {
+ tab.removeEventListener("load", this, false);
+ tab.linkedBrowser.removeEventListener("MozAfterPaint", this, false);
+ var index = this._tabs.indexOf(tab);
+ if (index == -1)
+ throw new Error("internal error: tab not found");
+ this._tabs.splice(index, 1);
+ try {
+ this._delegate.onUntrack(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ _safeSelectTab: function safeSelectTab(tab) {
+ var index = this._tabs.indexOf(tab);
+ if (index == -1)
+ console.error("internal error: tab not found");
+ try {
+ if (this._delegate.onSelect)
+ this._delegate.onSelect(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ _safeDOMContentLoaded: function safeDOMContentLoaded(event) {
+ let tabBrowser = event.currentTarget;
+ let tabBrowserIndex = tabBrowser.getBrowserIndexForDocument(event.target);
+ // TODO: I'm seeing this when loading data url images
+ if (tabBrowserIndex == -1)
+ return;
+ let tab = tabBrowser.tabContainer.getItemAtIndex(tabBrowserIndex);
+ let index = this._tabs.indexOf(tab);
+ if (index == -1)
+ console.error("internal error: tab not found");
+ try {
+ if (this._delegate.onReady)
+ this._delegate.onReady(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ _safeLoad: function safeLoad(event) {
+ let tab = event.target;
+ let index = this._tabs.indexOf(tab);
+ if (index == -1)
+ console.error("internal error: tab not found");
+ try {
+ if (this._delegate.onLoad)
+ this._delegate.onLoad(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ _safeMozAfterPaint: function safeMozAfterPaint(event) {
+ let win = event.currentTarget.ownerDocument.defaultView;
+ let tabIndex = win.gBrowser.getBrowserIndexForDocument(event.target.document);
+ if (tabIndex == -1)
+ return;
+ let tab = win.gBrowser.tabContainer.getItemAtIndex(tabIndex);
+ let index = this._tabs.indexOf(tab);
+ if (index == -1)
+ console.error("internal error: tab not found");
+ try {
+ if (this._delegate.onPaint)
+ this._delegate.onPaint(tab);
+ } catch (e) {
+ console.exception(e);
+ }
+ },
+ handleEvent: function handleEvent(event) {
+ switch (event.type) {
+ case "TabOpen":
+ this._safeTrackTab(event.target);
+ break;
+ case "TabClose":
+ this._safeUntrackTab(event.target);
+ break;
+ case "TabSelect":
+ this._safeSelectTab(event.target);
+ break;
+ case "DOMContentLoaded":
+ this._safeDOMContentLoaded(event);
+ break;
+ case "load":
+ this._safeLoad(event);
+ break;
+ case "MozAfterPaint":
+ this._safeMozAfterPaint(event);
+ break;
+ default:
+ throw new Error("internal error: unknown event type: " +
+ event.type);
+ }
+ },
+ onTrack: function onTrack(tabbrowser) {
+ for (let tab in tabIterator(tabbrowser))
+ this._safeTrackTab(tab);
+ tabbrowser.tabContainer.addEventListener("TabOpen", this, false);
+ tabbrowser.tabContainer.addEventListener("TabClose", this, false);
+ tabbrowser.tabContainer.addEventListener("TabSelect", this, false);
+ tabbrowser.ownerDocument.defaultView.gBrowser.addEventListener("DOMContentLoaded", this, false);
+ },
+ onUntrack: function onUntrack(tabbrowser) {
+ for (let tab in tabIterator(tabbrowser))
+ this._safeUntrackTab(tab);
+ tabbrowser.tabContainer.removeEventListener("TabOpen", this, false);
+ tabbrowser.tabContainer.removeEventListener("TabClose", this, false);
+ tabbrowser.tabContainer.removeEventListener("TabSelect", this, false);
+ tabbrowser.ownerDocument.defaultView.gBrowser.removeEventListener("DOMContentLoaded", this, false);
+ },
+ unload: function unload() {
+ this._tracker.unload();
+ }
+};
+
+// Utility to get a thumbnail canvas from a tab object
+function getThumbnailCanvasForTab(tabEl, window) {
+ var thumbnail = window.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ thumbnail.mozOpaque = true;
+ window = tabEl.linkedBrowser.contentWindow;
+ thumbnail.width = Math.ceil(window.screen.availWidth / 5.75);
+ var aspectRatio = 0.5625; // 16:9
+ thumbnail.height = Math.round(thumbnail.width * aspectRatio);
+ var ctx = thumbnail.getContext("2d");
+ var snippetWidth = window.innerWidth * .6;
+ var scale = thumbnail.width / snippetWidth;
+ ctx.scale(scale, scale);
+ ctx.drawWindow(window, window.scrollX, window.scrollY, snippetWidth, snippetWidth * aspectRatio, "rgb(255,255,255)");
+ return thumbnail;
+}
+
+// Utility to return the contents of the target of a chrome URL
+function getChromeURLContents(chromeURL) {
+ let io = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ let channel = io.newChannel(chromeURL, null, null);
+ let input = channel.open();
+ let stream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ stream.setInputStream(input);
+ let str = stream.readBytes(input.available());
+ stream.close();
+ input.close();
+ return str;
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/deprecated/traits.js b/tools/addon-sdk-1.12/lib/sdk/deprecated/traits.js
new file mode 100644
index 0000000..30ae342
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/deprecated/traits.js
@@ -0,0 +1,187 @@
+/* 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 {
+ compose: _compose,
+ override: _override,
+ resolve: _resolve,
+ trait: _trait,
+ //create: _create,
+ required,
+} = require('./traits/core');
+
+const defineProperties = Object.defineProperties,
+ freeze = Object.freeze,
+ create = Object.create;
+
+/**
+ * Work around bug 608959 by defining the _create function here instead of
+ * importing it from traits/core. For docs on this function, see the create
+ * function in that module.
+ *
+ * FIXME: remove this workaround in favor of importing the function once that
+ * bug has been fixed.
+ */
+function _create(proto, trait) {
+ let properties = {},
+ keys = Object.getOwnPropertyNames(trait);
+ for each(let key in keys) {
+ let descriptor = trait[key];
+ if (descriptor.required &&
+ !Object.prototype.hasOwnProperty.call(proto, key))
+ throw new Error('Missing required property: ' + key);
+ else if (descriptor.conflict)
+ throw new Error('Remaining conflicting property: ' + key);
+ else
+ properties[key] = descriptor;
+ }
+ return Object.create(proto, properties);
+}
+
+/**
+ * Placeholder for `Trait.prototype`
+ */
+let TraitProto = Object.prototype;
+
+function Get(key) this[key]
+function Set(key, value) this[key] = value
+
+/**
+ * Creates anonymous trait descriptor from the passed argument, unless argument
+ * is a trait constructor. In later case trait's already existing properties
+ * descriptor is returned.
+ * This is module's internal function and is used as a gateway to a trait's
+ * internal properties descriptor.
+ * @param {Function} $
+ * Composed trait's constructor.
+ * @returns {Object}
+ * Private trait property of the composition.
+ */
+function TraitDescriptor(object)
+ (
+ 'function' == typeof object &&
+ (object.prototype == TraitProto || object.prototype instanceof Trait)
+ ) ? object._trait(TraitDescriptor) : _trait(object)
+
+function Public(instance, trait) {
+ let result = {},
+ keys = Object.getOwnPropertyNames(trait);
+ for each (let key in keys) {
+ if ('_' === key.charAt(0) && '__iterator__' !== key )
+ continue;
+ let property = trait[key],
+ descriptor = {
+ configurable: property.configurable,
+ enumerable: property.enumerable
+ };
+ if (property.get)
+ descriptor.get = property.get.bind(instance);
+ if (property.set)
+ descriptor.set = property.set.bind(instance);
+ if ('value' in property) {
+ let value = property.value;
+ if ('function' === typeof value) {
+ descriptor.value = property.value.bind(instance);
+ descriptor.writable = property.writable;
+ } else {
+ descriptor.get = Get.bind(instance, key);
+ descriptor.set = Set.bind(instance, key);
+ }
+ }
+ result[key] = descriptor;
+ }
+ return result;
+}
+
+/**
+ * This is private function that composes new trait with privates.
+ */
+function Composition(trait) {
+ function Trait() {
+ let self = _create({}, trait);
+ self._public = create(Trait.prototype, Public(self, trait));
+ delete self._public.constructor;
+ if (Object === self.constructor)
+ self.constructor = Trait;
+ else
+ return self.constructor.apply(self, arguments) || self._public;
+ return self._public;
+ }
+ defineProperties(Trait, {
+ prototype: { value: freeze(create(TraitProto, {
+ constructor: { value: constructor, writable: true }
+ }))}, // writable is `true` to avoid getters in custom ES5
+ displayName: { value: (trait.constructor || constructor).name },
+ compose: { value: compose, enumerable: true },
+ override: { value: override, enumerable: true },
+ resolve: { value: resolve, enumerable: true },
+ required: { value: required, enumerable: true },
+ _trait: { value: function _trait(caller)
+ caller === TraitDescriptor ? trait : undefined
+ }
+ });
+ return freeze(Trait);
+}
+
+/**
+ * Composes new trait out of itself and traits / property maps passed as an
+ * arguments. If two or more traits / property maps have properties with the
+ * same name, the new trait will contain a "conflict" property for that name.
+ * This is a commutative and associative operation, and the order of its
+ * arguments is not significant.
+ * @params {Object|Function}
+ * List of Traits or property maps to create traits from.
+ * @returns {Function}
+ * New trait containing the combined properties of all the traits.
+ */
+function compose() {
+ let traits = Array.slice(arguments, 0);
+ traits.push(this);
+ return Composition(_compose.apply(null, traits.map(TraitDescriptor)));
+}
+
+/**
+ * Composes a new trait with all of the combined properties of `this` and the
+ * argument traits. In contrast to `compose`, `override` immediately resolves
+ * all conflicts resulting from this composition by overriding the properties of
+ * later traits. Trait priority is from left to right. I.e. the properties of
+ * the leftmost trait are never overridden.
+ * @params {Object} trait
+ * @returns {Object}
+ */
+function override() {
+ let traits = Array.slice(arguments, 0);
+ traits.push(this);
+ return Composition(_override.apply(null, traits.map(TraitDescriptor)));
+}
+
+/**
+ * Composes new resolved trait, with all the same properties as this
+ * trait, except that all properties whose name is an own property of
+ * `resolutions` will be renamed to `resolutions[name]`. If it is
+ * `resolutions[name]` is `null` value is changed into a required property
+ * descriptor.
+ */
+function resolve(resolutions)
+ Composition(_resolve(resolutions, TraitDescriptor(this)))
+
+/**
+ * Base Trait, that all the traits are composed of.
+ */
+const Trait = Composition({
+ /**
+ * Internal property holding public API of this instance.
+ */
+ _public: { value: null, configurable: true, writable: true },
+ toString: { value: function() '[object ' + this.constructor.name + ']' }
+});
+TraitProto = Trait.prototype;
+exports.Trait = Trait;
+
diff --git a/tools/addon-sdk-1.12/lib/sdk/deprecated/traits/core.js b/tools/addon-sdk-1.12/lib/sdk/deprecated/traits/core.js
new file mode 100644
index 0000000..c08c38f
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/deprecated/traits/core.js
@@ -0,0 +1,322 @@
+/* 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"
+};
+
+// Design inspired by: http://www.traitsjs.org/
+
+// shortcuts
+const getOwnPropertyNames = Object.getOwnPropertyNames,
+ getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor,
+ hasOwn = Object.prototype.hasOwnProperty,
+ _create = Object.create;
+
+function doPropertiesMatch(object1, object2, name) {
+ // If `object1` has property with the given `name`
+ return name in object1 ?
+ // then `object2` should have it with the same value.
+ name in object2 && object1[name] === object2[name] :
+ // otherwise `object2` should not have property with the given `name`.
+ !(name in object2);
+}
+
+/**
+ * Compares two trait custom property descriptors if they are the same. If
+ * both are `conflict` or all the properties of descriptor are equal returned
+ * value will be `true`, otherwise it will be `false`.
+ * @param {Object} desc1
+ * @param {Object} desc2
+ */
+function areSame(desc1, desc2) {
+ return ('conflict' in desc1 && desc1.conflict &&
+ 'conflict' in desc2 && desc2.conflict) ||
+ (doPropertiesMatch(desc1, desc2, 'get') &&
+ doPropertiesMatch(desc1, desc2, 'set') &&
+ doPropertiesMatch(desc1, desc2, 'value') &&
+ doPropertiesMatch(desc1, desc2, 'enumerable') &&
+ doPropertiesMatch(desc1, desc2, 'required') &&
+ doPropertiesMatch(desc1, desc2, 'conflict'));
+}
+
+/**
+ * Converts array to an object whose own property names represent
+ * values of array.
+ * @param {String[]} names
+ * @returns {Object}
+ * @example
+ * Map(['foo', ...]) => { foo: true, ...}
+ */
+function Map(names) {
+ let map = {};
+ for each (let name in names)
+ map[name] = true;
+ return map;
+}
+
+
+const ERR_CONFLICT = 'Remaining conflicting property: ',
+ ERR_REQUIRED = 'Missing required property: ';
+/**
+ * Constant singleton, representing placeholder for required properties.
+ * @type {Object}
+ */
+const required = { toString: function()'<Trait.required>' };
+exports.required = required;
+
+/**
+ * Generates custom **required** property descriptor. Descriptor contains
+ * non-standard property `required` that is equal to `true`.
+ * @param {String} name
+ * property name to generate descriptor for.
+ * @returns {Object}
+ * custom property descriptor
+ */
+function Required(name) {
+ function required() { throw new Error(ERR_REQUIRED + name) }
+ return {
+ get: required,
+ set: required,
+ required: true
+ };
+}
+
+/**
+ * Generates custom **conflicting** property descriptor. Descriptor contains
+ * non-standard property `conflict` that is equal to `true`.
+ * @param {String} name
+ * property name to generate descriptor for.
+ * @returns {Object}
+ * custom property descriptor
+ */
+function Conflict(name) {
+ function conflict() { throw new Error(ERR_CONFLICT + name) }
+ return {
+ get: conflict,
+ set: conflict,
+ conflict: true
+ };
+}
+
+/**
+ * Function generates custom properties descriptor of the `object`s own
+ * properties. All the inherited properties are going to be ignored.
+ * Properties with values matching `required` singleton will be marked as
+ * 'required' properties.
+ * @param {Object} object
+ * Set of properties to generate trait from.
+ * @returns {Object}
+ * Properties descriptor of all of the `object`'s own properties.
+ */
+function trait(properties) {
+ let result = {},
+ keys = getOwnPropertyNames(properties);
+ for each (let key in keys) {
+ let descriptor = getOwnPropertyDescriptor(properties, key);
+ result[key] = (required === descriptor.value) ? Required(key) : descriptor;
+ }
+ return result;
+}
+exports.Trait = exports.trait = trait;
+
+/**
+ * Composes new trait. If two or more traits have own properties with the
+ * same name, the new trait will contain a 'conflict' property for that name.
+ * 'compose' is a commutative and associative operation, and the order of its
+ * arguments is not significant.
+ *
+ * @params {Object} trait
+ * Takes traits as an arguments
+ * @returns {Object}
+ * New trait containing the combined own properties of all the traits.
+ * @example
+ * var newTrait = compose(trait_1, trait_2, ..., trait_N);
+ */
+function compose(trait1, trait2) {
+ let traits = Array.slice(arguments, 0),
+ result = {};
+ for each (let trait in traits) {
+ let keys = getOwnPropertyNames(trait);
+ for each (let key in keys) {
+ let descriptor = trait[key];
+ // if property already exists and it's not a requirement
+ if (hasOwn.call(result, key) && !result[key].required) {
+ if (descriptor.required)
+ continue;
+ if (!areSame(descriptor, result[key]))
+ result[key] = Conflict(key);
+ } else {
+ result[key] = descriptor;
+ }
+ }
+ }
+ return result;
+}
+exports.compose = compose;
+
+/**
+ * Composes new trait with the same own properties as the original trait,
+ * except that all property names appearing in the first argument are replaced
+ * by 'required' property descriptors.
+ * @param {String[]} keys
+ * Array of strings property names.
+ * @param {Object} trait
+ * A trait some properties of which should be excluded.
+ * @returns {Object}
+ * @example
+ * var newTrait = exclude(['name', ...], trait)
+ */
+function exclude(keys, trait) {
+ let exclusions = Map(keys),
+ result = {};
+
+ keys = getOwnPropertyNames(trait);
+
+ for each (let key in keys) {
+ if (!hasOwn.call(exclusions, key) || trait[key].required)
+ result[key] = trait[key];
+ else
+ result[key] = Required(key);
+ }
+ return result;
+}
+
+/**
+ * Composes a new trait with all of the combined properties of the argument
+ * traits. In contrast to `compose`, `override` immediately resolves all
+ * conflicts resulting from this composition by overriding the properties of
+ * later traits. Trait priority is from left to right. I.e. the properties of
+ * the leftmost trait are never overridden.
+ * @params {Object} trait
+ * @returns {Object}
+ * @examples
+ * // override is associative:
+ * override(t1,t2,t3)
+ * // is equivalent to
+ * override(t1, override(t2, t3))
+ * // or
+ * to override(override(t1, t2), t3)
+ *
+ * // override is not commutative:
+ * override(t1,t2)
+ * // is not equivalent to
+ * override(t2,t1)
+ */
+function override() {
+ let traits = Array.slice(arguments, 0),
+ result = {};
+ for each (let trait in traits) {
+ let keys = getOwnPropertyNames(trait);
+ for each(let key in keys) {
+ let descriptor = trait[key];
+ if (!hasOwn.call(result, key) || result[key].required)
+ result[key] = descriptor;
+ }
+ }
+ return result;
+}
+exports.override = override;
+
+/**
+ * Composes a new trait with the same properties as the original trait, except
+ * that all properties whose name is an own property of map will be renamed to
+ * map[name], and a 'required' property for name will be added instead.
+ * @param {Object} map
+ * An object whose own properties serve as a mapping from old names to new
+ * names.
+ * @param {Object} trait
+ * A trait object
+ * @returns {Object}
+ * @example
+ * var newTrait = rename(map, trait);
+ */
+function rename(map, trait) {
+ let result = {},
+ keys = getOwnPropertyNames(trait);
+ for each(let key in keys) {
+ // must be renamed & it's not requirement
+ if (hasOwn.call(map, key) && !trait[key].required) {
+ let alias = map[key];
+ if (hasOwn.call(result, alias) && !result[alias].required)
+ result[alias] = Conflict(alias);
+ else
+ result[alias] = trait[key];
+ if (!hasOwn.call(result, key))
+ result[key] = Required(key);
+ } else { // must not be renamed or its a requirement
+ // property is not in result trait yet
+ if (!hasOwn.call(result, key))
+ result[key] = trait[key];
+ // property is already in resulted trait & it's not requirement
+ else if (!trait[key].required)
+ result[key] = Conflict(key);
+ }
+ }
+ return result;
+}
+
+/**
+* Composes new resolved trait, with all the same properties as the original
+* trait, except that all properties whose name is an own property of
+* resolutions will be renamed to `resolutions[name]`. If it is
+* `resolutions[name]` is `null` value is changed into a required property
+* descriptor.
+* function can be implemented as `rename(map,exclude(exclusions, trait))`
+* where map is the subset of mappings from oldName to newName and exclusions
+* is an array of all the keys that map to `null`.
+* Note: it's important to **first** `exclude`, **then** `rename`, since
+* `exclude` and rename are not associative.
+* @param {Object} resolutions
+* An object whose own properties serve as a mapping from old names to new
+* names, or to `null` if the property should be excluded.
+* @param {Object} trait
+* A trait object
+* @returns {Object}
+* Resolved trait with the same own properties as the original trait.
+*/
+function resolve(resolutions, trait) {
+ let renames = {},
+ exclusions = [],
+ keys = getOwnPropertyNames(resolutions);
+ for each (let key in keys) { // pre-process renamed and excluded properties
+ if (resolutions[key]) // old name -> new name
+ renames[key] = resolutions[key];
+ else // name -> undefined
+ exclusions.push(key);
+ }
+ return rename(renames, exclude(exclusions, trait));
+}
+exports.resolve = resolve;
+
+/**
+ * `create` is like `Object.create`, except that it ensures that:
+ * - an exception is thrown if 'trait' still contains required properties
+ * - an exception is thrown if 'trait' still contains conflicting
+ * properties
+ * @param {Object}
+ * prototype of the completed object
+ * @param {Object} trait
+ * trait object to be turned into a complete object
+ * @returns {Object}
+ * An object with all of the properties described by the trait.
+ */
+function create(proto, trait) {
+ let properties = {},
+ keys = getOwnPropertyNames(trait);
+ for each(let key in keys) {
+ let descriptor = trait[key];
+ if (descriptor.required && !hasOwn.call(proto, key))
+ throw new Error(ERR_REQUIRED + key);
+ else if (descriptor.conflict)
+ throw new Error(ERR_CONFLICT + key);
+ else
+ properties[key] = descriptor;
+ }
+ return _create(proto, properties);
+}
+exports.create = create;
+
diff --git a/tools/addon-sdk-1.12/lib/sdk/deprecated/unit-test-finder.js b/tools/addon-sdk-1.12/lib/sdk/deprecated/unit-test-finder.js
new file mode 100644
index 0000000..880f554
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/deprecated/unit-test-finder.js
@@ -0,0 +1,71 @@
+/* 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 file = require("../io/file");
+const memory = require('./memory');
+const suites = require('@test/options').allTestModules;
+
+
+const NOT_TESTS = ['setup', 'teardown'];
+
+var TestFinder = exports.TestFinder = function TestFinder(options) {
+ memory.track(this);
+ this.filter = options.filter;
+ this.testInProcess = options.testInProcess === false ? false : true;
+ this.testOutOfProcess = options.testOutOfProcess === true ? true : false;
+};
+
+TestFinder.prototype = {
+ findTests: function findTests(cb) {
+ var self = this;
+ var tests = [];
+ var filter;
+ // A filter string is {fileNameRegex}[:{testNameRegex}] - ie, a colon
+ // optionally separates a regex for the test fileName from a regex for the
+ // testName.
+ if (this.filter) {
+ var colonPos = this.filter.indexOf(':');
+ var filterFileRegex, filterNameRegex;
+ if (colonPos === -1) {
+ filterFileRegex = new RegExp(self.filter);
+ } else {
+ filterFileRegex = new RegExp(self.filter.substr(0, colonPos));
+ filterNameRegex = new RegExp(self.filter.substr(colonPos + 1));
+ }
+ // This function will first be called with just the filename; if
+ // it returns true the module will be loaded then the function
+ // called again with both the filename and the testname.
+ filter = function(filename, testname) {
+ return filterFileRegex.test(filename) &&
+ ((testname && filterNameRegex) ? filterNameRegex.test(testname)
+ : true);
+ };
+ } else
+ filter = function() {return true};
+
+ suites.forEach(
+ function(suite) {
+ var module = require(suite);
+ if (self.testInProcess)
+ for each (let name in Object.keys(module).sort()) {
+ if(NOT_TESTS.indexOf(name) === -1 && filter(suite, name)) {
+ tests.push({
+ setup: module.setup,
+ teardown: module.teardown,
+ testFunction: module[name],
+ name: suite + "." + name
+ });
+ }
+ }
+ });
+
+ cb(tests);
+ }
+};
diff --git a/tools/addon-sdk-1.12/lib/sdk/deprecated/unit-test.js b/tools/addon-sdk-1.12/lib/sdk/deprecated/unit-test.js
new file mode 100644
index 0000000..6a8ac1b
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/deprecated/unit-test.js
@@ -0,0 +1,451 @@
+/* vim:st=2:sts=2:sw=2:
+ * 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 memory = require('./memory');
+var timer = require("../timers");
+
+exports.findAndRunTests = function findAndRunTests(options) {
+ var TestFinder = require("./unit-test-finder").TestFinder;
+ var finder = new TestFinder({
+ filter: options.filter,
+ testInProcess: options.testInProcess,
+ testOutOfProcess: options.testOutOfProcess
+ });
+ var runner = new TestRunner({fs: options.fs});
+ finder.findTests(
+ function (tests) {
+ runner.startMany({tests: tests,
+ stopOnError: options.stopOnError,
+ onDone: options.onDone});
+ });
+};
+
+var TestRunner = exports.TestRunner = function TestRunner(options) {
+ if (options) {
+ this.fs = options.fs;
+ }
+ this.console = (options && "console" in options) ? options.console : console;
+ memory.track(this);
+ this.passed = 0;
+ this.failed = 0;
+ this.testRunSummary = [];
+ this.expectFailNesting = 0;
+};
+
+TestRunner.prototype = {
+ toString: function toString() "[object TestRunner]",
+
+ DEFAULT_PAUSE_TIMEOUT: 10000,
+ PAUSE_DELAY: 500,
+
+ _logTestFailed: function _logTestFailed(why) {
+ if (!(why in this.test.errors))
+ this.test.errors[why] = 0;
+ this.test.errors[why]++;
+ if (!this.testFailureLogged) {
+ this.console.error("TEST FAILED: " + this.test.name + " (" + why + ")");
+ this.testFailureLogged = true;
+ }
+ },
+
+ pass: function pass(message) {
+ if(!this.expectFailure) {
+ this.console.info("pass:", message);
+ this.passed++;
+ this.test.passed++;
+ }
+ else {
+ this.expectFailure = false;
+ this.fail('Failure Expected: ' + message);
+ }
+ },
+
+ fail: function fail(message) {
+ if(!this.expectFailure) {
+ this._logTestFailed("failure");
+ this.console.error("fail:", message);
+ this.console.trace();
+ this.failed++;
+ this.test.failed++;
+ }
+ else {
+ this.expectFailure = false;
+ this.pass(message);
+ }
+ },
+
+ expectFail: function(callback) {
+ this.expectFailure = true;
+ callback();
+ this.expectFailure = false;
+ },
+
+ exception: function exception(e) {
+ this._logTestFailed("exception");
+ this.console.exception(e);
+ this.failed++;
+ this.test.failed++;
+ },
+
+ assertMatches: function assertMatches(string, regexp, message) {
+ if (regexp.test(string)) {
+ if (!message)
+ message = uneval(string) + " matches " + uneval(regexp);
+ this.pass(message);
+ } else {
+ var no = uneval(string) + " doesn't match " + uneval(regexp);
+ if (!message)
+ message = no;
+ else
+ message = message + " (" + no + ")";
+ this.fail(message);
+ }
+ },
+
+ assertRaises: function assertRaises(func, predicate, message) {
+ try {
+ func();
+ if (message)
+ this.fail(message + " (no exception thrown)");
+ else
+ this.fail("function failed to throw exception");
+ } catch (e) {
+ var errorMessage;
+ if (typeof(e) == "string")
+ errorMessage = e;
+ else
+ errorMessage = e.message;
+ if (typeof(predicate) == "string")
+ this.assertEqual(errorMessage, predicate, message);
+ else
+ this.assertMatches(errorMessage, predicate, message);
+ }
+ },
+
+ assert: function assert(a, message) {
+ if (!a) {
+ if (!message)
+ message = "assertion failed, value is " + a;
+ this.fail(message);
+ } else
+ this.pass(message || "assertion successful");
+ },
+
+ assertNotEqual: function assertNotEqual(a, b, message) {
+ if (a != b) {
+ if (!message)
+ message = "a != b != " + uneval(a);
+ this.pass(message);
+ } else {
+ var equality = uneval(a) + " == " + uneval(b);
+ if (!message)
+ message = equality;
+ else
+ message += " (" + equality + ")";
+ this.fail(message);
+ }
+ },
+
+ assertEqual: function assertEqual(a, b, message) {
+ if (a == b) {
+ if (!message)
+ message = "a == b == " + uneval(a);
+ this.pass(message);
+ } else {
+ var inequality = uneval(a) + " != " + uneval(b);
+ if (!message)
+ message = inequality;
+ else
+ message += " (" + inequality + ")";
+ this.fail(message);
+ }
+ },
+
+ assertNotStrictEqual: function assertNotStrictEqual(a, b, message) {
+ if (a !== b) {
+ if (!message)
+ message = "a !== b !== " + uneval(a);
+ this.pass(message);
+ } else {
+ var equality = uneval(a) + " === " + uneval(b);
+ if (!message)
+ message = equality;
+ else
+ message += " (" + equality + ")";
+ this.fail(message);
+ }
+ },
+
+ assertStrictEqual: function assertStrictEqual(a, b, message) {
+ if (a === b) {
+ if (!message)
+ message = "a === b === " + uneval(a);
+ this.pass(message);
+ } else {
+ var inequality = uneval(a) + " !== " + uneval(b);
+ if (!message)
+ message = inequality;
+ else
+ message += " (" + inequality + ")";
+ this.fail(message);
+ }
+ },
+
+ assertFunction: function assertFunction(a, message) {
+ this.assertStrictEqual('function', typeof a, message);
+ },
+
+ assertUndefined: function(a, message) {
+ this.assertStrictEqual('undefined', typeof a, message);
+ },
+
+ assertNotUndefined: function(a, message) {
+ this.assertNotStrictEqual('undefined', typeof a, message);
+ },
+
+ assertNull: function(a, message) {
+ this.assertStrictEqual(null, a, message);
+ },
+
+ assertNotNull: function(a, message) {
+ this.assertNotStrictEqual(null, a, message);
+ },
+
+ assertObject: function(a, message) {
+ this.assertStrictEqual('[object Object]', Object.prototype.toString.apply(a), message);
+ },
+
+ assertString: function(a, message) {
+ this.assertStrictEqual('[object String]', Object.prototype.toString.apply(a), message);
+ },
+
+ assertArray: function(a, message) {
+ this.assertStrictEqual('[object Array]', Object.prototype.toString.apply(a), message);
+ },
+
+ assertNumber: function(a, message) {
+ this.assertStrictEqual('[object Number]', Object.prototype.toString.apply(a), message);
+ },
+
+ done: function done() {
+ if (!this.isDone) {
+ this.isDone = true;
+ if(this.test.teardown) {
+ this.test.teardown(this);
+ }
+ if (this.waitTimeout !== null) {
+ timer.clearTimeout(this.waitTimeout);
+ this.waitTimeout = null;
+ }
+ // Do not leave any callback set when calling to `waitUntil`
+ this.waitUntilCallback = null;
+ if (this.test.passed == 0 && this.test.failed == 0) {
+ this._logTestFailed("empty test");
+ this.failed++;
+ this.test.failed++;
+ }
+
+ this.testRunSummary.push({
+ name: this.test.name,
+ passed: this.test.passed,
+ failed: this.test.failed,
+ errors: [error for (error in this.test.errors)].join(", ")
+ });
+
+ if (this.onDone !== null) {
+ var onDone = this.onDone;
+ var self = this;
+ this.onDone = null;
+ timer.setTimeout(function() { onDone(self); }, 0);
+ }
+ }
+ },
+
+ // 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);
+ },
+
+ waitUntilNotEqual: function waitUntilNotEqual() {
+ return this._waitUntil(this.assertNotEqual, arguments);
+ },
+
+ waitUntilEqual: function waitUntilEqual() {
+ return this._waitUntil(this.assertEqual, arguments);
+ },
+
+ waitUntilMatches: function waitUntilMatches() {
+ return this._waitUntil(this.assertMatches, arguments);
+ },
+
+ /**
+ * Internal function that waits for an assertion to become true.
+ * @param {Function} assertionMethod
+ * Reference to a TestRunner assertion method like test.assert,
+ * test.assertEqual, ...
+ * @param {Array} args
+ * List of arguments to give to the previous assertion method.
+ * All functions in this list are going to be called to retrieve current
+ * assertion values.
+ */
+ _waitUntil: function waitUntil(assertionMethod, args) {
+ let count = 0;
+ let maxCount = this.DEFAULT_PAUSE_TIMEOUT / this.PAUSE_DELAY;
+
+ // We need to ensure that test is asynchronous
+ if (!this.waitTimeout)
+ this.waitUntilDone(this.DEFAULT_PAUSE_TIMEOUT);
+
+ let callback = null;
+ let finished = false;
+
+ let test = this;
+
+ // capture a traceback before we go async.
+ let traceback = require("../console/traceback");
+ let stack = traceback.get();
+ stack.splice(-2, 2);
+ let currentWaitStack = traceback.format(stack);
+ let timeout = null;
+
+ function loop(stopIt) {
+ timeout = null;
+
+ // Build a mockup object to fake TestRunner API and intercept calls to
+ // pass and fail methods, in order to retrieve nice error messages
+ // and assertion result
+ let mock = {
+ pass: function (msg) {
+ test.pass(msg);
+ test.waitUntilCallback = null;
+ if (callback && !stopIt)
+ callback();
+ finished = true;
+ },
+ fail: function (msg) {
+ // If we are called on test timeout, we stop the loop
+ // and print which test keeps failing:
+ if (stopIt) {
+ test.console.error("test assertion never became true:\n",
+ msg + "\n",
+ currentWaitStack);
+ if (timeout)
+ timer.clearTimeout(timeout);
+ return;
+ }
+ timeout = timer.setTimeout(loop, test.PAUSE_DELAY);
+ }
+ };
+
+ // Automatically call args closures in order to build arguments for
+ // assertion function
+ let appliedArgs = [];
+ for (let i = 0, l = args.length; i < l; i++) {
+ let a = args[i];
+ if (typeof a == "function") {
+ try {
+ a = a();
+ }
+ catch(e) {
+ test.fail("Exception when calling asynchronous assertion: " + e +
+ "\n" + e.stack);
+ finished = true;
+ return;
+ }
+ }
+ appliedArgs.push(a);
+ }
+
+ // Finally call assertion function with current assertion values
+ assertionMethod.apply(mock, appliedArgs);
+ }
+ loop();
+ this.waitUntilCallback = loop;
+
+ // Return an object with `then` method, to offer a way to execute
+ // some code when the assertion passed or failed
+ return {
+ then: function (c) {
+ callback = c;
+
+ // In case of immediate positive result, we need to execute callback
+ // immediately here:
+ if (finished)
+ callback();
+ }
+ };
+ },
+
+ waitUntilDone: function waitUntilDone(ms) {
+ if (ms === undefined)
+ ms = this.DEFAULT_PAUSE_TIMEOUT;
+
+ var self = this;
+
+ function tiredOfWaiting() {
+ self._logTestFailed("timed out");
+ if (self.waitUntilCallback) {
+ self.waitUntilCallback(true);
+ self.waitUntilCallback = null;
+ }
+ self.failed++;
+ self.test.failed++;
+ self.done();
+ }
+
+ // We may already have registered a timeout callback
+ if (this.waitTimeout)
+ timer.clearTimeout(this.waitTimeout);
+
+ this.waitTimeout = timer.setTimeout(tiredOfWaiting, ms);
+ },
+
+ startMany: function startMany(options) {
+ function runNextTest(self) {
+ var test = options.tests.shift();
+ if (options.stopOnError && self.test && self.test.failed) {
+ self.console.error("aborted: test failed and --stop-on-error was specified");
+ options.onDone(self);
+ } else if (test) {
+ self.start({test: test, onDone: runNextTest});
+ } else {
+ options.onDone(self);
+ }
+ }
+ runNextTest(this);
+ },
+
+ start: function start(options) {
+ this.test = options.test;
+ this.test.passed = 0;
+ this.test.failed = 0;
+ this.test.errors = {};
+
+ this.isDone = false;
+ this.onDone = options.onDone;
+ this.waitTimeout = null;
+ this.testFailureLogged = false;
+
+ try {
+ this.console.info("executing '" + this.test.name + "'");
+
+ if(this.test.setup) {
+ this.test.setup(this);
+ }
+ this.test.testFunction(this);
+ } catch (e) {
+ this.exception(e);
+ }
+ if (this.waitTimeout === null)
+ this.done();
+ }
+};
diff --git a/tools/addon-sdk-1.12/lib/sdk/deprecated/window-utils.js b/tools/addon-sdk-1.12/lib/sdk/deprecated/window-utils.js
new file mode 100644
index 0000000..66fcfba
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/deprecated/window-utils.js
@@ -0,0 +1,201 @@
+/* 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 { Cc, Ci } = require('chrome');
+const { EventEmitter } = require('../deprecated/events');
+const { Trait } = require('../deprecated/traits');
+const { when } = require('../system/unload');
+const { getInnerId, getOuterId, windows, isDocumentLoaded, isBrowser,
+ getMostRecentBrowserWindow } = require('../window/utils');
+const errors = require('../deprecated/errors');
+const { deprecateFunction } = require('../util/deprecate');
+
+const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1'].
+ getService(Ci.nsIWindowWatcher);
+const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
+ getService(Ci.nsIAppShellService);
+
+/**
+ * An iterator for XUL windows currently in the application.
+ *
+ * @return A generator that yields XUL windows exposing the
+ * nsIDOMWindow interface.
+ */
+function windowIterator() {
+ // Bug 752631: We only pass already loaded window in order to avoid
+ // breaking XUL windows DOM. DOM is broken when some JS code try
+ // to access DOM during "uninitialized" state of the related document.
+ let list = windows().filter(isDocumentLoaded);
+ for (let i = 0, l = list.length; i < l; i++) {
+ yield list[i];
+ }
+};
+exports.windowIterator = windowIterator;
+
+/**
+ * An iterator for browser windows currently open in the application.
+ * @returns {Function}
+ * A generator that yields browser windows exposing the `nsIDOMWindow`
+ * interface.
+ */
+function browserWindowIterator() {
+ for each (let window in windowIterator()) {
+ if (isBrowser(window))
+ yield window;
+ }
+}
+exports.browserWindowIterator = browserWindowIterator;
+
+function WindowTracker(delegate) {
+ if (!(this instanceof WindowTracker)) {
+ return new WindowTracker(delegate);
+ }
+
+ this._delegate = delegate;
+ this._loadingWindows = [];
+
+ for each (let window in windows())
+ this._regWindow(window);
+ windowWatcher.registerNotification(this);
+
+ require('../system/unload').ensure(this);
+
+ return this;
+};
+
+WindowTracker.prototype = {
+ _regLoadingWindow: function _regLoadingWindow(window) {
+ this._loadingWindows.push(window);
+ window.addEventListener('load', this, true);
+ },
+
+ _unregLoadingWindow: function _unregLoadingWindow(window) {
+ var index = this._loadingWindows.indexOf(window);
+
+ if (index != -1) {
+ this._loadingWindows.splice(index, 1);
+ window.removeEventListener('load', this, true);
+ }
+ },
+
+ _regWindow: function _regWindow(window) {
+ if (window.document.readyState == 'complete') {
+ this._unregLoadingWindow(window);
+ this._delegate.onTrack(window);
+ } else
+ this._regLoadingWindow(window);
+ },
+
+ _unregWindow: function _unregWindow(window) {
+ if (window.document.readyState == 'complete') {
+ if (this._delegate.onUntrack)
+ this._delegate.onUntrack(window);
+ } else {
+ this._unregLoadingWindow(window);
+ }
+ },
+
+ unload: function unload() {
+ windowWatcher.unregisterNotification(this);
+ for each (let window in windows())
+ this._unregWindow(window);
+ },
+
+ handleEvent: errors.catchAndLog(function handleEvent(event) {
+ if (event.type == 'load' && event.target) {
+ var window = event.target.defaultView;
+ if (window)
+ this._regWindow(window);
+ }
+ }),
+
+ observe: errors.catchAndLog(function observe(subject, topic, data) {
+ var window = subject.QueryInterface(Ci.nsIDOMWindow);
+ if (topic == 'domwindowopened')
+ this._regWindow(window);
+ else
+ this._unregWindow(window);
+ })
+};
+exports.WindowTracker = WindowTracker;
+
+const WindowTrackerTrait = Trait.compose({
+ _onTrack: Trait.required,
+ _onUntrack: Trait.required,
+ constructor: function WindowTrackerTrait() {
+ WindowTracker({
+ onTrack: this._onTrack.bind(this),
+ onUntrack: this._onUntrack.bind(this)
+ });
+ }
+});
+exports.WindowTrackerTrait = WindowTrackerTrait;
+
+var gDocsToClose = [];
+
+function onDocUnload(event) {
+ var index = gDocsToClose.indexOf(event.target);
+ if (index == -1)
+ throw new Error('internal error: unloading document not found');
+ var document = gDocsToClose.splice(index, 1)[0];
+ // Just in case, let's remove the event listener too.
+ document.defaultView.removeEventListener('unload', onDocUnload, false);
+}
+
+onDocUnload = require('./errors').catchAndLog(onDocUnload);
+
+exports.closeOnUnload = function closeOnUnload(window) {
+ window.addEventListener('unload', onDocUnload, false);
+ gDocsToClose.push(window.document);
+};
+
+Object.defineProperties(exports, {
+ activeWindow: {
+ enumerable: true,
+ get: function() {
+ return Cc['@mozilla.org/appshell/window-mediator;1']
+ .getService(Ci.nsIWindowMediator)
+ .getMostRecentWindow(null);
+ },
+ set: function(window) {
+ try { window.focus(); } catch (e) { }
+ }
+ },
+ activeBrowserWindow: {
+ enumerable: true,
+ get: getMostRecentBrowserWindow
+ }
+});
+
+
+/**
+ * Returns the ID of the window's current inner window.
+ */
+exports.getInnerId = deprecateFunction(getInnerId,
+ 'require("window-utils").getInnerId is deprecated, ' +
+ 'please use require("window/utils").getInnerId instead'
+);
+
+exports.getOuterId = deprecateFunction(getOuterId,
+ 'require("window-utils").getOuterId is deprecated, ' +
+ 'please use require("window/utils").getOuterId instead'
+);
+
+exports.isBrowser = deprecateFunction(isBrowser,
+ 'require("window-utils").isBrowser is deprecated, ' +
+ 'please use require("window/utils").isBrowser instead'
+);
+
+exports.hiddenWindow = appShellService.hiddenDOMWindow;
+
+when(
+ function() {
+ gDocsToClose.slice().forEach(
+ function(doc) { doc.defaultView.close(); });
+ });
diff --git a/tools/addon-sdk-1.12/lib/sdk/dom/events.js b/tools/addon-sdk-1.12/lib/sdk/dom/events.js
new file mode 100644
index 0000000..3f2d21c
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/dom/events.js
@@ -0,0 +1,140 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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"
+};
+
+// Utility function that returns copy of the given `text` with last character
+// removed if it is `"s"`.
+function singularify(text) {
+ return text[text.length - 1] === "s" ? text.substr(0, text.length - 1) : text;
+}
+
+// Utility function that takes event type, argument is passed to
+// `document.createEvent` and returns name of the initializer method of the
+// given event. Please note that there are some event types whose initializer
+// methods can't be guessed by this function. For more details see following
+// link: https://developer.mozilla.org/En/DOM/Document.createEvent
+function getInitializerName(category) {
+ return "init" + singularify(category);
+}
+
+/**
+ * Registers an event `listener` on a given `element`, that will be called
+ * when events of specified `type` is dispatched on the `element`.
+ * @param {Element} element
+ * Dom element to register listener on.
+ * @param {String} type
+ * A string representing the
+ * [event type](https://developer.mozilla.org/en/DOM/event.type) to
+ * listen for.
+ * @param {Function} listener
+ * Function that is called whenever an event of the specified `type`
+ * occurs.
+ * @param {Boolean} capture
+ * If true, indicates that the user wishes to initiate capture. After
+ * initiating capture, all events of the specified type will be dispatched
+ * to the registered listener before being dispatched to any `EventTarget`s
+ * beneath it in the DOM tree. Events which are bubbling upward through
+ * the tree will not trigger a listener designated to use capture.
+ * See [DOM Level 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#event-flow)
+ * for a detailed explanation.
+ */
+function on(element, type, listener, capture) {
+ // `capture` defaults to `false`.
+ capture = capture || false;
+ element.addEventListener(type, listener, capture);
+}
+exports.on = on;
+
+/**
+ * Registers an event `listener` on a given `element`, that will be called
+ * only once, next time event of specified `type` is dispatched on the
+ * `element`.
+ * @param {Element} element
+ * Dom element to register listener on.
+ * @param {String} type
+ * A string representing the
+ * [event type](https://developer.mozilla.org/en/DOM/event.type) to
+ * listen for.
+ * @param {Function} listener
+ * Function that is called whenever an event of the specified `type`
+ * occurs.
+ * @param {Boolean} capture
+ * If true, indicates that the user wishes to initiate capture. After
+ * initiating capture, all events of the specified type will be dispatched
+ * to the registered listener before being dispatched to any `EventTarget`s
+ * beneath it in the DOM tree. Events which are bubbling upward through
+ * the tree will not trigger a listener designated to use capture.
+ * See [DOM Level 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#event-flow)
+ * for a detailed explanation.
+ */
+function once(element, type, listener, capture) {
+ on(element, type, function selfRemovableListener(event) {
+ removeListener(element, type, selfRemovableListener, capture);
+ listener.apply(this, arguments);
+ }, capture);
+}
+exports.once = once;
+
+/**
+ * Unregisters an event `listener` on a given `element` for the events of the
+ * specified `type`.
+ *
+ * @param {Element} element
+ * Dom element to unregister listener from.
+ * @param {String} type
+ * A string representing the
+ * [event type](https://developer.mozilla.org/en/DOM/event.type) to
+ * listen for.
+ * @param {Function} listener
+ * Function that is called whenever an event of the specified `type`
+ * occurs.
+ * @param {Boolean} capture
+ * If true, indicates that the user wishes to initiate capture. After
+ * initiating capture, all events of the specified type will be dispatched
+ * to the registered listener before being dispatched to any `EventTarget`s
+ * beneath it in the DOM tree. Events which are bubbling upward through
+ * the tree will not trigger a listener designated to use capture.
+ * See [DOM Level 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#event-flow)
+ * for a detailed explanation.
+ */
+function removeListener(element, type, listener, capture) {
+ element.removeEventListener(type, listener, capture);
+}
+exports.removeListener = removeListener;
+
+/**
+ * Emits event of the specified `type` and `category` on the given `element`.
+ * Specified `settings` are used to initialize event before dispatching it.
+ * @param {Element} element
+ * Dom element to dispatch event on.
+ * @param {String} type
+ * A string representing the
+ * [event type](https://developer.mozilla.org/en/DOM/event.type).
+ * @param {Object} options
+ * Options object containing following properties:
+ * - `category`: String passed to the `document.createEvent`. Option is
+ * optional and defaults to "UIEvents".
+ * - `initializer`: If passed it will be used as name of the method used
+ * to initialize event. If omitted name will be generated from the
+ * `category` field by prefixing it with `"init"` and removing last
+ * character if it matches `"s"`.
+ * - `settings`: Array of settings that are forwarded to the event
+ * initializer after firs `type` argument.
+ * @see https://developer.mozilla.org/En/DOM/Document.createEvent
+ */
+function emit(element, type, { category, initializer, settings }) {
+ category = category || "UIEvents";
+ initializer = initializer || getInitializerName(category);
+ let document = element.ownerDocument;
+ let event = document.createEvent(category);
+ event[initializer].apply(event, [type].concat(settings));
+ element.dispatchEvent(event);
+};
+exports.emit = emit;
diff --git a/tools/addon-sdk-1.12/lib/sdk/dom/events/keys.js b/tools/addon-sdk-1.12/lib/sdk/dom/events/keys.js
new file mode 100644
index 0000000..01e4a0d
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/dom/events/keys.js
@@ -0,0 +1,64 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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("../events");
+const { getCodeForKey, toJSON } = require("../../keyboard/utils");
+const { has } = require("../../util/array");
+const { isString } = require("../../lang/type");
+
+const INITIALIZER = "initKeyEvent";
+const CATEGORY = "KeyboardEvent";
+
+function Options(options) {
+ if (!isString(options))
+ return options;
+
+ var { key, modifiers } = toJSON(options);
+ return {
+ key: key,
+ control: has(modifiers, "control"),
+ alt: has(modifiers, "alt"),
+ shift: has(modifiers, "shift"),
+ meta: has(modifiers, "meta")
+ };
+}
+
+var keyEvent = exports.keyEvent = function keyEvent(element, type, options) {
+
+ emit(element, type, {
+ initializer: INITIALIZER,
+ category: CATEGORY,
+ settings: [
+ !("bubbles" in options) || options.bubbles !== false,
+ !("cancelable" in options) || options.cancelable !== false,
+ "window" in options && options.window ? options.window : null,
+ "control" in options && !!options.control,
+ "alt" in options && !!options.alt,
+ "shift" in options && !!options.shift,
+ "meta" in options && !!options.meta,
+ getCodeForKey(options.key) || 0,
+ options.key.length === 1 ? options.key.charCodeAt(0) : 0
+ ]
+ });
+}
+
+exports.keyDown = function keyDown(element, options) {
+ keyEvent(element, "keydown", Options(options));
+};
+
+exports.keyUp = function keyUp(element, options) {
+ keyEvent(element, "keyup", Options(options));
+};
+
+exports.keyPress = function keyPress(element, options) {
+ keyEvent(element, "keypress", Options(options));
+};
+
diff --git a/tools/addon-sdk-1.12/lib/sdk/event/core.js b/tools/addon-sdk-1.12/lib/sdk/event/core.js
new file mode 100644
index 0000000..e63e3b9
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/event/core.js
@@ -0,0 +1,152 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 UNCAUGHT_ERROR = 'An error event was emitted for which there was no listener.';
+const BAD_LISTENER = 'The event listener must be a function.';
+
+const { ns } = require('../core/namespace');
+
+const event = ns();
+
+// Utility function to access given event `target` object's event listeners for
+// the specific event `type`. If listeners for this type does not exists they
+// will be created.
+const observers = function observers(target, type) {
+ let listeners = event(target);
+ return type in listeners ? listeners[type] : listeners[type] = [];
+};
+
+/**
+ * Registers an event `listener` that is called every time events of
+ * specified `type` is emitted on the given event `target`.
+ * @param {Object} target
+ * Event target object.
+ * @param {String} type
+ * The type of event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+function on(target, type, listener) {
+ if (typeof(listener) !== 'function')
+ throw new Error(BAD_LISTENER);
+
+ let listeners = observers(target, type);
+ if (!~listeners.indexOf(listener))
+ listeners.push(listener);
+}
+exports.on = on;
+
+/**
+ * Registers an event `listener` that is called only the next time an event
+ * of the specified `type` is emitted on the given event `target`.
+ * @param {Object} target
+ * Event target object.
+ * @param {String} type
+ * The type of the event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+function once(target, type, listener) {
+ on(target, type, function observer() {
+ off(target, type, observer);
+ listener.apply(target, arguments);
+ });
+}
+exports.once = once;
+
+/**
+ * Execute each of the listeners in order with the supplied arguments.
+ * All the exceptions that are thrown by listeners during the emit
+ * are caught and can be handled by listeners of 'error' event. Thrown
+ * exceptions are passed as an argument to an 'error' event listener.
+ * If no 'error' listener is registered exception will be logged into an
+ * error console.
+ * @param {Object} target
+ * Event target object.
+ * @param {String} type
+ * The type of event.
+ * @params {Object|Number|String|Boolean} message
+ * First argument that will be passed to listeners.
+ * @params {Object|Number|String|Boolean} ...
+ * More arguments that will be passed to listeners.
+ */
+function emit(target, type, message /*, ...*/) {
+ for each (let item in emit.lazy.apply(emit.lazy, arguments)) {
+ // We just iterate, iterator take care of emitting events.
+ }
+}
+
+/**
+ * This is very experimental feature that you should not use unless absolutely
+ * need it. Also it may be removed at any point without any further notice.
+ *
+ * Creates lazy iterator of return values of listeners. You can think of it
+ * as lazy array of return values of listeners for the `emit` with the given
+ * arguments.
+ */
+emit.lazy = function lazy(target, type, message /*, ...*/) {
+ let args = Array.slice(arguments, 2)
+ let listeners = observers(target, type).slice()
+ while (listeners.length) {
+ try {
+ yield listeners.shift().apply(target, args);
+ }
+ catch (error) {
+ // If exception is not thrown by a error listener and error listener is
+ // registered emit `error` event. Otherwise dump exception to the console.
+ if (type !== 'error' && observers(target, 'error').length)
+ emit(target, 'error', error);
+ else
+ console.exception(error);
+ }
+ }
+}
+exports.emit = emit;
+
+/**
+ * Removes an event `listener` for the given event `type` on the given event
+ * `target`. If no `listener` is passed removes all listeners of the given
+ * `type`. If `type` is not passed removes all the listeners of the given
+ * event `target`.
+ * @param {Object} target
+ * The event target object.
+ * @param {String} type
+ * The type of event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+function off(target, type, listener) {
+ let length = arguments.length;
+ if (length === 3) {
+ let listeners = observers(target, type);
+ let index = listeners.indexOf(listener);
+ if (~index)
+ listeners.splice(index, 1);
+ }
+ else if (length === 2) {
+ observers(target, type).splice(0);
+ }
+ else if (length === 1) {
+ let listeners = event(target);
+ Object.keys(listeners).forEach(function(type) delete listeners[type]);
+ }
+}
+exports.off = off;
+
+/**
+ * Returns a number of event listeners registered for the given event `type`
+ * on the given event `target`.
+ */
+function count(target, type) {
+ return observers(target, type).length;
+}
+exports.count = count;
diff --git a/tools/addon-sdk-1.12/lib/sdk/event/target.js b/tools/addon-sdk-1.12/lib/sdk/event/target.js
new file mode 100644
index 0000000..b8906c0
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/event/target.js
@@ -0,0 +1,80 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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": "stable"
+};
+
+const { on, once, off } = require('./core');
+const { method } = require('../lang/functional');
+const { Class } = require('../core/heritage');
+
+const EVENT_TYPE_PATTERN = /^on([A-Z]\w+$)/;
+
+/**
+ * `EventTarget` is an exemplar for creating an objects that can be used to
+ * add / remove event listeners on them. Events on these objects may be emitted
+ * via `emit` function exported by 'event/core' module.
+ */
+const EventTarget = Class({
+ /**
+ * Method initializes `this` event source. It goes through properties of a
+ * given `options` and registers listeners for the ones that look like an
+ * event listeners.
+ */
+ initialize: function initialize(options) {
+ options = options || {};
+ // Go through each property and registers event listeners for those
+ // that have a name matching following pattern (`onEventType`).
+ Object.keys(options).forEach(function onEach(key) {
+ let match = EVENT_TYPE_PATTERN.exec(key);
+ let type = match && match[1].toLowerCase();
+ let listener = options[key];
+
+ if (type && typeof(listener) === 'function')
+ this.on(type, listener);
+ }, this);
+ },
+ /**
+ * Registers an event `listener` that is called every time events of
+ * specified `type` are emitted.
+ * @param {String} type
+ * The type of event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ * @example
+ * worker.on('message', function (data) {
+ * console.log('data received: ' + data)
+ * })
+ */
+ on: method(on),
+ /**
+ * Registers an event `listener` that is called once the next time an event
+ * of the specified `type` is emitted.
+ * @param {String} type
+ * The type of the event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+ once: method(once),
+ /**
+ * Removes an event `listener` for the given event `type`.
+ * @param {String} type
+ * The type of event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+ removeListener: function removeListener(type, listener) {
+ // Note: We can't just wrap `off` in `method` as we do it for other methods
+ // cause skipping a second or third argument will behave very differently
+ // than intended. This way we make sure all arguments are passed and only
+ // one listener is removed at most.
+ off(this, type, listener);
+ }
+});
+exports.EventTarget = EventTarget;
diff --git a/tools/addon-sdk-1.12/lib/sdk/frame/hidden-frame.js b/tools/addon-sdk-1.12/lib/sdk/frame/hidden-frame.js
new file mode 100644
index 0000000..1bf51de
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/frame/hidden-frame.js
@@ -0,0 +1,181 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 {Cc, Ci} = require("chrome");
+const errors = require("../deprecated/errors");
+const apiUtils = require("../deprecated/api-utils");
+const timer = require("../timers");
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+let hostFrame, hostDocument, hiddenWindow, isHostFrameReady = false;
+
+if (!require("../system/xul-app").isOneOf(["Firefox", "Fennec", "Thunderbird"])) {
+ throw new Error([
+ "The hidden-frame module currently supports only Firefox and Thunderbird. ",
+ "In the future, we would like it to support other applications, however. ",
+ "Please see https://bugzilla.mozilla.org/show_bug.cgi?id=546740 for more ",
+ "information."
+ ].join(""));
+}
+
+let appShellService = Cc["@mozilla.org/appshell/appShellService;1"].
+ getService(Ci.nsIAppShellService);
+hiddenWindow = appShellService.hiddenDOMWindow;
+
+if (!hiddenWindow) {
+ throw new Error([
+ "The hidden-frame module needs an app that supports a hidden window. ",
+ "We would like it to support other applications, however. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=546740 for more information."
+ ].join(""));
+}
+
+// Check if we can use the hidden window itself to host our iframes.
+// If it's not a suitable host, the hostFrame will be lazily created
+// by the first HiddenFrame instance.
+if (hiddenWindow.location.protocol == "chrome:" &&
+ (hiddenWindow.document.contentType == "application/vnd.mozilla.xul+xml" ||
+ hiddenWindow.document.contentType == "application/xhtml+xml")) {
+ hostFrame = hiddenWindow;
+ hostDocument = hiddenWindow.document;
+ isHostFrameReady = true;
+}
+
+function setHostFrameReady() {
+ hostDocument = hostFrame.contentDocument;
+ hostFrame.removeEventListener("DOMContentLoaded", setHostFrameReady, false);
+ isHostFrameReady = true;
+}
+
+// This cache is used to access friend properties between functions
+// without exposing them on the public API.
+let cache = [];
+
+exports.HiddenFrame = apiUtils.publicConstructor(HiddenFrame);
+
+function HiddenFrame(options) {
+ options = options || {};
+ let self = this;
+ let validOptions = apiUtils.validateOptions(options, {
+ onReady: {
+ is: ["undefined", "function", "array"],
+ ok: function(v) {
+ if (apiUtils.getTypeOf(v) === "array") {
+ // make sure every item is a function
+ return v.every(function (item) typeof(item) === "function")
+ }
+ return true;
+ }
+ },
+ onUnload: {
+ is: ["undefined", "function"]
+ }
+ });
+
+ for (let key in validOptions) {
+ let val = validOptions[key];
+ if (typeof(val) != "undefined")
+ options[key] = val;
+ }
+
+ require("../util/collection").addCollectionProperty(this, "onReady");
+ if (options.onReady)
+ this.onReady.add(options.onReady);
+ if (options.onUnload)
+ this.onUnload = options.onUnload;
+
+ if (!hostFrame) {
+ hostFrame = hiddenWindow.document.createElement("iframe");
+
+ // ugly ugly hack. This is the most lightweight chrome:// file I could find on the tree
+ // This hack should be removed by proper platform support on bug 565388
+ hostFrame.setAttribute("src", "chrome://global/content/mozilla.xhtml");
+ hostFrame.addEventListener("DOMContentLoaded", setHostFrameReady, false);
+
+ hiddenWindow.document.body.appendChild(hostFrame);
+ }
+
+ this.toString = function toString() "[object Frame]";
+}
+
+exports.add = function JP_SDK_Frame_add(frame) {
+ if (!(frame instanceof HiddenFrame))
+ throw new Error("The object to be added must be a HiddenFrame.");
+
+ // This instance was already added.
+ if (cache.filter(function (v) v.frame === frame)[0])
+ return frame;
+
+ function createElement() {
+ hostFrame.removeEventListener("DOMContentLoaded", createElement, false);
+
+ let element = hostDocument.createElementNS(XUL_NS, "iframe");
+
+ element.setAttribute("type", "content");
+ hostDocument.documentElement.appendChild(element);
+
+ /* Public API: hiddenFrame.element */
+ frame.__defineGetter__("element", function () element);
+
+ // Notify consumers that the frame is ready.
+ function onReadyListener(event) {
+ element.removeEventListener("DOMContentLoaded", onReadyListener, false);
+ if (event.target == element.contentDocument) {
+ for (let handler in frame.onReady)
+ errors.catchAndLog(function () handler.call(frame))();
+ }
+ }
+ element.addEventListener("DOMContentLoaded", onReadyListener, false);
+
+ cache.push({
+ frame: frame,
+ element: element,
+ unload: function unload() {
+ // Call before removing to let a chance to avoid "dead object" exception
+ if (typeof frame.onUnload === "function")
+ frame.onUnload();
+ hostDocument.documentElement.removeChild(element);
+ }
+ });
+ }
+
+ /* Begin element construction or schedule it for later */
+ if (isHostFrameReady) {
+ createElement();
+ } else {
+ hostFrame.addEventListener("DOMContentLoaded", createElement, false);
+ }
+
+ return frame;
+}
+
+exports.remove = function remove(frame) {
+ if (!(frame instanceof HiddenFrame))
+ throw new Error("The object to be removed must be a HiddenFrame.");
+
+ let entry = cache.filter(function (v) v.frame === frame)[0];
+ if (!entry)
+ return;
+
+ // Remove from cache before calling in order to avoid loop
+ cache.splice(cache.indexOf(entry), 1);
+ entry.unload();
+}
+
+require("../system/unload").when(function () {
+ for each (let entry in cache.slice())
+ exports.remove(entry.frame);
+
+ if (hostFrame && hostFrame !== hiddenWindow)
+ hiddenWindow.document.body.removeChild(hostFrame);
+});
diff --git a/tools/addon-sdk-1.12/lib/sdk/frame/utils.js b/tools/addon-sdk-1.12/lib/sdk/frame/utils.js
new file mode 100644
index 0000000..ad4ca7c
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/frame/utils.js
@@ -0,0 +1,63 @@
+/* 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 XUL = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
+
+/**
+ * Creates a XUL `browser` element in a privileged document.
+ * @params {nsIDOMDocument} document
+ * @params {String} options.type
+ * By default is 'content' for possible values see:
+ * https://developer.mozilla.org/en/XUL/iframe#a-browser.type
+ * @params {String} options.uri
+ * URI of the document to be loaded into created frame.
+ * @params {Boolean} options.remote
+ * If `true` separate process will be used for this frame, also in such
+ * case all the following options are ignored.
+ * @params {Boolean} options.allowAuth
+ * Whether to allow auth dialogs. Defaults to `false`.
+ * @params {Boolean} options.allowJavascript
+ * Whether to allow Javascript execution. Defaults to `false`.
+ * @params {Boolean} options.allowPlugins
+ * Whether to allow plugin execution. Defaults to `false`.
+ */
+function create(document, options) {
+ options = options || {};
+ let remote = 'remote' in options && options.remote === true;
+
+ let frame = document.createElementNS(XUL, 'browser');
+ // Type="content" is mandatory to enable stuff here:
+ // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsFrameLoader.cpp#1776
+ frame.setAttribute('type', options.type || 'content');
+ frame.setAttribute('src', options.uri || 'about:blank');
+
+ // Load in separate process if `options.remote` is `true`.
+ // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsFrameLoader.cpp#1347
+ if (remote) {
+ // We remove XBL binding to avoid execution of code that is not going to
+ // work because browser has no docShell attribute in remote mode
+ // (for example)
+ frame.setAttribute('style', '-moz-binding: none;');
+ frame.setAttribute('remote', 'true');
+ }
+
+ document.documentElement.appendChild(frame);
+
+ // If browser is remote it won't have a `docShell`.
+ if (!remote) {
+ let docShell = frame.docShell;
+ docShell.allowAuth = options.allowAuth || false;
+ docShell.allowJavascript = options.allowJavascript || false;
+ docShell.allowPlugins = options.allowPlugins || false;
+ }
+
+ return frame;
+}
+exports.create = create;
diff --git a/tools/addon-sdk-1.12/lib/sdk/hotkeys.js b/tools/addon-sdk-1.12/lib/sdk/hotkeys.js
new file mode 100644
index 0000000..3491d2c
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/hotkeys.js
@@ -0,0 +1,41 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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": "stable"
+};
+
+const INVALID_HOTKEY = "Hotkey must have at least one modifier.";
+
+const { toJSON: jsonify, toString: stringify,
+ isFunctionKey } = require("./keyboard/utils");
+const { register, unregister } = require("./keyboard/hotkeys");
+
+const Hotkey = exports.Hotkey = function Hotkey(options) {
+ if (!(this instanceof Hotkey))
+ return new Hotkey(options);
+
+ // Parsing key combination string.
+ let hotkey = jsonify(options.combo);
+ if (!isFunctionKey(hotkey.key) && !hotkey.modifiers.length) {
+ throw new TypeError(INVALID_HOTKEY);
+ }
+
+ this.onPress = options.onPress;
+ this.toString = stringify.bind(null, hotkey);
+ // Registering listener on keyboard combination enclosed by this hotkey.
+ // Please note that `this.toString()` is a normalized version of
+ // `options.combination` where order of modifiers is sorted and `accel` is
+ // replaced with platform specific key.
+ register(this.toString(), this.onPress);
+ // We freeze instance before returning it in order to make it's properties
+ // read-only.
+ return Object.freeze(this);
+};
+Hotkey.prototype.destroy = function destroy() {
+ unregister(this.toString(), this.onPress);
+};
diff --git a/tools/addon-sdk-1.12/lib/sdk/io/byte-streams.js b/tools/addon-sdk-1.12/lib/sdk/io/byte-streams.js
new file mode 100644
index 0000000..27b1802
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/io/byte-streams.js
@@ -0,0 +1,106 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et filetype=javascript
+ * 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"
+};
+
+exports.ByteReader = ByteReader;
+exports.ByteWriter = ByteWriter;
+
+const {Cc, Ci} = require("chrome");
+
+// This just controls the maximum number of bytes we read in at one time.
+const BUFFER_BYTE_LEN = 0x8000;
+
+function ByteReader(inputStream) {
+ const self = this;
+
+ let stream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ stream.setInputStream(inputStream);
+
+ let manager = new StreamManager(this, stream);
+
+ this.read = function ByteReader_read(numBytes) {
+ manager.ensureOpened();
+ if (typeof(numBytes) !== "number")
+ numBytes = Infinity;
+
+ let data = "";
+ let read = 0;
+ try {
+ while (true) {
+ let avail = stream.available();
+ let toRead = Math.min(numBytes - read, avail, BUFFER_BYTE_LEN);
+ if (toRead <= 0)
+ break;
+ data += stream.readBytes(toRead);
+ read += toRead;
+ }
+ }
+ catch (err) {
+ throw new Error("Error reading from stream: " + err);
+ }
+
+ return data;
+ };
+}
+
+function ByteWriter(outputStream) {
+ const self = this;
+
+ let stream = Cc["@mozilla.org/binaryoutputstream;1"].
+ createInstance(Ci.nsIBinaryOutputStream);
+ stream.setOutputStream(outputStream);
+
+ let manager = new StreamManager(this, stream);
+
+ this.write = function ByteWriter_write(str) {
+ manager.ensureOpened();
+ try {
+ stream.writeBytes(str, str.length);
+ }
+ catch (err) {
+ throw new Error("Error writing to stream: " + err);
+ }
+ };
+}
+
+
+// This manages the lifetime of stream, a ByteReader or ByteWriter. It defines
+// closed and close() on stream and registers an unload listener that closes
+// rawStream if it's still opened. It also provides ensureOpened(), which
+// throws an exception if the stream is closed.
+function StreamManager(stream, rawStream) {
+ const self = this;
+ this.rawStream = rawStream;
+ this.opened = true;
+
+ stream.__defineGetter__("closed", function stream_closed() {
+ return !self.opened;
+ });
+
+ stream.close = function stream_close() {
+ self.ensureOpened();
+ self.unload();
+ };
+
+ require("../system/unload").ensure(this);
+}
+
+StreamManager.prototype = {
+ ensureOpened: function StreamManager_ensureOpened() {
+ if (!this.opened)
+ throw new Error("The stream is closed and cannot be used.");
+ },
+ unload: function StreamManager_unload() {
+ this.rawStream.close();
+ this.opened = false;
+ }
+};
diff --git a/tools/addon-sdk-1.12/lib/sdk/io/data.js b/tools/addon-sdk-1.12/lib/sdk/io/data.js
new file mode 100644
index 0000000..9dd7246
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/io/data.js
@@ -0,0 +1,84 @@
+/* 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, Cu } = require("chrome");
+const base64 = require("../base64");
+
+const IOService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm");
+const FaviconService = Cc["@mozilla.org/browser/favicon-service;1"].
+ getService(Ci.nsIFaviconService);
+const { deprecateFunction, deprecatedUsage } = require("../util/deprecate");
+
+const PNG_B64 = "data:image/png;base64,";
+const DEF_FAVICON_URI = "chrome://mozapps/skin/places/defaultFavicon.png";
+let DEF_FAVICON = null;
+
+/**
+ * Takes URI of the page and returns associated favicon URI.
+ * If page under passed uri has no favicon then base64 encoded data URI of
+ * default faveicon is returned.
+ * @param {String} uri
+ * @returns {String}
+ */
+exports.getFaviconURIForLocation = function getFaviconURIForLocation(uri) {
+ let pageURI = NetUtil.newURI(uri);
+ try {
+ return FaviconService.getFaviconDataAsDataURL(
+ FaviconService.getFaviconForPage(pageURI));
+ }
+ catch(e) {
+ if (!DEF_FAVICON) {
+ DEF_FAVICON = PNG_B64 +
+ base64.encode(getChromeURIContent(DEF_FAVICON_URI));
+ }
+ return DEF_FAVICON;
+ }
+}
+
+/**
+ * Takes chrome URI and returns content under that URI.
+ * @param {String} chromeURI
+ * @returns {String}
+ */
+function getChromeURIContent(chromeURI) {
+ let channel = IOService.newChannel(chromeURI, null, null);
+ let input = channel.open();
+ let stream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ stream.setInputStream(input);
+ let content = stream.readBytes(input.available());
+ stream.close();
+ input.close();
+ return content;
+}
+exports.getChromeURIContent = function deprecated_getChromeURIContent() {
+ deprecatedUsage(
+ 'getChromeURIContent is deprecated, ' +
+ 'please use require("sdk/net/url").readURI instead.'
+ );
+};
+
+/**
+ * Creates a base-64 encoded ASCII string from a string of binary data.
+ */
+exports.base64Encode = deprecateFunction(base64.encode,
+ 'base64Encode is deprecated, ' +
+ 'please use require("sdk/base64").encode instead.'
+);
+/**
+ * Decodes a string of data which has been encoded using base-64 encoding.
+ */
+exports.base64Decode = deprecateFunction(base64.decode,
+ 'base64Dencode is deprecated, ' +
+ 'please use require("sdk/base64").decode instead.'
+);
diff --git a/tools/addon-sdk-1.12/lib/sdk/io/file.js b/tools/addon-sdk-1.12/lib/sdk/io/file.js
new file mode 100644
index 0000000..735edea
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/io/file.js
@@ -0,0 +1,196 @@
+/* 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 {Cc,Ci,Cr} = require("chrome");
+const byteStreams = require("./byte-streams");
+const textStreams = require("./text-streams");
+
+// Flags passed when opening a file. See nsprpub/pr/include/prio.h.
+const OPEN_FLAGS = {
+ RDONLY: parseInt("0x01"),
+ WRONLY: parseInt("0x02"),
+ CREATE_FILE: parseInt("0x08"),
+ APPEND: parseInt("0x10"),
+ TRUNCATE: parseInt("0x20"),
+ EXCL: parseInt("0x80")
+};
+
+var dirsvc = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties);
+
+function MozFile(path) {
+ var file = Cc['@mozilla.org/file/local;1']
+ .createInstance(Ci.nsILocalFile);
+ file.initWithPath(path);
+ return file;
+}
+
+function ensureReadable(file) {
+ if (!file.isReadable())
+ throw new Error("path is not readable: " + file.path);
+}
+
+function ensureDir(file) {
+ ensureExists(file);
+ if (!file.isDirectory())
+ throw new Error("path is not a directory: " + file.path);
+}
+
+function ensureFile(file) {
+ ensureExists(file);
+ if (!file.isFile())
+ throw new Error("path is not a file: " + file.path);
+}
+
+function ensureExists(file) {
+ if (!file.exists())
+ throw friendlyError(Cr.NS_ERROR_FILE_NOT_FOUND, file.path);
+}
+
+function friendlyError(errOrResult, filename) {
+ var isResult = typeof(errOrResult) === "number";
+ var result = isResult ? errOrResult : errOrResult.result;
+ switch (result) {
+ case Cr.NS_ERROR_FILE_NOT_FOUND:
+ return new Error("path does not exist: " + filename);
+ }
+ return isResult ? new Error("XPCOM error code: " + errOrResult) : errOrResult;
+}
+
+exports.exists = function exists(filename) {
+ return MozFile(filename).exists();
+};
+
+exports.isFile = function isFile(filename) {
+ return MozFile(filename).isFile();
+};
+
+exports.read = function read(filename, mode) {
+ if (typeof(mode) !== "string")
+ mode = "";
+
+ // Ensure mode is read-only.
+ mode = /b/.test(mode) ? "b" : "";
+
+ var stream = exports.open(filename, mode);
+ try {
+ var str = stream.read();
+ }
+ finally {
+ stream.close();
+ }
+
+ return str;
+};
+
+exports.join = function join(base) {
+ if (arguments.length < 2)
+ throw new Error("need at least 2 args");
+ base = MozFile(base);
+ for (var i = 1; i < arguments.length; i++)
+ base.append(arguments[i]);
+ return base.path;
+};
+
+exports.dirname = function dirname(path) {
+ var parent = MozFile(path).parent;
+ return parent ? parent.path : "";
+};
+
+exports.basename = function basename(path) {
+ var leafName = MozFile(path).leafName;
+
+ // On Windows, leafName when the path is a volume letter and colon ("c:") is
+ // the path itself. But such a path has no basename, so we want the empty
+ // string.
+ return leafName == path ? "" : leafName;
+};
+
+exports.list = function list(path) {
+ var file = MozFile(path);
+ ensureDir(file);
+ ensureReadable(file);
+
+ var entries = file.directoryEntries;
+ var entryNames = [];
+ while(entries.hasMoreElements()) {
+ var entry = entries.getNext();
+ entry.QueryInterface(Ci.nsIFile);
+ entryNames.push(entry.leafName);
+ }
+ return entryNames;
+};
+
+exports.open = function open(filename, mode) {
+ var file = MozFile(filename);
+ if (typeof(mode) !== "string")
+ mode = "";
+
+ // File opened for write only.
+ if (/w/.test(mode)) {
+ if (file.exists())
+ ensureFile(file);
+ var stream = Cc['@mozilla.org/network/file-output-stream;1'].
+ createInstance(Ci.nsIFileOutputStream);
+ var openFlags = OPEN_FLAGS.WRONLY |
+ OPEN_FLAGS.CREATE_FILE |
+ OPEN_FLAGS.TRUNCATE;
+ var permFlags = parseInt("0644"); // u+rw go+r
+ try {
+ stream.init(file, openFlags, permFlags, 0);
+ }
+ catch (err) {
+ throw friendlyError(err, filename);
+ }
+ return /b/.test(mode) ?
+ new byteStreams.ByteWriter(stream) :
+ new textStreams.TextWriter(stream);
+ }
+
+ // File opened for read only, the default.
+ ensureFile(file);
+ stream = Cc['@mozilla.org/network/file-input-stream;1'].
+ createInstance(Ci.nsIFileInputStream);
+ try {
+ stream.init(file, OPEN_FLAGS.RDONLY, 0, 0);
+ }
+ catch (err) {
+ throw friendlyError(err, filename);
+ }
+ return /b/.test(mode) ?
+ new byteStreams.ByteReader(stream) :
+ new textStreams.TextReader(stream);
+};
+
+exports.remove = function remove(path) {
+ var file = MozFile(path);
+ ensureFile(file);
+ file.remove(false);
+};
+
+exports.mkpath = function mkpath(path) {
+ var file = MozFile(path);
+ if (!file.exists())
+ file.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755")); // u+rwx go+rx
+ else if (!file.isDirectory())
+ throw new Error("The path already exists and is not a directory: " + path);
+};
+
+exports.rmdir = function rmdir(path) {
+ var file = MozFile(path);
+ ensureDir(file);
+ try {
+ file.remove(false);
+ }
+ catch (err) {
+ // Bug 566950 explains why we're not catching a specific exception here.
+ throw new Error("The directory is not empty: " + path);
+ }
+};
diff --git a/tools/addon-sdk-1.12/lib/sdk/io/text-streams.js b/tools/addon-sdk-1.12/lib/sdk/io/text-streams.js
new file mode 100644
index 0000000..0aa469d
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/io/text-streams.js
@@ -0,0 +1,244 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et filetype=javascript
+ * 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 {Cc,Ci,Cu,components} = require("chrome");
+var NetUtil = {};
+Cu.import("resource://gre/modules/NetUtil.jsm", NetUtil);
+NetUtil = NetUtil.NetUtil;
+
+// NetUtil.asyncCopy() uses this buffer length, and since we call it, for best
+// performance we use it, too.
+const BUFFER_BYTE_LEN = 0x8000;
+const PR_UINT32_MAX = 0xffffffff;
+const DEFAULT_CHARSET = "UTF-8";
+
+exports.TextReader = TextReader;
+exports.TextWriter = TextWriter;
+
+/**
+ * An input stream that reads text from a backing stream using a given text
+ * encoding.
+ *
+ * @param inputStream
+ * The stream is backed by this nsIInputStream. It must already be
+ * opened.
+ * @param charset
+ * Text in inputStream is expected to be in this character encoding. If
+ * not given, "UTF-8" is assumed. See nsICharsetConverterManager.idl for
+ * documentation on how to determine other valid values for this.
+ */
+function TextReader(inputStream, charset) {
+ const self = this;
+ charset = checkCharset(charset);
+
+ let stream = Cc["@mozilla.org/intl/converter-input-stream;1"].
+ createInstance(Ci.nsIConverterInputStream);
+ stream.init(inputStream, charset, BUFFER_BYTE_LEN,
+ Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
+
+ let manager = new StreamManager(this, stream);
+
+ /**
+ * Reads a string from the stream. If the stream is closed, an exception is
+ * thrown.
+ *
+ * @param numChars
+ * The number of characters to read. If not given, the remainder of
+ * the stream is read.
+ * @return The string read. If the stream is already at EOS, returns the
+ * empty string.
+ */
+ this.read = function TextReader_read(numChars) {
+ manager.ensureOpened();
+
+ let readAll = false;
+ if (typeof(numChars) === "number")
+ numChars = Math.max(numChars, 0);
+ else
+ readAll = true;
+
+ let str = "";
+ let totalRead = 0;
+ let chunkRead = 1;
+
+ // Read in numChars or until EOS, whichever comes first. Note that the
+ // units here are characters, not bytes.
+ while (true) {
+ let chunk = {};
+ let toRead = readAll ?
+ PR_UINT32_MAX :
+ Math.min(numChars - totalRead, PR_UINT32_MAX);
+ if (toRead <= 0 || chunkRead <= 0)
+ break;
+
+ // The converter stream reads in at most BUFFER_BYTE_LEN bytes in a call
+ // to readString, enough to fill its byte buffer. chunkRead will be the
+ // number of characters encoded by the bytes in that buffer.
+ chunkRead = stream.readString(toRead, chunk);
+ str += chunk.value;
+ totalRead += chunkRead;
+ }
+
+ return str;
+ };
+}
+
+/**
+ * A buffered output stream that writes text to a backing stream using a given
+ * text encoding.
+ *
+ * @param outputStream
+ * The stream is backed by this nsIOutputStream. It must already be
+ * opened.
+ * @param charset
+ * Text will be written to outputStream using this character encoding.
+ * If not given, "UTF-8" is assumed. See nsICharsetConverterManager.idl
+ * for documentation on how to determine other valid values for this.
+ */
+function TextWriter(outputStream, charset) {
+ const self = this;
+ charset = checkCharset(charset);
+
+ let stream = outputStream;
+
+ // Buffer outputStream if it's not already.
+ let ioUtils = Cc["@mozilla.org/io-util;1"].getService(Ci.nsIIOUtil);
+ if (!ioUtils.outputStreamIsBuffered(outputStream)) {
+ stream = Cc["@mozilla.org/network/buffered-output-stream;1"].
+ createInstance(Ci.nsIBufferedOutputStream);
+ stream.init(outputStream, BUFFER_BYTE_LEN);
+ }
+
+ // I'd like to use nsIConverterOutputStream. But NetUtil.asyncCopy(), which
+ // we use below in writeAsync(), naturally expects its sink to be an instance
+ // of nsIOutputStream, which nsIConverterOutputStream's only implementation is
+ // not. So we use uconv and manually convert all strings before writing to
+ // outputStream.
+ let uconv = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+ createInstance(Ci.nsIScriptableUnicodeConverter);
+ uconv.charset = charset;
+
+ let manager = new StreamManager(this, stream);
+
+ /**
+ * Flushes the backing stream's buffer.
+ */
+ this.flush = function TextWriter_flush() {
+ manager.ensureOpened();
+ stream.flush();
+ };
+
+ /**
+ * Writes a string to the stream. If the stream is closed, an exception is
+ * thrown.
+ *
+ * @param str
+ * The string to write.
+ */
+ this.write = function TextWriter_write(str) {
+ manager.ensureOpened();
+ let istream = uconv.convertToInputStream(str);
+ let len = istream.available();
+ while (len > 0) {
+ stream.writeFrom(istream, len);
+ len = istream.available();
+ }
+ istream.close();
+ };
+
+ /**
+ * Writes a string on a background thread. After the write completes, the
+ * backing stream's buffer is flushed, and both the stream and the backing
+ * stream are closed, also on the background thread. If the stream is already
+ * closed, an exception is thrown immediately.
+ *
+ * @param str
+ * The string to write.
+ * @param callback
+ * An optional function. If given, it's called as callback(error) when
+ * the write completes. error is an Error object or undefined if there
+ * was no error. Inside callback, |this| is the stream object.
+ */
+ this.writeAsync = function TextWriter_writeAsync(str, callback) {
+ manager.ensureOpened();
+ let istream = uconv.convertToInputStream(str);
+ NetUtil.asyncCopy(istream, stream, function (result) {
+ let err = components.isSuccessCode(result) ? undefined :
+ new Error("An error occured while writing to the stream: " + result);
+ if (err)
+ console.error(err);
+
+ // asyncCopy() closes its output (and input) stream.
+ manager.opened = false;
+
+ if (typeof(callback) === "function") {
+ try {
+ callback.call(self, err);
+ }
+ catch (exc) {
+ console.exception(exc);
+ }
+ }
+ });
+ };
+}
+
+// This manages the lifetime of stream, a TextReader or TextWriter. It defines
+// closed and close() on stream and registers an unload listener that closes
+// rawStream if it's still opened. It also provides ensureOpened(), which
+// throws an exception if the stream is closed.
+function StreamManager(stream, rawStream) {
+ const self = this;
+ this.rawStream = rawStream;
+ this.opened = true;
+
+ /**
+ * True iff the stream is closed.
+ */
+ stream.__defineGetter__("closed", function stream_closed() {
+ return !self.opened;
+ });
+
+ /**
+ * Closes both the stream and its backing stream. If the stream is already
+ * closed, an exception is thrown. For TextWriters, this first flushes the
+ * backing stream's buffer.
+ */
+ stream.close = function stream_close() {
+ self.ensureOpened();
+ self.unload();
+ };
+
+ require("../system/unload").ensure(this);
+}
+
+StreamManager.prototype = {
+ ensureOpened: function StreamManager_ensureOpened() {
+ if (!this.opened)
+ throw new Error("The stream is closed and cannot be used.");
+ },
+ unload: function StreamManager_unload() {
+ // TextWriter.writeAsync() causes rawStream to close and therefore sets
+ // opened to false, so check that we're still opened.
+ if (this.opened) {
+ // Calling close() on both an nsIUnicharInputStream and
+ // nsIBufferedOutputStream closes their backing streams. It also forces
+ // nsIOutputStreams to flush first.
+ this.rawStream.close();
+ this.opened = false;
+ }
+ }
+};
+
+function checkCharset(charset) {
+ return typeof(charset) === "string" ? charset : DEFAULT_CHARSET;
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/keyboard/hotkeys.js b/tools/addon-sdk-1.12/lib/sdk/keyboard/hotkeys.js
new file mode 100644
index 0000000..2d963ab
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/keyboard/hotkeys.js
@@ -0,0 +1,111 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 { observer: keyboardObserver } = require("./observer");
+const { getKeyForCode, normalize, isFunctionKey,
+ MODIFIERS } = require("./utils");
+
+/**
+ * Register a global `hotkey` that executes `listener` when the key combination
+ * in `hotkey` is pressed. If more then one `listener` is registered on the same
+ * key combination only last one will be executed.
+ *
+ * @param {string} hotkey
+ * Key combination in the format of 'modifier key'.
+ *
+ * Examples:
+ *
+ * "accel s"
+ * "meta shift i"
+ * "control alt d"
+ *
+ * Modifier keynames:
+ *
+ * - **shift**: The Shift key.
+ * - **alt**: The Alt key. On the Macintosh, this is the Option key. On
+ * Macintosh this can only be used in conjunction with another modifier,
+ * since `Alt+Letter` combinations are reserved for entering special
+ * characters in text.
+ * - **meta**: The Meta key. On the Macintosh, this is the Command key.
+ * - **control**: The Control key.
+ * - **accel**: The key used for keyboard shortcuts on the user's platform,
+ * which is Control on Windows and Linux, and Command on Mac. Usually, this
+ * would be the value you would use.
+ *
+ * @param {function} listener
+ * Function to execute when the `hotkey` is executed.
+ */
+exports.register = function register(hotkey, listener) {
+ hotkey = normalize(hotkey);
+ hotkeys[hotkey] = listener;
+};
+
+/**
+ * Unregister a global `hotkey`. If passed `listener` is not the one registered
+ * for the given `hotkey`, the call to this function will be ignored.
+ *
+ * @param {string} hotkey
+ * Key combination in the format of 'modifier key'.
+ * @param {function} listener
+ * Function that will be invoked when the `hotkey` is pressed.
+ */
+exports.unregister = function unregister(hotkey, listener) {
+ hotkey = normalize(hotkey);
+ if (hotkeys[hotkey] === listener)
+ delete hotkeys[hotkey];
+};
+
+/**
+ * Map of hotkeys and associated functions.
+ */
+const hotkeys = exports.hotkeys = {};
+
+keyboardObserver.on("keydown", function onKeypress(event, window) {
+ let key, modifiers = [];
+ let isChar = "isChar" in event && event.isChar;
+ let which = "which" in event ? event.which : null;
+ let keyCode = "keyCode" in event ? event.keyCode : null;
+
+ if ("shiftKey" in event && event.shiftKey)
+ modifiers.push("shift");
+ if ("altKey" in event && event.altKey)
+ modifiers.push("alt");
+ if ("ctrlKey" in event && event.ctrlKey)
+ modifiers.push("control");
+ if ("metaKey" in event && event.metaKey)
+ modifiers.push("meta");
+
+ // If it's not a printable character then we fall back to a human readable
+ // equivalent of one of the following constants.
+ // http://mxr.mozilla.org/mozilla-central/source/dom/interfaces/events/nsIDOMKeyEvent.idl
+ key = getKeyForCode(keyCode);
+
+ // If only non-function (f1 - f24) key or only modifiers are pressed we don't
+ // have a valid combination so we return immediately (Also, sometimes
+ // `keyCode` may be one for the modifier which means we do not have a
+ // modifier).
+ if (!key || (!isFunctionKey(key) && !modifiers.length) || key in MODIFIERS)
+ return;
+
+ let combination = normalize({ key: key, modifiers: modifiers });
+ let hotkey = hotkeys[combination];
+
+ if (hotkey) {
+ try {
+ hotkey();
+ } catch (exception) {
+ console.exception(exception);
+ } finally {
+ // Work around bug 582052 by preventing the (nonexistent) default action.
+ event.preventDefault();
+ }
+ }
+});
diff --git a/tools/addon-sdk-1.12/lib/sdk/keyboard/observer.js b/tools/addon-sdk-1.12/lib/sdk/keyboard/observer.js
new file mode 100644
index 0000000..26c0a83
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/keyboard/observer.js
@@ -0,0 +1,58 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 { Trait } = require("../deprecated/light-traits");
+const { EventEmitterTrait: EventEmitter } = require("../deprecated/events");
+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,
+ /**
+ * 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);
+ }
+});
+
+// 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 each (let window in browserWindowIterator())
+ observer.observe(window);
+
+exports.observer = observer;
diff --git a/tools/addon-sdk-1.12/lib/sdk/keyboard/utils.js b/tools/addon-sdk-1.12/lib/sdk/keyboard/utils.js
new file mode 100644
index 0000000..a4a8f76
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/keyboard/utils.js
@@ -0,0 +1,190 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 runtime = require("../system/runtime");
+const { isString } = require("../lang/type");
+const array = require("../util/array");
+
+
+const SWP = "{{SEPARATOR}}";
+const SEPARATOR = "-"
+const INVALID_COMBINATION = "Hotkey key combination must contain one or more " +
+ "modifiers and only one key";
+
+// Map of modifier key mappings.
+const MODIFIERS = exports.MODIFIERS = {
+ 'accel': runtime.OS === "Darwin" ? 'meta' : 'control',
+ 'meta': 'meta',
+ 'control': 'control',
+ 'ctrl': 'control',
+ 'option': 'alt',
+ 'command': 'meta',
+ 'alt': 'alt',
+ 'shift': 'shift'
+};
+
+// Hash of key:code pairs for all the chars supported by `nsIDOMKeyEvent`.
+// This is just a copy of the `nsIDOMKeyEvent` hash with normalized names.
+// @See: http://mxr.mozilla.org/mozilla-central/source/dom/interfaces/events/nsIDOMKeyEvent.idl
+const CODES = exports.CODES = new function Codes() {
+ let nsIDOMKeyEvent = Ci.nsIDOMKeyEvent;
+ // Names that will be substituted with a shorter analogs.
+ let aliases = {
+ 'subtract': '-',
+ 'add': '+',
+ 'equals': '=',
+ 'slash': '/',
+ 'backslash': '\\',
+ 'openbracket': '[',
+ 'closebracket': ']',
+ 'quote': '\'',
+ 'backquote': '`',
+ 'period': '.',
+ 'semicolon': ';',
+ 'comma': ','
+ };
+
+ // Normalizing keys and copying values to `this` object.
+ Object.keys(nsIDOMKeyEvent).filter(function(key) {
+ // Filter out only key codes.
+ return key.indexOf('DOM_VK') === 0;
+ }).map(function(key) {
+ // Map to key:values
+ return [ key, nsIDOMKeyEvent[key] ];
+ }).map(function([key, value]) {
+ return [ key.replace('DOM_VK_', '').replace('_', '').toLowerCase(), value ];
+ }).forEach(function ([ key, value ]) {
+ this[aliases[key] || key] = value;
+ }, this);
+};
+
+// Inverted `CODES` hash of `code:key`.
+const KEYS = exports.KEYS = new function Keys() {
+ Object.keys(CODES).forEach(function(key) {
+ this[CODES[key]] = key;
+ }, this)
+}
+
+exports.getKeyForCode = function getKeyForCode(code) {
+ return (code in KEYS) && KEYS[code];
+};
+exports.getCodeForKey = function getCodeForKey(key) {
+ return (key in CODES) && CODES[key];
+};
+
+/**
+ * Utility function that takes string or JSON that defines a `hotkey` and
+ * returns normalized string version of it.
+ * @param {JSON|String} hotkey
+ * @param {String} [separator=" "]
+ * Optional string that represents separator used to concatenate keys in the
+ * given `hotkey`.
+ * @returns {String}
+ * @examples
+ *
+ * require("keyboard/hotkeys").normalize("b Shift accel");
+ * // 'control shift b' -> on windows & linux
+ * // 'meta shift b' -> on mac
+ * require("keyboard/hotkeys").normalize("alt-d-shift", "-");
+ * // 'alt shift d'
+ */
+var normalize = exports.normalize = function normalize(hotkey, separator) {
+ if (!isString(hotkey))
+ hotkey = toString(hotkey, separator);
+ return toString(toJSON(hotkey, separator), separator);
+};
+
+/*
+ * Utility function that splits a string of characters that defines a `hotkey`
+ * into modifier keys and the defining key.
+ * @param {String} hotkey
+ * @param {String} [separator=" "]
+ * Optional string that represents separator used to concatenate keys in the
+ * given `hotkey`.
+ * @returns {JSON}
+ * @examples
+ *
+ * require("keyboard/hotkeys").toJSON("accel shift b");
+ * // { key: 'b', modifiers: [ 'control', 'shift' ] } -> on windows & linux
+ * // { key: 'b', modifiers: [ 'meta', 'shift' ] } -> on mac
+ *
+ * require("keyboard/hotkeys").normalize("alt-d-shift", "-");
+ * // { key: 'd', modifiers: [ 'alt', 'shift' ] }
+ */
+var toJSON = exports.toJSON = function toJSON(hotkey, separator) {
+ separator = separator || SEPARATOR;
+ // Since default separator is `-`, combination may take form of `alt--`. To
+ // avoid misbehavior we replace `--` with `-{{SEPARATOR}}` where
+ // `{{SEPARATOR}}` can be swapped later.
+ hotkey = hotkey.toLowerCase().replace(separator + separator, separator + SWP);
+
+ let value = {};
+ let modifiers = [];
+ let keys = hotkey.split(separator);
+ keys.forEach(function(name) {
+ // If name is `SEPARATOR` than we swap it back.
+ if (name === SWP)
+ name = separator;
+ if (name in MODIFIERS) {
+ array.add(modifiers, MODIFIERS[name]);
+ } else {
+ if (!value.key)
+ value.key = name;
+ else
+ throw new TypeError(INVALID_COMBINATION);
+ }
+ });
+
+ if (!value.key)
+ throw new TypeError(INVALID_COMBINATION);
+
+ value.modifiers = modifiers.sort();
+ return value;
+};
+
+/**
+ * Utility function that takes object that defines a `hotkey` and returns
+ * string representation of it.
+ *
+ * _Please note that this function does not validates data neither it normalizes
+ * it, if you are unsure that data is well formed use `normalize` function
+ * instead.
+ *
+ * @param {JSON} hotkey
+ * @param {String} [separator=" "]
+ * Optional string that represents separator used to concatenate keys in the
+ * given `hotkey`.
+ * @returns {String}
+ * @examples
+ *
+ * require("keyboard/hotkeys").toString({
+ * key: 'b',
+ * modifiers: [ 'control', 'shift' ]
+ * }, '+');
+ * // 'control+shift+b
+ *
+ */
+var toString = exports.toString = function toString(hotkey, separator) {
+ let keys = hotkey.modifiers.slice();
+ keys.push(hotkey.key);
+ return keys.join(separator || SEPARATOR);
+};
+
+/**
+ * Utility function takes `key` name and returns `true` if it's function key
+ * (F1, ..., F24) and `false` if it's not.
+ */
+var isFunctionKey = exports.isFunctionKey = function isFunctionKey(key) {
+ var $
+ return key[0].toLowerCase() === 'f' &&
+ ($ = parseInt(key.substr(1)), 0 < $ && $ < 25);
+};
diff --git a/tools/addon-sdk-1.12/lib/sdk/l10n.js b/tools/addon-sdk-1.12/lib/sdk/l10n.js
new file mode 100644
index 0000000..40f219c
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/l10n.js
@@ -0,0 +1,83 @@
+/* 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": "stable"
+};
+
+const core = require("./l10n/core");
+const { getRulesForLocale } = require("./l10n/plural-rules");
+
+// Retrieve the plural mapping function
+let pluralMappingFunction = getRulesForLocale(core.language()) ||
+ getRulesForLocale("en");
+
+exports.get = function get(k) {
+ // For now, we only accept a "string" as first argument
+ // TODO: handle plural forms in gettext pattern
+ if (typeof k !== "string")
+ throw new Error("First argument of localization method should be a string");
+
+ // Get translation from big hashmap or default to hard coded string:
+ let localized = core.get(k) || k;
+
+ // # Simplest usecase:
+ // // String hard coded in source code:
+ // _("Hello world")
+ // // Identifier of a key stored in properties file
+ // _("helloString")
+ if (arguments.length <= 1)
+ return localized;
+
+ let args = arguments;
+
+ if (typeof localized == "object" && "other" in localized) {
+ // # Plural form:
+ // // Strings hard coded in source code:
+ // _(["One download", "%d downloads"], 10);
+ // // Identifier of a key stored in properties file
+ // _("downloadNumber", 0);
+ let n = arguments[1];
+
+ // First handle simple universal forms that may not be mandatory
+ // for each language, (i.e. not different than 'other' form,
+ // but still usefull for better phrasing)
+ // For example 0 in english is the same form than 'other'
+ // but we accept 'zero' form if specified in localization file
+ if (n === 0 && "zero" in localized)
+ localized = localized["zero"];
+ else if (n === 1 && "one" in localized)
+ localized = localized["one"];
+ else if (n === 2 && "two" in localized)
+ localized = localized["two"];
+ else {
+ let pluralForm = pluralMappingFunction(n);
+ if (pluralForm in localized)
+ localized = localized[pluralForm];
+ else // Fallback in case of error: missing plural form
+ localized = localized["other"];
+ }
+
+ // Simulate a string with one placeholder:
+ args = [null, n];
+ }
+
+ // # String with placeholders:
+ // // Strings hard coded in source code:
+ // _("Hello %s", username)
+ // // Identifier of a key stored in properties file
+ // _("helloString", username)
+ // * We supports `%1s`, `%2s`, ... pattern in order to change arguments order
+ // in translation.
+ // * In case of plural form, we has `%d` instead of `%s`.
+ let offset = 1;
+ localized = localized.replace(/%(\d*)(s|d)/g, function (v, n) {
+ let rv = args[n != "" ? n : offset];
+ offset++;
+ return rv;
+ });
+
+ return localized;
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/l10n/core.js b/tools/addon-sdk-1.12/lib/sdk/l10n/core.js
new file mode 100644
index 0000000..50472e9
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/l10n/core.js
@@ -0,0 +1,35 @@
+/* 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/. */
+
+// Following pseudo module is set by `api-utils/addon/runner` and its load
+// method needs to be called before loading `core` module. But it may have
+// failed, so that this pseudo won't be available
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+
+let hash = {}, bestMatchingLocale = null;
+try {
+ let data = require("@l10n/data");
+ hash = data.hash;
+ bestMatchingLocale = data.bestMatchingLocale;
+}
+catch(e) {}
+
+// Returns the translation for a given key, if available.
+exports.get = function get(k) {
+ return k in hash ? hash[k] : null;
+}
+
+// Returns the full length locale code: ja-JP-mac, en-US or fr
+exports.locale = function locale() {
+ return bestMatchingLocale;
+}
+// Returns the short locale code: ja, en, fr
+exports.language = function language() {
+ return bestMatchingLocale ? bestMatchingLocale.split("-")[0].toLowerCase()
+ : null;
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/l10n/html.js b/tools/addon-sdk-1.12/lib/sdk/l10n/html.js
new file mode 100644
index 0000000..1a36415
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/l10n/html.js
@@ -0,0 +1,93 @@
+/* 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/. */
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Ci } = require("chrome");
+const events = require("../system/events");
+const core = require("./core");
+
+const assetsURI = require('../self').data.url();
+
+// Taken from Gaia:
+// https://github.com/andreasgal/gaia/blob/04fde2640a7f40314643016a5a6c98bf3755f5fd/webapi.js#L1470
+function translateElement(element) {
+ element = element || document;
+
+ // check all translatable children (= w/ a `data-l10n-id' attribute)
+ var children = element.querySelectorAll('*[data-l10n-id]');
+ var elementCount = children.length;
+ for (var i = 0; i < elementCount; i++) {
+ var child = children[i];
+
+ // translate the child
+ var key = child.dataset.l10nId;
+ var data = core.get(key);
+ if (data)
+ child.textContent = data;
+ }
+}
+exports.translateElement = translateElement;
+
+function onDocumentReady2Translate(event) {
+ let document = event.target;
+ document.removeEventListener("DOMContentLoaded", onDocumentReady2Translate,
+ false);
+
+ translateElement(document);
+
+ // Finally display document when we finished replacing all text content
+ document.documentElement.style.visibility = "visible";
+}
+
+function onContentWindow(event) {
+ let document = event.subject;
+
+ // Accept only HTML documents
+ if (!(document instanceof Ci.nsIDOMHTMLDocument))
+ return;
+
+ // Bug 769483: data:URI documents instanciated with nsIDOMParser
+ // have a null `location` attribute at this time
+ if (!document.location)
+ return;
+
+ // Accept only document from this addon
+ if (document.location.href.indexOf(assetsURI) !== 0)
+ return;
+
+ // First hide content of the document in order to have content blinking
+ // between untranslated and translated states
+ // TODO: use result of bug 737003 discussion in order to avoid any conflict
+ // with document CSS
+ document.documentElement.style.visibility = "hidden";
+
+ // Wait for DOM tree to be built before applying localization
+ document.addEventListener("DOMContentLoaded", onDocumentReady2Translate,
+ false);
+}
+
+// Listen to creation of content documents in order to translate them as soon
+// as possible in their loading process
+const ON_CONTENT = "document-element-inserted";
+let enabled = false;
+function enable() {
+ if (!enabled) {
+ events.on(ON_CONTENT, onContentWindow);
+ enabled = true;
+ }
+}
+exports.enable = enable;
+
+function disable() {
+ if (enabled) {
+ events.off(ON_CONTENT, onContentWindow);
+ enabled = false;
+ }
+}
+exports.disable = disable;
+
+require("api-utils/unload").when(disable);
diff --git a/tools/addon-sdk-1.12/lib/sdk/l10n/loader.js b/tools/addon-sdk-1.12/lib/sdk/l10n/loader.js
new file mode 100644
index 0000000..9af6c5c
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/l10n/loader.js
@@ -0,0 +1,71 @@
+/* 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 { getPreferedLocales, findClosestLocale } = require("./locale");
+const { readURI } = require("../net/url");
+const { resolve } = require("../core/promise");
+
+function parseJsonURI(uri) {
+ return readURI(uri).
+ then(JSON.parse).
+ then(null, function (error) {
+ throw Error("Failed to parse locale file:\n" + uri + "\n" + error);
+ });
+}
+
+// Returns the array stored in `locales.json` manifest that list available
+// locales files
+function getAvailableLocales(rootURI) {
+ let uri = rootURI + "locales.json";
+ return parseJsonURI(uri).then(function (manifest) {
+ return "locales" in manifest &&
+ Array.isArray(manifest.locales) ?
+ manifest.locales : [];
+ });
+}
+
+// Returns URI of the best locales file to use from the XPI
+function getBestLocale(rootURI) {
+ // Read localization manifest file that contains list of available languages
+ return getAvailableLocales(rootURI).then(function (availableLocales) {
+ // Retrieve list of prefered locales to use
+ let preferedLocales = getPreferedLocales();
+
+ // Compute the most preferable locale to use by using these two lists
+ return findClosestLocale(availableLocales, preferedLocales);
+ });
+}
+
+/**
+ * Read localization files and returns a promise of data to put in `@l10n/data`
+ * pseudo module, in order to allow l10n/core to fetch it.
+ */
+exports.load = function load(rootURI) {
+ // First, search for a locale file:
+ return getBestLocale(rootURI).then(function (bestMatchingLocale) {
+ // It may be null if the addon doesn't have any locale file
+ if (!bestMatchingLocale)
+ return resolve(null);
+
+ let localeURI = rootURI + "locale/" + bestMatchingLocale + ".json";
+
+ // Locale files only contains one big JSON object that is used as
+ // an hashtable of: "key to translate" => "translated key"
+ // TODO: We are likely to change this in order to be able to overload
+ // a specific key translation. For a specific package, module or line?
+ return parseJsonURI(localeURI).then(function (json) {
+ return {
+ hash: json,
+ bestMatchingLocale: bestMatchingLocale
+ };
+ });
+ });
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/l10n/locale.js b/tools/addon-sdk-1.12/lib/sdk/l10n/locale.js
new file mode 100644
index 0000000..0e59a64
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/l10n/locale.js
@@ -0,0 +1,126 @@
+/* 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 prefs = require("../preferences/service");
+const { Cu, Cc, Ci } = require("chrome");
+const { Services } = Cu.import("resource://gre/modules/Services.jsm");
+
+
+/**
+ * Gets the currently selected locale for display.
+ * Gets all usable locale that we can use sorted by priority of relevance
+ * @return Array of locales, begins with highest priority
+ */
+const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
+const PREF_SELECTED_LOCALE = "general.useragent.locale";
+const PREF_ACCEPT_LANGUAGES = "intl.accept_languages";
+exports.getPreferedLocales = function getPreferedLocales() {
+ let locales = [];
+
+ function addLocale(locale) {
+ locale = locale.toLowerCase();
+ if (locales.indexOf(locale) === -1)
+ locales.push(locale);
+ }
+
+ // Most important locale is OS one. But we use it, only if
+ // "intl.locale.matchOS" pref is set to `true`.
+ // Currently only used for multi-locales mobile builds.
+ // http://mxr.mozilla.org/mozilla-central/source/mobile/android/installer/Makefile.in#46
+ if (prefs.get(PREF_MATCH_OS_LOCALE, false)) {
+ let localeService = Cc["@mozilla.org/intl/nslocaleservice;1"].
+ getService(Ci.nsILocaleService);
+ let osLocale = localeService.getLocaleComponentForUserAgent();
+ addLocale(osLocale);
+ }
+
+ // In some cases, mainly on Fennec and on Linux version,
+ // `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, "");
+ if (contentLocales) {
+ // This list is a string of locales seperated by commas.
+ // There is spaces after commas, so strip each item
+ for each(let locale in contentLocales.split(","))
+ addLocale(locale.replace(/(^\s+)|(\s+$)/g, ""));
+ }
+
+ // Finally, we ensure that en-US is the final fallback if it wasn't added
+ addLocale("en-US");
+
+ return locales;
+}
+
+/**
+ * Selects the closest matching locale from a list of locales.
+ *
+ * @param aLocales
+ * An array of available locales
+ * @param aMatchLocales
+ * An array of prefered locales, ordered by priority. Most wanted first.
+ * Locales have to be in lowercase.
+ * If null, uses getPreferedLocales() results
+ * @return the best match for the currently selected locale
+ *
+ * Stolen from http://mxr.mozilla.org/mozilla-central/source/toolkit/mozapps/extensions/XPIProvider.jsm
+ */
+exports.findClosestLocale = function findClosestLocale(aLocales, aMatchLocales) {
+
+ aMatchLocales = aMatchLocales || exports.getPreferedLocales();
+
+ // Holds the best matching localized resource
+ let bestmatch = null;
+ // The number of locale parts it matched with
+ let bestmatchcount = 0;
+ // The number of locale parts in the match
+ let bestpartcount = 0;
+
+ for each (let locale in aMatchLocales) {
+ let lparts = locale.split("-");
+ for each (let localized in aLocales) {
+ let found = localized.toLowerCase();
+ // Exact match is returned immediately
+ if (locale == found)
+ return localized;
+
+ let fparts = found.split("-");
+ /* If we have found a possible match and this one isn't any longer
+ then we dont need to check further. */
+ if (bestmatch && fparts.length < bestmatchcount)
+ continue;
+
+ // Count the number of parts that match
+ let maxmatchcount = Math.min(fparts.length, lparts.length);
+ let matchcount = 0;
+ while (matchcount < maxmatchcount &&
+ fparts[matchcount] == lparts[matchcount])
+ matchcount++;
+
+ /* If we matched more than the last best match or matched the same and
+ this locale is less specific than the last best match. */
+ if (matchcount > bestmatchcount ||
+ (matchcount == bestmatchcount && fparts.length < bestpartcount)) {
+ bestmatch = localized;
+ bestmatchcount = matchcount;
+ bestpartcount = fparts.length;
+ }
+ }
+ // If we found a valid match for this locale return it
+ if (bestmatch)
+ return bestmatch;
+ }
+ return null;
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/l10n/plural-rules.js b/tools/addon-sdk-1.12/lib/sdk/l10n/plural-rules.js
new file mode 100644
index 0000000..22e5b8b
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/l10n/plural-rules.js
@@ -0,0 +1,403 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file is automatically generated with /python-lib/plural-rules-generator.py
+// Fetching data from: http://unicode.org/repos/cldr/trunk/common/supplemental/plurals.xml
+
+// Mapping of short locale name == to == > rule index in following list
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const LOCALES_TO_RULES = {
+ "af": 3,
+ "ak": 4,
+ "am": 4,
+ "ar": 1,
+ "asa": 3,
+ "az": 0,
+ "be": 11,
+ "bem": 3,
+ "bez": 3,
+ "bg": 3,
+ "bh": 4,
+ "bm": 0,
+ "bn": 3,
+ "bo": 0,
+ "br": 20,
+ "brx": 3,
+ "bs": 11,
+ "ca": 3,
+ "cgg": 3,
+ "chr": 3,
+ "cs": 12,
+ "cy": 17,
+ "da": 3,
+ "de": 3,
+ "dv": 3,
+ "dz": 0,
+ "ee": 3,
+ "el": 3,
+ "en": 3,
+ "eo": 3,
+ "es": 3,
+ "et": 3,
+ "eu": 3,
+ "fa": 0,
+ "ff": 5,
+ "fi": 3,
+ "fil": 4,
+ "fo": 3,
+ "fr": 5,
+ "fur": 3,
+ "fy": 3,
+ "ga": 8,
+ "gd": 24,
+ "gl": 3,
+ "gsw": 3,
+ "gu": 3,
+ "guw": 4,
+ "gv": 23,
+ "ha": 3,
+ "haw": 3,
+ "he": 2,
+ "hi": 4,
+ "hr": 11,
+ "hu": 0,
+ "id": 0,
+ "ig": 0,
+ "ii": 0,
+ "is": 3,
+ "it": 3,
+ "iu": 7,
+ "ja": 0,
+ "jmc": 3,
+ "jv": 0,
+ "ka": 0,
+ "kab": 5,
+ "kaj": 3,
+ "kcg": 3,
+ "kde": 0,
+ "kea": 0,
+ "kk": 3,
+ "kl": 3,
+ "km": 0,
+ "kn": 0,
+ "ko": 0,
+ "ksb": 3,
+ "ksh": 21,
+ "ku": 3,
+ "kw": 7,
+ "lag": 18,
+ "lb": 3,
+ "lg": 3,
+ "ln": 4,
+ "lo": 0,
+ "lt": 10,
+ "lv": 6,
+ "mas": 3,
+ "mg": 4,
+ "mk": 16,
+ "ml": 3,
+ "mn": 3,
+ "mo": 9,
+ "mr": 3,
+ "ms": 0,
+ "mt": 15,
+ "my": 0,
+ "nah": 3,
+ "naq": 7,
+ "nb": 3,
+ "nd": 3,
+ "ne": 3,
+ "nl": 3,
+ "nn": 3,
+ "no": 3,
+ "nr": 3,
+ "nso": 4,
+ "ny": 3,
+ "nyn": 3,
+ "om": 3,
+ "or": 3,
+ "pa": 3,
+ "pap": 3,
+ "pl": 13,
+ "ps": 3,
+ "pt": 3,
+ "rm": 3,
+ "ro": 9,
+ "rof": 3,
+ "ru": 11,
+ "rwk": 3,
+ "sah": 0,
+ "saq": 3,
+ "se": 7,
+ "seh": 3,
+ "ses": 0,
+ "sg": 0,
+ "sh": 11,
+ "shi": 19,
+ "sk": 12,
+ "sl": 14,
+ "sma": 7,
+ "smi": 7,
+ "smj": 7,
+ "smn": 7,
+ "sms": 7,
+ "sn": 3,
+ "so": 3,
+ "sq": 3,
+ "sr": 11,
+ "ss": 3,
+ "ssy": 3,
+ "st": 3,
+ "sv": 3,
+ "sw": 3,
+ "syr": 3,
+ "ta": 3,
+ "te": 3,
+ "teo": 3,
+ "th": 0,
+ "ti": 4,
+ "tig": 3,
+ "tk": 3,
+ "tl": 4,
+ "tn": 3,
+ "to": 0,
+ "tr": 0,
+ "ts": 3,
+ "tzm": 22,
+ "uk": 11,
+ "ur": 3,
+ "ve": 3,
+ "vi": 0,
+ "vun": 3,
+ "wa": 4,
+ "wae": 3,
+ "wo": 0,
+ "xh": 3,
+ "xog": 3,
+ "yo": 0,
+ "zh": 0,
+ "zu": 3
+};
+
+// Utility functions for plural rules methods
+function isIn(n, list) list.indexOf(n) !== -1;
+function isBetween(n, start, end) start <= n && n <= end;
+
+// List of all plural rules methods, that maps an integer to the plural form name to use
+const RULES = {
+ "0": function (n) {
+
+ return "other"
+ },
+ "1": function (n) {
+ if ((isBetween((n % 100), 3, 10)))
+ return "few";
+ if (n == 0)
+ return "zero";
+ if ((isBetween((n % 100), 11, 99)))
+ return "many";
+ if (n == 2)
+ return "two";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "2": function (n) {
+ if (n != 0 && (n % 10) == 0)
+ return "many";
+ if (n == 2)
+ return "two";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "3": function (n) {
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "4": function (n) {
+ if ((isBetween(n, 0, 1)))
+ return "one";
+ return "other"
+ },
+ "5": function (n) {
+ if ((isBetween(n, 0, 2)) && n != 2)
+ return "one";
+ return "other"
+ },
+ "6": function (n) {
+ if (n == 0)
+ return "zero";
+ if ((n % 10) == 1 && (n % 100) != 11)
+ return "one";
+ return "other"
+ },
+ "7": function (n) {
+ if (n == 2)
+ return "two";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "8": function (n) {
+ if ((isBetween(n, 3, 6)))
+ return "few";
+ if ((isBetween(n, 7, 10)))
+ return "many";
+ if (n == 2)
+ return "two";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "9": function (n) {
+ if (n == 0 || n != 1 && (isBetween((n % 100), 1, 19)))
+ return "few";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "10": function (n) {
+ if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19)))
+ return "few";
+ if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19)))
+ return "one";
+ return "other"
+ },
+ "11": function (n) {
+ if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
+ return "few";
+ if ((n % 10) == 0 || (isBetween((n % 10), 5, 9)) || (isBetween((n % 100), 11, 14)))
+ return "many";
+ if ((n % 10) == 1 && (n % 100) != 11)
+ return "one";
+ return "other"
+ },
+ "12": function (n) {
+ if ((isBetween(n, 2, 4)))
+ return "few";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "13": function (n) {
+ if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
+ return "few";
+ if (n != 1 && (isBetween((n % 10), 0, 1)) || (isBetween((n % 10), 5, 9)) || (isBetween((n % 100), 12, 14)))
+ return "many";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "14": function (n) {
+ if ((isBetween((n % 100), 3, 4)))
+ return "few";
+ if ((n % 100) == 2)
+ return "two";
+ if ((n % 100) == 1)
+ return "one";
+ return "other"
+ },
+ "15": function (n) {
+ if (n == 0 || (isBetween((n % 100), 2, 10)))
+ return "few";
+ if ((isBetween((n % 100), 11, 19)))
+ return "many";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "16": function (n) {
+ if ((n % 10) == 1 && n != 11)
+ return "one";
+ return "other"
+ },
+ "17": function (n) {
+ if (n == 3)
+ return "few";
+ if (n == 0)
+ return "zero";
+ if (n == 6)
+ return "many";
+ if (n == 2)
+ return "two";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "18": function (n) {
+ if (n == 0)
+ return "zero";
+ if ((isBetween(n, 0, 2)) && n != 0 && n != 2)
+ return "one";
+ return "other"
+ },
+ "19": function (n) {
+ if ((isBetween(n, 2, 10)))
+ return "few";
+ if ((isBetween(n, 0, 1)))
+ return "one";
+ return "other"
+ },
+ "20": function (n) {
+ if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !(isBetween((n % 100), 10, 19) || isBetween((n % 100), 70, 79) || isBetween((n % 100), 90, 99)))
+ return "few";
+ if ((n % 1000000) == 0 && n != 0)
+ return "many";
+ if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92]))
+ return "two";
+ if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91]))
+ return "one";
+ return "other"
+ },
+ "21": function (n) {
+ if (n == 0)
+ return "zero";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "22": function (n) {
+ if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99)))
+ return "one";
+ return "other"
+ },
+ "23": function (n) {
+ if ((isBetween((n % 10), 1, 2)) || (n % 20) == 0)
+ return "one";
+ return "other"
+ },
+ "24": function (n) {
+ if ((isBetween(n, 3, 10) || isBetween(n, 13, 19)))
+ return "few";
+ if (isIn(n, [2, 12]))
+ return "two";
+ if (isIn(n, [1, 11]))
+ return "one";
+ return "other"
+ },
+};
+
+/**
+ * Return a function that gives the plural form name for a given integer
+ * for the specified `locale`
+ * let fun = getRulesForLocale('en');
+ * fun(1) -> 'one'
+ * fun(0) -> 'other'
+ * fun(1000) -> 'other'
+ */
+exports.getRulesForLocale = function getRulesForLocale(locale) {
+ let index = LOCALES_TO_RULES[locale];
+ if (!(index in RULES)) {
+ console.warn('Plural form unknown for locale "' + locale + '"');
+ return function () { return "other"; };
+ }
+ return RULES[index];
+}
+
diff --git a/tools/addon-sdk-1.12/lib/sdk/l10n/prefs.js b/tools/addon-sdk-1.12/lib/sdk/l10n/prefs.js
new file mode 100644
index 0000000..016077c
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/l10n/prefs.js
@@ -0,0 +1,44 @@
+/* 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 observers = require("../deprecated/observer-service");
+const core = require("./core");
+const { id: jetpackId} = require('../self');
+
+const OPTIONS_DISPLAYED = "addon-options-displayed";
+
+function onOptionsDisplayed(document, addonId) {
+ if (addonId !== jetpackId)
+ return;
+ let query = 'setting[data-jetpack-id="' + jetpackId + '"][pref-name], ' +
+ 'button[data-jetpack-id="' + jetpackId + '"][pref-name]';
+ let nodes = document.querySelectorAll(query);
+ for (let node of nodes) {
+ let name = node.getAttribute("pref-name");
+ if (node.tagName == "setting") {
+ let desc = core.get(name + "_description");
+ if (desc)
+ node.setAttribute("desc", desc);
+ let title = core.get(name + "_title");
+ if (title)
+ node.setAttribute("title", title);
+
+ for (let item of node.querySelectorAll("menuitem, radio")) {
+ let key = name + "_options." + item.getAttribute("label");
+ let label = core.get(key);
+ if (label)
+ item.setAttribute("label", label);
+ }
+ }
+ else if (node.tagName == "button") {
+ let label = core.get(name + "_label");
+ if (label)
+ node.setAttribute("label", label);
+ }
+ }
+}
+
+observers.add(OPTIONS_DISPLAYED, onOptionsDisplayed);
diff --git a/tools/addon-sdk-1.12/lib/sdk/lang/functional.js b/tools/addon-sdk-1.12/lib/sdk/lang/functional.js
new file mode 100644
index 0000000..3595a27
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/lang/functional.js
@@ -0,0 +1,163 @@
+/* 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/. */
+
+// Disclaimer: Most of the functions in this module implement APIs from
+// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for
+// those goes to him.
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { setTimeout } = require("../timers");
+
+/**
+ * Takes `lambda` function and returns a method. When returned method is
+ * invoked it calls wrapped `lambda` and passes `this` as a first argument
+ * and given argument as rest.
+ */
+function method(lambda) {
+ return function method() {
+ return lambda.apply(null, [this].concat(Array.slice(arguments)));
+ }
+}
+exports.method = method;
+
+/**
+ * Takes a function and returns a wrapped one instead, calling which will call
+ * original function in the next turn of event loop. This is basically utility
+ * to do `setTimeout(function() { ... }, 0)`, with a difference that returned
+ * function is reused, instead of creating a new one each time. This also allows
+ * to use this functions as event listeners.
+ */
+function defer(f) {
+ return function deferred()
+ setTimeout(invoke, 0, f, arguments, this);
+}
+exports.defer = defer;
+// Exporting `remit` alias as `defer` may conflict with promises.
+exports.remit = defer;
+
+/**
+ * Invokes `callee` by passing `params` as an arguments and `self` as `this`
+ * pseudo-variable. Returns value that is returned by a callee.
+ * @param {Function} callee
+ * Function to invoke.
+ * @param {Array} params
+ * Arguments to invoke function with.
+ * @param {Object} self
+ * Object to be passed as a `this` pseudo variable.
+ */
+function invoke(callee, params, self) callee.apply(self, params);
+exports.invoke = invoke;
+
+/**
+ * Curries a function with the arguments given.
+ *
+ * @param {Function} fn
+ * The function to curry
+ *
+ * @returns The function curried
+ */
+function curry(fn) {
+ if (typeof fn !== "function")
+ throw new TypeError(String(fn) + " is not a function");
+
+ let args = Array.slice(arguments, 1);
+
+ return function() fn.apply(this, args.concat(Array.slice(arguments)));
+}
+exports.curry = curry;
+
+/**
+ * Returns the composition of a list of functions, where each function consumes
+ * the return value of the function that follows. In math terms, composing the
+ * functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
+ * @example
+ *
+ * var greet = function(name) { return "hi: " + name; };
+ * var exclaim = function(statement) { return statement + "!"; };
+ * var welcome = compose(exclaim, greet);
+ *
+ * welcome('moe'); // => 'hi: moe!'
+ */
+function compose() {
+ let lambdas = Array.slice(arguments);
+ return function composed() {
+ let args = Array.slice(arguments), index = lambdas.length;
+ while (0 <= --index)
+ args = [ lambdas[index].apply(this, args) ];
+ return args[0];
+ };
+}
+exports.compose = compose;
+
+/*
+ * Returns the first function passed as an argument to the second,
+ * allowing you to adjust arguments, run code before and after, and
+ * conditionally execute the original function.
+ * @example
+ *
+ * var hello = function(name) { return "hello: " + name; };
+ * hello = wrap(hello, function(f) {
+ * return "before, " + f("moe") + ", after";
+ * });
+ *
+ * hello(); // => 'before, hello: moe, after'
+ */
+function wrap(f, wrapper) {
+ return function wrapped()
+ wrapper.apply(this, [ f ].concat(Array.slice(arguments)))
+};
+exports.wrap = wrap;
+
+/**
+ * Returns the same value that is used as the argument. In math: f(x) = x
+ */
+function identity(value) value
+exports.identity = identity;
+
+/**
+ * Memoizes a given function by caching the computed result. Useful for
+ * speeding up slow-running computations. If passed an optional hashFunction,
+ * it will be used to compute the hash key for storing the result, based on
+ * the arguments to the original function. The default hashFunction just uses
+ * the first argument to the memoized function as the key.
+ */
+function memoize(f, hasher) {
+ let memo = Object.create(null);
+ hasher = hasher || identity;
+ return function memoizer() {
+ let key = hasher.apply(this, arguments);
+ return key in memo ? memo[key] : (memo[key] = f.apply(this, arguments));
+ };
+}
+exports.memoize = memoize;
+
+/**
+ * Much like setTimeout, invokes function after wait milliseconds. If you pass
+ * the optional arguments, they will be forwarded on to the function when it is
+ * invoked.
+ */
+function delay(f, ms) {
+ let args = Array.slice(arguments, 2);
+ setTimeout(function(context) { return f.apply(context, args); }, ms, this);
+};
+exports.delay = delay;
+
+/**
+ * Creates a version of the function that can only be called one time. Repeated
+ * calls to the modified function will have no effect, returning the value from
+ * the original call. Useful for initialization functions, instead of having to
+ * set a boolean flag and then check it later.
+ */
+function once(f) {
+ let ran = false, cache;
+ return function() ran ? cache : (ran = true, cache = f.apply(this, arguments))
+};
+exports.once = once;
+// export cache as once will may be conflicting with event once a lot.
+exports.cache = once;
diff --git a/tools/addon-sdk-1.12/lib/sdk/lang/type.js b/tools/addon-sdk-1.12/lib/sdk/lang/type.js
new file mode 100644
index 0000000..a184edf
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/lang/type.js
@@ -0,0 +1,344 @@
+/* vim:ts=2:sts=2:sw=2:
+ * 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"
+};
+
+/**
+ * Returns `true` if `value` is `undefined`.
+ * @examples
+ * var foo; isUndefined(foo); // true
+ * isUndefined(0); // false
+ */
+function isUndefined(value) {
+ return value === undefined;
+}
+exports.isUndefined = isUndefined;
+
+/**
+ * Returns `true` if value is `null`.
+ * @examples
+ * isNull(null); // true
+ * isNull(undefined); // false
+ */
+function isNull(value) {
+ return value === null;
+}
+exports.isNull = isNull;
+
+/**
+ * Returns `true` if value is a string.
+ * @examples
+ * isString("moe"); // true
+ */
+function isString(value) {
+ return typeof value === "string";
+}
+exports.isString = isString;
+
+/**
+ * Returns `true` if `value` is a number.
+ * @examples
+ * isNumber(8.4 * 5); // true
+ */
+function isNumber(value) {
+ return typeof value === "number";
+}
+exports.isNumber = isNumber;
+
+/**
+ * Returns `true` if `value` is a `RegExp`.
+ * @examples
+ * isRegExp(/moe/); // true
+ */
+function isRegExp(value) {
+ return isObject(value) && instanceOf(value, RegExp);
+}
+exports.isRegExp = isRegExp;
+
+/**
+ * Returns true if `value` is a `Date`.
+ * @examples
+ * isDate(new Date()); // true
+ */
+function isDate(value) {
+ return isObject(value) && instanceOf(value, Date);
+}
+exports.isDate = isDate;
+
+/**
+ * Returns true if object is a Function.
+ * @examples
+ * isFunction(function foo(){}) // true
+ */
+function isFunction(value) {
+ return typeof value === "function";
+}
+exports.isFunction = isFunction;
+
+/**
+ * Returns `true` if `value` is an object (please note that `null` is considered
+ * to be an atom and not an object).
+ * @examples
+ * isObject({}) // true
+ * isObject(null) // false
+ */
+function isObject(value) {
+ return typeof value === "object" && value !== null;
+}
+exports.isObject = isObject;
+
+/**
+ * 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]";
+}
+exports.isArray = isArray;
+
+/**
+ * Returns `true` if `value` is an Arguments object.
+ * @examples
+ * (function(){ return isArguments(arguments); })(1, 2, 3); // true
+ * isArguments([1,2,3]); // false
+ */
+function isArguments(value) {
+ Object.prototype.toString.call(value) === "[object Arguments]";
+}
+exports.isArguments = isArguments;
+
+/**
+ * Returns true if it is a primitive `value`. (null, undefined, number,
+ * boolean, string)
+ * @examples
+ * isPrimitive(3) // true
+ * isPrimitive('foo') // true
+ * isPrimitive({ bar: 3 }) // false
+ */
+function isPrimitive(value) {
+ return !isFunction(value) && !isObject(value);
+}
+exports.isPrimitive = isPrimitive;
+
+/**
+ * Returns `true` if given `object` is flat (it is direct decedent of
+ * `Object.prototype` or `null`).
+ * @examples
+ * isFlat({}) // true
+ * isFlat(new Type()) // false
+ */
+function isFlat(object) {
+ return isObject(object) && (isNull(Object.getPrototypeOf(object)) ||
+ isNull(Object.getPrototypeOf(
+ Object.getPrototypeOf(object))));
+}
+exports.isFlat = isFlat;
+
+/**
+ * Returns `true` if object contains no values.
+ */
+function isEmpty(object) {
+ if (isObject(object)) {
+ for (var key in object)
+ return false;
+ return true;
+ }
+ return false;
+}
+exports.isEmpty = isEmpty;
+
+/**
+ * Returns `true` if `value` is an array / flat object containing only atomic
+ * values and other flat objects.
+ */
+function isJSON(value, visited) {
+ // Adding value to array of visited values.
+ (visited || (visited = [])).push(value);
+ // If `value` is an atom return `true` cause it's valid JSON.
+ return isPrimitive(value) ||
+ // If `value` is an array of JSON values that has not been visited
+ // yet.
+ (isArray(value) && value.every(function(element) {
+ return isJSON(element, visited);
+ })) ||
+ // If `value` is a plain object containing properties with a JSON
+ // values it's a valid JSON.
+ (isFlat(value) && Object.keys(value).every(function(key) {
+ var $ = Object.getOwnPropertyDescriptor(value, key);
+ // Check every proprety of a plain object to verify that
+ // it's neither getter nor setter, but a JSON value, that
+ // has not been visited yet.
+ return ((!isObject($.value) || !~visited.indexOf($.value)) &&
+ !('get' in $) && !('set' in $) &&
+ isJSON($.value, visited));
+ }));
+}
+exports.isJSON = function (value) {
+ return isJSON(value);
+};
+
+/**
+ * Returns if `value` is an instance of a given `Type`. This is exactly same as
+ * `value instanceof Type` with a difference that `Type` can be from a scope
+ * that has a different top level object. (Like in case where `Type` is a
+ * function from different iframe / jetpack module / sandbox).
+ */
+function instanceOf(value, Type) {
+ var isConstructorNameSame;
+ var isConstructorSourceSame;
+
+ // If `instanceof` returned `true` we know result right away.
+ var isInstanceOf = value instanceof Type;
+
+ // If `instanceof` returned `false` we do ducktype check since `Type` may be
+ // from a different sandbox. If a constructor of the `value` or a constructor
+ // of the value's prototype has same name and source we assume that it's an
+ // instance of the Type.
+ if (!isInstanceOf && value) {
+ isConstructorNameSame = value.constructor.name === Type.name;
+ isConstructorSourceSame = String(value.constructor) == String(Type);
+ isInstanceOf = (isConstructorNameSame && isConstructorSourceSame) ||
+ instanceOf(Object.getPrototypeOf(value), Type);
+ }
+ return isInstanceOf;
+}
+exports.instanceOf = instanceOf;
+
+/**
+ * Function returns textual representation of a value passed to it. Function
+ * takes additional `indent` argument that is used for indentation. Also
+ * optional `limit` argument may be passed to limit amount of detail returned.
+ * @param {Object} value
+ * @param {String} [indent=" "]
+ * @param {Number} [limit]
+ */
+function source(value, indent, limit, offset, visited) {
+ var result;
+ var names;
+ var nestingIndex;
+ var isCompact = !isUndefined(limit);
+
+ indent = indent || " ";
+ offset = (offset || "");
+ result = "";
+ visited = visited || [];
+
+ if (isUndefined(value)) {
+ result += "undefined";
+ }
+ else if (isNull(value)) {
+ result += "null";
+ }
+ else if (isString(value)) {
+ result += '"' + value + '"';
+ }
+ else if (isFunction(value)) {
+ value = String(value).split("\n");
+ if (isCompact && value.length > 2) {
+ value = value.splice(0, 2);
+ value.push("...}");
+ }
+ result += value.join("\n" + offset);
+ }
+ else if (isArray(value)) {
+ if ((nestingIndex = (visited.indexOf(value) + 1))) {
+ result = "#" + nestingIndex + "#";
+ }
+ else {
+ visited.push(value);
+
+ if (isCompact)
+ value = value.slice(0, limit);
+
+ result += "[\n";
+ result += value.map(function(value) {
+ return offset + indent + source(value, indent, limit, offset + indent,
+ visited);
+ }).join(",\n");
+ result += isCompact && value.length > limit ?
+ ",\n" + offset + "...]" : "\n" + offset + "]";
+ }
+ }
+ else if (isObject(value)) {
+ if ((nestingIndex = (visited.indexOf(value) + 1))) {
+ result = "#" + nestingIndex + "#"
+ }
+ else {
+ visited.push(value)
+
+ names = Object.keys(value);
+
+ result += "{ // " + value + "\n";
+ result += (isCompact ? names.slice(0, limit) : names).map(function(name) {
+ var _limit = isCompact ? limit - 1 : limit;
+ var descriptor = Object.getOwnPropertyDescriptor(value, name);
+ var result = offset + indent + "// ";
+ var accessor;
+ if (0 <= name.indexOf(" "))
+ name = '"' + name + '"';
+
+ if (descriptor.writable)
+ result += "writable ";
+ if (descriptor.configurable)
+ result += "configurable ";
+ if (descriptor.enumerable)
+ result += "enumerable ";
+
+ result += "\n";
+ if ("value" in descriptor) {
+ result += offset + indent + name + ": ";
+ result += source(descriptor.value, indent, _limit, indent + offset,
+ visited);
+ }
+ else {
+
+ if (descriptor.get) {
+ result += offset + indent + "get " + name + " ";
+ accessor = source(descriptor.get, indent, _limit, indent + offset,
+ visited);
+ result += accessor.substr(accessor.indexOf("{"));
+ }
+
+ if (descriptor.set) {
+ result += offset + indent + "set " + name + " ";
+ accessor = source(descriptor.set, indent, _limit, indent + offset,
+ visited);
+ result += accessor.substr(accessor.indexOf("{"));
+ }
+ }
+ return result;
+ }).join(",\n");
+
+ if (isCompact) {
+ if (names.length > limit && limit > 0) {
+ result += ",\n" + offset + indent + "//...";
+ }
+ }
+ else {
+ if (names.length)
+ result += ",";
+
+ result += "\n" + offset + indent + '"__proto__": ';
+ result += source(Object.getPrototypeOf(value), indent, 0,
+ offset + indent);
+ }
+
+ result += "\n" + offset + "}";
+ }
+ }
+ else {
+ result += String(value);
+ }
+ return result;
+}
+exports.source = function (value, indentation, limit) {
+ return source(value, indentation, limit);
+};
diff --git a/tools/addon-sdk-1.12/lib/sdk/loader/cuddlefish.js b/tools/addon-sdk-1.12/lib/sdk/loader/cuddlefish.js
new file mode 100644
index 0000000..cac05a9
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/loader/cuddlefish.js
@@ -0,0 +1,78 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* 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"
+};
+
+// 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
+*/
+
+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");
+// 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 { override } = loaderModule;
+
+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,
+ 'addon-sdk/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.
+ if (entry) {
+ let requirement = entry.requirements[id];
+ // If requirer entry is in manifest and it's requirement is not, than
+ // it has no authority to load since linker was not able to find it.
+ if (!requirement)
+ throw Error('Module: ' + requirer.id + ' located at ' + requirer.uri
+ + ' has no authority to load: ' + id, requirer.uri);
+
+ uri = requirement;
+ }
+ // If requirer is off manifest than it's a system module and we allow it
+ // to go off manifest.
+ else {
+ uri = id;
+ }
+ return uri;
+ }
+ });
+
+ let loader = loaderModule.Loader(options);
+ // Hack to allow loading from `toolkit/loader`.
+ loader.modules[loaderURI] = loaderSandbox;
+ return loader;
+}
+
+exports = override(loaderModule, {
+ Loader: CuddlefishLoader
+});
diff --git a/tools/addon-sdk-1.12/lib/sdk/loader/sandbox.js b/tools/addon-sdk-1.12/lib/sdk/loader/sandbox.js
new file mode 100644
index 0000000..3c085aa
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/loader/sandbox.js
@@ -0,0 +1,50 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 { Cc, Ci, CC, Cu } = require('chrome');
+const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
+const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1'].
+ getService(Ci.mozIJSSubScriptLoader);
+
+/**
+ * Make a new sandbox that inherits given `source`'s principals. Source can be
+ * URI string, DOMWindow or `null` for system principals.
+ */
+function sandbox(target, options) {
+ return Cu.Sandbox(target || systemPrincipal, options || {});
+}
+exports.sandbox = sandbox
+
+/**
+ * Evaluates given `source` in a given `sandbox` and returns result.
+ */
+function evaluate(sandbox, code, uri, line, version) {
+ return Cu.evalInSandbox(code, sandbox, version || '1.8', uri || '', line || 1);
+}
+exports.evaluate = evaluate;
+
+/**
+ * Evaluates code under the given `uri` in the given `sandbox`.
+ *
+ * @param {String} uri
+ * The URL pointing to the script to load.
+ * It must be a local chrome:, resource:, file: or data: URL.
+ */
+function load(sandbox, uri) {
+ if (uri.indexOf('data:') === 0) {
+ let source = uri.substr(uri.indexOf(',') + 1);
+
+ return evaluate(sandbox, decodeURIComponent(source), '1.8', uri, 0);
+ } else {
+ return scriptLoader.loadSubScript(uri, sandbox, 'UTF-8');
+ }
+}
+exports.load = load; \ No newline at end of file
diff --git a/tools/addon-sdk-1.12/lib/sdk/net/url.js b/tools/addon-sdk-1.12/lib/sdk/net/url.js
new file mode 100644
index 0000000..5992e0d
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/net/url.js
@@ -0,0 +1,108 @@
+/* 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 { Cu, components } = require("chrome");
+const { defer } = require("../core/promise");
+const { merge } = require("../util/object");
+
+const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
+
+/**
+ * Open a channel synchronously for the URI given, with an optional charset, and
+ * returns a resolved promise if succeed; rejected promise otherwise.
+ */
+function readSync(uri, charset) {
+ let { promise, resolve, reject } = defer();
+
+ try {
+ resolve(readURISync(uri, charset));
+ }
+ catch (e) {
+ reject("Failed to read: '" + uri + "' (Error Code: " + e.result + ")");
+ }
+
+ return promise;
+}
+
+/**
+ * Open a channel synchronously for the URI given, with an optional charset, and
+ * returns a promise.
+ */
+function readAsync(uri, charset) {
+ let channel = NetUtil.newChannel(uri, charset, null);
+
+ let { promise, resolve, reject } = defer();
+
+ NetUtil.asyncFetch(channel, function (stream, result) {
+ if (components.isSuccessCode(result)) {
+ let count = stream.available();
+ let data = NetUtil.readInputStreamToString(stream, count, { charset : charset });
+
+ resolve(data);
+ } else {
+ reject("Failed to read: '" + uri + "' (Error Code: " + result + ")");
+ }
+ });
+
+ return promise;
+}
+
+/**
+ * Reads a URI and returns a promise. If the `sync` option is set to `true`, the
+ * promise will be resolved synchronously.
+ *
+ * @param uri {string} The URI to read
+ * @param [options] {object} This parameter can have any or all of the following
+ * fields: `sync`, `charset`. By default the `charset` is set to 'UTF-8'.
+ *
+ * @returns {promise} The promise that will be resolved with the content of the
+ * URL given.
+ *
+ * @example
+ * let promise = readURI('resource://gre/modules/NetUtil.jsm', {
+ * sync: true,
+ * charset: 'US-ASCII'
+ });
+ */
+function readURI(uri, options) {
+ options = merge({
+ charset: "UTF-8",
+ sync: false
+ }, options);
+
+ return options.sync
+ ? readSync(uri, options.charset)
+ : readAsync(uri, options.charset);
+}
+
+exports.readURI = readURI;
+
+/**
+ * Reads a URI synchronously.
+ * This function is intentionally undocumented to favorites the `readURI` usage.
+ *
+ * @param uri {string} The URI to read
+ * @param [charset] {string} The character set to use when read the content of
+ * the `uri` given. By default is set to 'UTF-8'.
+ *
+ * @returns {string} The content of the URI given.
+ *
+ * @example
+ * let data = readURISync('resource://gre/modules/NetUtil.jsm');
+ */
+function readURISync(uri, charset) {
+ charset = typeof charset === "string" ? charset : "UTF-8";
+
+ let channel = NetUtil.newChannel(uri, charset, null);
+ let stream = channel.open();
+
+ let count = stream.available();
+ let data = NetUtil.readInputStreamToString(stream, count, { charset : charset });
+
+ stream.close();
+
+ return data;
+}
+
+exports.readURISync = readURISync;
diff --git a/tools/addon-sdk-1.12/lib/sdk/net/xhr.js b/tools/addon-sdk-1.12/lib/sdk/net/xhr.js
new file mode 100644
index 0000000..b4fc1ef
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/net/xhr.js
@@ -0,0 +1,154 @@
+/* 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 memory = require('../deprecated/memory');
+const { when: unload } = require("../system/unload");
+
+// ## Implementation Notes ##
+//
+// Making `XMLHttpRequest` objects available to Jetpack code involves a
+// few key principles universal to all low-level module implementations:
+//
+// * **Unloadability**. A Jetpack-based extension using this module can be
+// asked to unload itself at any time, e.g. because the user decides to
+// uninstall or disable the extension. This means we need to keep track of
+// all in-progress reqests and abort them on unload.
+//
+// * **Developer-Ergonomic Tracebacks**. Whenever an exception is raised
+// by a Jetpack-based extension, we want it to be logged in a
+// place that is specific to that extension--so that a developer
+// can distinguish it from an error on a web page or in another
+// extension, for instance. We also want it to be logged with a
+// full stack traceback, which the Mozilla platform doesn't usually
+// do.
+//
+// Because of this, we don't actually want to give the Mozilla
+// platform's "real" XHR implementation to clients, but instead provide
+// a simple wrapper that trivially delegates to the implementation in
+// all cases except where callbacks are involved: whenever Mozilla
+// platform code calls into the extension, such as during the XHR's
+// `onreadystatechange` callback, we want to wrap the client's callback
+// in a try-catch clause that traps any exceptions raised by the
+// callback and logs them via console.exception() instead of allowing
+// them to propagate back into Mozilla platform code.
+
+// This is a private list of all active requests, so we know what to
+// abort if we're asked to unload.
+var requests = [];
+
+// Events on XHRs that we should listen for, so we know when to remove
+// a request from our private list.
+const TERMINATE_EVENTS = ["load", "error", "abort"];
+
+// Read-only properties of XMLHttpRequest objects that we want to
+// directly delegate to.
+const READ_ONLY_PROPS = ["readyState", "responseText", "responseXML",
+ "status", "statusText"];
+
+// Methods of XMLHttpRequest that we want to directly delegate to.
+const DELEGATED_METHODS = ["abort", "getAllResponseHeaders",
+ "getResponseHeader", "overrideMimeType",
+ "send", "sendAsBinary", "setRequestHeader",
+ "open"];
+
+var getRequestCount = exports.getRequestCount = function getRequestCount() {
+ return requests.length;
+};
+
+var XMLHttpRequest = exports.XMLHttpRequest = function XMLHttpRequest() {
+ var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ // For the sake of simplicity, don't tie this request to any UI.
+ req.mozBackgroundRequest = true;
+
+ memory.track(req, "XMLHttpRequest");
+
+ this._req = req;
+ this._orsc = null;
+
+ requests.push(this);
+
+ var self = this;
+
+ this._boundCleanup = function _boundCleanup() {
+ self._cleanup();
+ };
+
+ TERMINATE_EVENTS.forEach(
+ function(name) {
+ self._req.addEventListener(name, self._boundCleanup, false);
+ });
+};
+
+XMLHttpRequest.prototype = {
+ _cleanup: function _cleanup() {
+ this.onreadystatechange = null;
+ var index = requests.indexOf(this);
+ if (index != -1) {
+ var self = this;
+ TERMINATE_EVENTS.forEach(
+ function(name) {
+ self._req.removeEventListener(name, self._boundCleanup, false);
+ });
+ requests.splice(index, 1);
+ }
+ },
+ _unload: function _unload() {
+ this._req.abort();
+ this._cleanup();
+ },
+ addEventListener: function addEventListener() {
+ throw new Error("not implemented");
+ },
+ removeEventListener: function removeEventListener() {
+ throw new Error("not implemented");
+ },
+ set upload(newValue) {
+ throw new Error("not implemented");
+ },
+ get onreadystatechange() {
+ return this._orsc;
+ },
+ set onreadystatechange(cb) {
+ this._orsc = cb;
+ if (cb) {
+ var self = this;
+ this._req.onreadystatechange = function() {
+ try {
+ self._orsc.apply(self, arguments);
+ } catch (e) {
+ console.exception(e);
+ }
+ };
+ } else
+ this._req.onreadystatechange = null;
+ }
+};
+
+READ_ONLY_PROPS.forEach(
+ function(name) {
+ XMLHttpRequest.prototype.__defineGetter__(
+ name,
+ function() {
+ return this._req[name];
+ });
+ });
+
+DELEGATED_METHODS.forEach(
+ function(name) {
+ XMLHttpRequest.prototype[name] = function() {
+ return this._req[name].apply(this._req, arguments);
+ };
+ });
+
+unload(function() {
+ requests.slice().forEach(function(request) { request._unload(); });
+});
diff --git a/tools/addon-sdk-1.12/lib/sdk/notifications.js b/tools/addon-sdk-1.12/lib/sdk/notifications.js
new file mode 100644
index 0000000..909552f
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/notifications.js
@@ -0,0 +1,83 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et filetype=javascript
+ * 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": "stable"
+};
+
+const { Cc, Ci, Cr } = require("chrome");
+const apiUtils = require("./deprecated/api-utils");
+const errors = require("./deprecated/errors");
+
+try {
+ let alertServ = Cc["@mozilla.org/alerts-service;1"].
+ getService(Ci.nsIAlertsService);
+
+ // The unit test sets this to a mock notification function.
+ var notify = alertServ.showAlertNotification.bind(alertServ);
+}
+catch (err) {
+ // An exception will be thrown if the platform doesn't provide an alert
+ // service, e.g., if Growl is not installed on OS X. In that case, use a
+ // mock notification function that just logs to the console.
+ notify = notifyUsingConsole;
+}
+
+exports.notify = function notifications_notify(options) {
+ let valOpts = validateOptions(options);
+ let clickObserver = !valOpts.onClick ? null : {
+ observe: function notificationClickObserved(subject, topic, data) {
+ if (topic === "alertclickcallback")
+ errors.catchAndLog(valOpts.onClick).call(exports, valOpts.data);
+ }
+ };
+ function notifyWithOpts(notifyFn) {
+ notifyFn(valOpts.iconURL, valOpts.title, valOpts.text, !!clickObserver,
+ valOpts.data, clickObserver);
+ }
+ try {
+ notifyWithOpts(notify);
+ }
+ catch (err if err instanceof Ci.nsIException &&
+ err.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
+ console.warn("The notification icon named by " + valOpts.iconURL +
+ " does not exist. A default icon will be used instead.");
+ delete valOpts.iconURL;
+ notifyWithOpts(notify);
+ }
+ catch (err) {
+ notifyWithOpts(notifyUsingConsole);
+ }
+};
+
+function notifyUsingConsole(iconURL, title, text) {
+ title = title ? "[" + title + "]" : "";
+ text = text || "";
+ let str = [title, text].filter(function (s) s).join(" ");
+ console.log(str);
+}
+
+function validateOptions(options) {
+ return apiUtils.validateOptions(options, {
+ data: {
+ is: ["string", "undefined"]
+ },
+ iconURL: {
+ is: ["string", "undefined"]
+ },
+ onClick: {
+ is: ["function", "undefined"]
+ },
+ text: {
+ is: ["string", "undefined"]
+ },
+ title: {
+ is: ["string", "undefined"]
+ }
+ });
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/page-mod.js b/tools/addon-sdk-1.12/lib/sdk/page-mod.js
new file mode 100644
index 0000000..8bd4e5f
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/page-mod.js
@@ -0,0 +1,380 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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": "stable"
+};
+
+const observers = require('./deprecated/observer-service');
+const { Loader, validationAttributes } = require('./content/loader');
+const { Worker } = require('./content/worker');
+const { EventEmitter } = require('./deprecated/events');
+const { List } = require('./deprecated/list');
+const { Registry } = require('./util/registry');
+const { MatchPattern } = require('./page-mod/match-pattern');
+const { validateOptions : validate } = require('./deprecated/api-utils');
+const { Cc, Ci } = require('chrome');
+const { merge } = require('./util/object');
+const { readURISync } = require('./net/url');
+const { windowIterator } = require('./deprecated/window-utils');
+const { isBrowser, getFrames } = require('./window/utils');
+const { getTabs, getTabContentWindow, getTabForContentWindow,
+ getURI: getTabURI } = require('./tabs/utils');
+const { has, hasAny } = require('./util/array');
+
+const styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"].
+ getService(Ci.nsIStyleSheetService);
+
+const USER_SHEET = styleSheetService.USER_SHEET;
+
+const io = Cc['@mozilla.org/network/io-service;1'].
+ getService(Ci.nsIIOService);
+
+// Valid values for `attachTo` option
+const VALID_ATTACHTO_OPTIONS = ['existing', 'top', 'frame'];
+
+// contentStyle* / contentScript* are sharing the same validation constraints,
+// so they can be mostly reused, except for the messages.
+const validStyleOptions = {
+ contentStyle: merge(Object.create(validationAttributes.contentScript), {
+ msg: 'The `contentStyle` option must be a string or an array of strings.'
+ }),
+ contentStyleFile: merge(Object.create(validationAttributes.contentScriptFile), {
+ msg: 'The `contentStyleFile` option must be a local URL or an array of URLs'
+ })
+};
+
+// rules registry
+const RULES = {};
+
+const Rules = EventEmitter.resolve({ toString: null }).compose(List, {
+ add: function() Array.slice(arguments).forEach(function onAdd(rule) {
+ if (this._has(rule))
+ return;
+ // registering rule to the rules registry
+ if (!(rule in RULES))
+ RULES[rule] = new MatchPattern(rule);
+ this._add(rule);
+ this._emit('add', rule);
+ }.bind(this)),
+ remove: function() Array.slice(arguments).forEach(function onRemove(rule) {
+ if (!this._has(rule))
+ return;
+ this._remove(rule);
+ this._emit('remove', rule);
+ }.bind(this)),
+});
+
+/**
+ * PageMod constructor (exported below).
+ * @constructor
+ */
+const PageMod = Loader.compose(EventEmitter, {
+ on: EventEmitter.required,
+ _listeners: EventEmitter.required,
+ attachTo: [],
+ contentScript: Loader.required,
+ contentScriptFile: Loader.required,
+ contentScriptWhen: Loader.required,
+ contentScriptOptions: Loader.required,
+ include: null,
+ constructor: function PageMod(options) {
+ this._onContent = this._onContent.bind(this);
+ options = options || {};
+
+ let { contentStyle, contentStyleFile } = validate(options, validStyleOptions);
+
+ if ('contentScript' in options)
+ this.contentScript = options.contentScript;
+ if ('contentScriptFile' in options)
+ this.contentScriptFile = options.contentScriptFile;
+ if ('contentScriptOptions' in options)
+ this.contentScriptOptions = options.contentScriptOptions;
+ if ('contentScriptWhen' in options)
+ this.contentScriptWhen = options.contentScriptWhen;
+ if ('onAttach' in options)
+ this.on('attach', options.onAttach);
+ if ('onError' in options)
+ this.on('error', options.onError);
+ if ('attachTo' in options) {
+ if (typeof options.attachTo == 'string')
+ this.attachTo = [options.attachTo];
+ else if (Array.isArray(options.attachTo))
+ this.attachTo = options.attachTo;
+ else
+ throw new Error('The `attachTo` option must be a string or an array ' +
+ 'of strings.');
+
+ let isValidAttachToItem = function isValidAttachToItem(item) {
+ return typeof item === 'string' &&
+ VALID_ATTACHTO_OPTIONS.indexOf(item) !== -1;
+ }
+ if (!this.attachTo.every(isValidAttachToItem))
+ throw new Error('The `attachTo` option valid accept only following ' +
+ 'values: '+ VALID_ATTACHTO_OPTIONS.join(', '));
+ if (!hasAny(this.attachTo, ["top", "frame"]))
+ throw new Error('The `attachTo` option must always contain at least' +
+ ' `top` or `frame` value');
+ }
+ else {
+ this.attachTo = ["top", "frame"];
+ }
+
+ let include = options.include;
+ let rules = this.include = Rules();
+ rules.on('add', this._onRuleAdd = this._onRuleAdd.bind(this));
+ rules.on('remove', this._onRuleRemove = this._onRuleRemove.bind(this));
+
+ if (Array.isArray(include))
+ rules.add.apply(null, include);
+ else
+ rules.add(include);
+
+ let styleRules = "";
+
+ if (contentStyleFile)
+ styleRules = [].concat(contentStyleFile).map(readURISync).join("");
+
+ if (contentStyle)
+ styleRules += [].concat(contentStyle).join("");
+
+ if (styleRules) {
+ this._onRuleUpdate = this._onRuleUpdate.bind(this);
+
+ this._styleRules = styleRules;
+
+ this._registerStyleSheet();
+ rules.on('add', this._onRuleUpdate);
+ rules.on('remove', this._onRuleUpdate);
+ }
+
+ this.on('error', this._onUncaughtError = this._onUncaughtError.bind(this));
+ pageModManager.add(this._public);
+
+ this._loadingWindows = [];
+
+ // `_applyOnExistingDocuments` has to be called after `pageModManager.add()`
+ // otherwise its calls to `_onContent` method won't do anything.
+ if ('attachTo' in options && has(options.attachTo, 'existing'))
+ this._applyOnExistingDocuments();
+ },
+
+ destroy: function destroy() {
+
+ this._unregisterStyleSheet();
+
+ this.include.removeListener('add', this._onRuleUpdate);
+ this.include.removeListener('remove', this._onRuleUpdate);
+
+ for each (let rule in this.include)
+ this.include.remove(rule);
+ pageModManager.remove(this._public);
+ this._loadingWindows = [];
+
+ },
+
+ _loadingWindows: [],
+
+ _applyOnExistingDocuments: function _applyOnExistingDocuments() {
+ let mod = this;
+ // Returns true if the tab match one rule
+ function isMatchingURI(uri) {
+ // Use Array.some as `include` isn't a native array
+ return Array.some(mod.include, function (rule) {
+ return RULES[rule].test(uri);
+ });
+ }
+ let tabs = getAllTabs().filter(function (tab) {
+ return isMatchingURI(getTabURI(tab));
+ });
+
+ tabs.forEach(function (tab) {
+ // Fake a newly created document
+ let window = getTabContentWindow(tab);
+ if (has(mod.attachTo, "top"))
+ mod._onContent(window);
+ if (has(mod.attachTo, "frame"))
+ getFrames(window).forEach(mod._onContent);
+ });
+ },
+
+ _onContent: function _onContent(window) {
+ // not registered yet
+ if (!pageModManager.has(this))
+ return;
+
+ let isTopDocument = window.top === window;
+ // Is a top level document and `top` is not set, ignore
+ if (isTopDocument && !has(this.attachTo, "top"))
+ return;
+ // Is a frame document and `frame` is not set, ignore
+ if (!isTopDocument && !has(this.attachTo, "frame"))
+ return;
+
+ // Immediatly evaluate content script if the document state is already
+ // matching contentScriptWhen expectations
+ let state = window.document.readyState;
+ if ('start' === this.contentScriptWhen ||
+ // Is `load` event already dispatched?
+ 'complete' === state ||
+ // Is DOMContentLoaded already dispatched and waiting for it?
+ ('ready' === this.contentScriptWhen && state === 'interactive') ) {
+ this._createWorker(window);
+ return;
+ }
+
+ let eventName = 'end' == this.contentScriptWhen ? 'load' : 'DOMContentLoaded';
+ let self = this;
+ window.addEventListener(eventName, function onReady(event) {
+ if (event.target.defaultView != window)
+ return;
+ window.removeEventListener(eventName, onReady, true);
+
+ self._createWorker(window);
+ }, true);
+ },
+ _createWorker: function _createWorker(window) {
+ let worker = Worker({
+ window: window,
+ contentScript: this.contentScript,
+ contentScriptFile: this.contentScriptFile,
+ contentScriptOptions: this.contentScriptOptions,
+ onError: this._onUncaughtError
+ });
+ this._emit('attach', worker);
+ let self = this;
+ worker.once('detach', function detach() {
+ worker.destroy();
+ });
+ },
+ _onRuleAdd: function _onRuleAdd(url) {
+ pageModManager.on(url, this._onContent);
+ },
+ _onRuleRemove: function _onRuleRemove(url) {
+ pageModManager.off(url, this._onContent);
+ },
+ _onUncaughtError: function _onUncaughtError(e) {
+ if (this._listeners('error').length == 1)
+ console.exception(e);
+ },
+ _onRuleUpdate: function _onRuleUpdate(){
+ this._registerStyleSheet();
+ },
+
+ _registerStyleSheet : function _registerStyleSheet() {
+ let rules = this.include;
+ let styleRules = this._styleRules;
+
+ let documentRules = [];
+
+ this._unregisterStyleSheet();
+
+ for each (let rule in rules) {
+ let pattern = RULES[rule];
+
+ if (!pattern)
+ continue;
+
+ if (pattern.regexp)
+ documentRules.push("regexp(\"" + pattern.regexp.source + "\")");
+ else if (pattern.exactURL)
+ documentRules.push("url(" + pattern.exactURL + ")");
+ else if (pattern.domain)
+ documentRules.push("domain(" + pattern.domain + ")");
+ else if (pattern.urlPrefix)
+ documentRules.push("url-prefix(" + pattern.urlPrefix + ")");
+ else if (pattern.anyWebPage) {
+ documentRules.push("regexp(\"^(https?|ftp)://.*?\")");
+ break;
+ }
+ }
+
+ let uri = "data:text/css;charset=utf-8,";
+ if (documentRules.length > 0)
+ uri += encodeURIComponent("@-moz-document " +
+ documentRules.join(",") + " {" + styleRules + "}");
+ else
+ uri += encodeURIComponent(styleRules);
+
+ this._registeredStyleURI = io.newURI(uri, null, null);
+
+ styleSheetService.loadAndRegisterSheet(
+ this._registeredStyleURI,
+ USER_SHEET
+ );
+ },
+
+ _unregisterStyleSheet : function () {
+ let uri = this._registeredStyleURI;
+
+ if (uri && styleSheetService.sheetRegistered(uri, USER_SHEET))
+ styleSheetService.unregisterSheet(uri, USER_SHEET);
+
+ this._registeredStyleURI = null;
+ }
+});
+exports.PageMod = function(options) PageMod(options)
+exports.PageMod.prototype = PageMod.prototype;
+
+const PageModManager = Registry.resolve({
+ constructor: '_init',
+ _destructor: '_registryDestructor'
+}).compose({
+ constructor: function PageModRegistry(constructor) {
+ this._init(PageMod);
+ observers.add(
+ 'document-element-inserted',
+ this._onContentWindow = this._onContentWindow.bind(this)
+ );
+ },
+ _destructor: function _destructor() {
+ observers.remove('document-element-inserted', this._onContentWindow);
+ this._removeAllListeners();
+ for (let rule in RULES) {
+ delete RULES[rule];
+ }
+
+ // We need to do some cleaning er PageMods, like unregistering any
+ // `contentStyle*`
+ this._registry.forEach(function(pageMod) {
+ pageMod.destroy();
+ });
+
+ this._registryDestructor();
+ },
+ _onContentWindow: function _onContentWindow(document) {
+ let window = document.defaultView;
+ // XML documents don't have windows, and we don't yet support them.
+ if (!window)
+ return;
+ // We apply only on documents in tabs of Firefox
+ if (!getTabForContentWindow(window))
+ return;
+
+ for (let rule in RULES)
+ if (RULES[rule].test(document.URL))
+ this._emit(rule, window);
+ },
+ off: function off(topic, listener) {
+ this.removeListener(topic, listener);
+ if (!this._listeners(topic).length)
+ delete RULES[topic];
+ }
+});
+const pageModManager = PageModManager();
+
+// Returns all tabs on all currently opened windows
+function getAllTabs() {
+ let tabs = [];
+ // Iterate over all chrome windows
+ for (let window in windowIterator()) {
+ if (!isBrowser(window))
+ continue;
+ tabs = tabs.concat(getTabs(window));
+ }
+ return tabs;
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/page-mod/match-pattern.js b/tools/addon-sdk-1.12/lib/sdk/page-mod/match-pattern.js
new file mode 100644
index 0000000..5c42e66
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/page-mod/match-pattern.js
@@ -0,0 +1,108 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { URL } = require("../url");
+
+exports.MatchPattern = MatchPattern;
+
+function MatchPattern(pattern) {
+ if (typeof pattern.test == "function") {
+
+ // For compatibility with -moz-document rules, we require the RegExp's
+ // global, ignoreCase, and multiline flags to be set to false.
+ if (pattern.global) {
+ throw new Error("A RegExp match pattern cannot be set to `global` " +
+ "(i.e. //g).");
+ }
+ if (pattern.ignoreCase) {
+ throw new Error("A RegExp match pattern cannot be set to `ignoreCase` " +
+ "(i.e. //i).");
+ }
+ if (pattern.multiline) {
+ throw new Error("A RegExp match pattern cannot be set to `multiline` " +
+ "(i.e. //m).");
+ }
+
+ this.regexp = pattern;
+ }
+ else {
+ let firstWildcardPosition = pattern.indexOf("*");
+ let lastWildcardPosition = pattern.lastIndexOf("*");
+ if (firstWildcardPosition != lastWildcardPosition)
+ throw new Error("There can be at most one '*' character in a wildcard.");
+
+ if (firstWildcardPosition == 0) {
+ if (pattern.length == 1)
+ this.anyWebPage = true;
+ else if (pattern[1] != ".")
+ throw new Error("Expected a *.<domain name> string, got: " + pattern);
+ else
+ this.domain = pattern.substr(2);
+ }
+ else {
+ if (pattern.indexOf(":") == -1) {
+ throw new Error("When not using *.example.org wildcard, the string " +
+ "supplied is expected to be either an exact URL to " +
+ "match or a URL prefix. The provided string ('" +
+ pattern + "') is unlikely to match any pages.");
+ }
+
+ if (firstWildcardPosition == -1)
+ this.exactURL = pattern;
+ else if (firstWildcardPosition == pattern.length - 1)
+ this.urlPrefix = pattern.substr(0, pattern.length - 1);
+ else {
+ throw new Error("The provided wildcard ('" + pattern + "') has a '*' " +
+ "in an unexpected position. It is expected to be the " +
+ "first or the last character in the wildcard.");
+ }
+ }
+ }
+}
+
+MatchPattern.prototype = {
+
+ test: function MatchPattern_test(urlStr) {
+ try {
+ var url = URL(urlStr);
+ }
+ catch (err) {
+ return false;
+ }
+
+ // Test the URL against a RegExp pattern. For compatibility with
+ // -moz-document rules, we require the RegExp to match the entire URL,
+ // so we not only test for a match, we also make sure the matched string
+ // is the entire URL string.
+ //
+ // Assuming most URLs don't match most match patterns, we call `test` for
+ // speed when determining whether or not the URL matches, then call `exec`
+ // for the small subset that match to make sure the entire URL matches.
+ //
+ if (this.regexp && this.regexp.test(urlStr) &&
+ this.regexp.exec(urlStr)[0] == urlStr)
+ return true;
+
+ if (this.anyWebPage && /^(https?|ftp)$/.test(url.scheme))
+ return true;
+ if (this.exactURL && this.exactURL == urlStr)
+ return true;
+ if (this.domain && url.host &&
+ url.host.slice(-this.domain.length) == this.domain)
+ return true;
+ if (this.urlPrefix && 0 == urlStr.indexOf(this.urlPrefix))
+ return true;
+
+ return false;
+ }
+
+};
diff --git a/tools/addon-sdk-1.12/lib/sdk/page-worker.js b/tools/addon-sdk-1.12/lib/sdk/page-worker.js
new file mode 100644
index 0000000..f667e4d
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/page-worker.js
@@ -0,0 +1,71 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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": "stable"
+};
+
+const { Symbiont } = require("./content/symbiont");
+const { Trait } = require("./deprecated/traits");
+
+if (!require("./system/xul-app").isOneOf(["Firefox", "Thunderbird"])) {
+ throw new Error([
+ "The page-worker module currently supports only Firefox and Thunderbird. ",
+ "In the future, we would like it to support other applications, however. ",
+ "Please see https://bugzilla.mozilla.org/show_bug.cgi?id=546740 for more ",
+ "information."
+ ].join(""));
+}
+
+const Page = Trait.compose(
+ Symbiont.resolve({
+ constructor: '_initSymbiont'
+ }),
+ {
+ _frame: Trait.required,
+ _initFrame: Trait.required,
+ postMessage: Symbiont.required,
+ on: Symbiont.required,
+ destroy: Symbiont.required,
+
+ constructor: function Page(options) {
+ options = options || {};
+
+ this.contentURL = 'contentURL' in options ? options.contentURL
+ : 'about:blank';
+ if ('contentScriptWhen' in options)
+ this.contentScriptWhen = options.contentScriptWhen;
+ 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 ('allow' in options)
+ this.allow = options.allow;
+ if ('onError' in options)
+ this.on('error', options.onError);
+ if ('onMessage' in options)
+ this.on('message', options.onMessage);
+
+ this.on('propertyChange', this._onChange.bind(this));
+
+ this._initSymbiont();
+ },
+
+ _onChange: function _onChange(e) {
+ if ('contentURL' in e && this._frame) {
+ // Cleanup the worker before injecting the content script in the new
+ // document
+ this._workerCleanup();
+ this._initFrame(this._frame);
+ }
+ }
+ }
+);
+exports.Page = function(options) Page(options);
+exports.Page.prototype = Page.prototype;
diff --git a/tools/addon-sdk-1.12/lib/sdk/panel.js b/tools/addon-sdk-1.12/lib/sdk/panel.js
new file mode 100644
index 0000000..3ce6284
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/panel.js
@@ -0,0 +1,403 @@
+/* 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": "stable"
+};
+
+if (!require("./system/xul-app").is("Firefox")) {
+ throw new Error([
+ "The panel module currently supports only Firefox. In the future ",
+ "we would like it to support other applications, however. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps ",
+ "for more information."
+ ].join(""));
+}
+
+const { Cc, Ci } = require("chrome");
+
+const { validateOptions: valid } = require('./deprecated/api-utils');
+const { Symbiont } = require('./content/content');
+const { EventEmitter } = require('./deprecated/events');
+const timer = require('./timers');
+const runtime = require('./system/runtime');
+const { getMostRecentBrowserWindow } = require('./window/utils');
+
+const windowMediator = Cc['@mozilla.org/appshell/window-mediator;1'].
+ getService(Ci.nsIWindowMediator);
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ ON_SHOW = 'popupshown',
+ ON_HIDE = 'popuphidden',
+ validNumber = { is: ['number', 'undefined', 'null'] };
+
+/**
+ * Emits show and hide events.
+ */
+const Panel = Symbiont.resolve({
+ constructor: '_init',
+ _onInit: '_onSymbiontInit',
+ destroy: '_symbiontDestructor',
+ _documentUnload: '_workerDocumentUnload'
+}).compose({
+ _frame: Symbiont.required,
+ _init: Symbiont.required,
+ _onSymbiontInit: Symbiont.required,
+ _symbiontDestructor: Symbiont.required,
+ _emit: Symbiont.required,
+ on: Symbiont.required,
+ removeListener: Symbiont.required,
+
+ _inited: false,
+
+ /**
+ * If set to `true` frame loaders between xul panel frame and
+ * hidden frame are swapped. If set to `false` frame loaders are
+ * set back to normal. Setting the value that was already set will
+ * have no effect.
+ */
+ set _frameLoadersSwapped(value) {
+ if (this.__frameLoadersSwapped == value) return;
+ this._frame.QueryInterface(Ci.nsIFrameLoaderOwner)
+ .swapFrameLoaders(this._viewFrame);
+ this.__frameLoadersSwapped = value;
+ },
+ __frameLoadersSwapped: false,
+
+ constructor: function Panel(options) {
+ this._onShow = this._onShow.bind(this);
+ this._onHide = this._onHide.bind(this);
+ this.on('inited', this._onSymbiontInit.bind(this));
+ this.on('propertyChange', this._onChange.bind(this));
+
+ options = options || {};
+ if ('onShow' in options)
+ this.on('show', options.onShow);
+ if ('onHide' in options)
+ this.on('hide', options.onHide);
+ if ('width' in options)
+ this.width = options.width;
+ if ('height' in options)
+ this.height = options.height;
+ if ('contentURL' in options)
+ this.contentURL = options.contentURL;
+
+ this._init(options);
+ },
+ _destructor: function _destructor() {
+ this.hide();
+ this._removeAllListeners('show');
+ this._removeAllListeners('hide');
+ this._removeAllListeners('propertyChange');
+ this._removeAllListeners('inited');
+ // defer cleanup to be performed after panel gets hidden
+ this._xulPanel = null;
+ this._symbiontDestructor(this);
+ this._removeAllListeners();
+ },
+ destroy: function destroy() {
+ this._destructor();
+ },
+ /* Public API: Panel.width */
+ get width() this._width,
+ set width(value)
+ this._width = valid({ $: value }, { $: validNumber }).$ || this._width,
+ _width: 320,
+ /* Public API: Panel.height */
+ get height() this._height,
+ set height(value)
+ this._height = valid({ $: value }, { $: validNumber }).$ || this._height,
+ _height: 240,
+
+ /* Public API: Panel.isShowing */
+ get isShowing() !!this._xulPanel && this._xulPanel.state == "open",
+
+ /* Public API: Panel.show */
+ show: function show(anchor) {
+ anchor = anchor || null;
+ let document = getWindow(anchor).document;
+ let xulPanel = this._xulPanel;
+ if (!xulPanel) {
+ xulPanel = this._xulPanel = document.createElementNS(XUL_NS, 'panel');
+ xulPanel.setAttribute("type", "arrow");
+
+ // One anonymous node has a big padding that doesn't work well with
+ // Jetpack, as we would like to display an iframe that completely fills
+ // the panel.
+ // -> Use a XBL wrapper with inner stylesheet to remove this padding.
+ let css = ".panel-inner-arrowcontent, .panel-arrowcontent {padding: 0;}";
+ let originalXBL = "chrome://global/content/bindings/popup.xml#arrowpanel";
+ let binding =
+ '<bindings xmlns="http://www.mozilla.org/xbl">' +
+ '<binding id="id" extends="' + originalXBL + '">' +
+ '<resources>' +
+ '<stylesheet src="data:text/css;charset=utf-8,' +
+ document.defaultView.encodeURIComponent(css) + '"/>' +
+ '</resources>' +
+ '</binding>' +
+ '</bindings>';
+ xulPanel.style.MozBinding = 'url("data:text/xml;charset=utf-8,' +
+ document.defaultView.encodeURIComponent(binding) + '")';
+
+ let frame = document.createElementNS(XUL_NS, 'iframe');
+ frame.setAttribute('type', 'content');
+ frame.setAttribute('flex', '1');
+ frame.setAttribute('transparent', 'transparent');
+ if (runtime.OS === "Darwin") {
+ frame.style.borderRadius = "6px";
+ frame.style.padding = "1px";
+ }
+
+ // Load an empty document in order to have an immediatly loaded iframe,
+ // so swapFrameLoaders is going to work without having to wait for load.
+ frame.setAttribute("src","data:;charset=utf-8,");
+
+ xulPanel.appendChild(frame);
+ document.getElementById("mainPopupSet").appendChild(xulPanel);
+ }
+ let { width, height } = this, x, y, position;
+
+ if (!anchor) {
+ // Open the popup in the middle of the window.
+ x = document.documentElement.clientWidth / 2 - width / 2;
+ y = document.documentElement.clientHeight / 2 - height / 2;
+ position = null;
+ }
+ else {
+ // Open the popup by the anchor.
+ let rect = anchor.getBoundingClientRect();
+
+ let window = anchor.ownerDocument.defaultView;
+
+ let zoom = window.mozScreenPixelsPerCSSPixel;
+ let screenX = rect.left + window.mozInnerScreenX * zoom;
+ let screenY = rect.top + window.mozInnerScreenY * zoom;
+
+ // Set up the vertical position of the popup relative to the anchor
+ // (always display the arrow on anchor center)
+ let horizontal, vertical;
+ if (screenY > window.screen.availHeight / 2 + height)
+ vertical = "top";
+ else
+ vertical = "bottom";
+
+ if (screenY > window.screen.availWidth / 2 + width)
+ horizontal = "left";
+ else
+ horizontal = "right";
+
+ let verticalInverse = vertical == "top" ? "bottom" : "top";
+ position = vertical + "center " + verticalInverse + horizontal;
+
+ // Allow panel to flip itself if the panel can't be displayed at the
+ // specified position (useful if we compute a bad position or if the
+ // user moves the window and panel remains visible)
+ xulPanel.setAttribute("flip","both");
+ }
+
+ // Resize the iframe instead of using panel.sizeTo
+ // because sizeTo doesn't work with arrow panels
+ xulPanel.firstChild.style.width = width + "px";
+ xulPanel.firstChild.style.height = height + "px";
+
+ // Wait for the XBL binding to be constructed
+ function waitForBinding() {
+ if (!xulPanel.openPopup) {
+ timer.setTimeout(waitForBinding, 50);
+ return;
+ }
+ xulPanel.openPopup(anchor, position, x, y);
+ }
+ waitForBinding();
+
+ return this._public;
+ },
+ /* Public API: Panel.hide */
+ hide: function hide() {
+ // The popuphiding handler takes care of swapping back the frame loaders
+ // and removing the XUL panel from the application window, we just have to
+ // trigger it by hiding the popup.
+ // XXX Sometimes I get "TypeError: xulPanel.hidePopup is not a function"
+ // when quitting the host application while a panel is visible. To suppress
+ // them, this now checks for "hidePopup" in xulPanel before calling it.
+ // It's not clear if there's an actual issue or the error is just normal.
+ let xulPanel = this._xulPanel;
+ if (xulPanel && "hidePopup" in xulPanel)
+ xulPanel.hidePopup();
+ return this._public;
+ },
+
+ /* Public API: Panel.resize */
+ resize: function resize(width, height) {
+ this.width = width;
+ this.height = height;
+ // Resize the iframe instead of using panel.sizeTo
+ // because sizeTo doesn't work with arrow panels
+ let xulPanel = this._xulPanel;
+ if (xulPanel) {
+ xulPanel.firstChild.style.width = width + "px";
+ xulPanel.firstChild.style.height = height + "px";
+ }
+ },
+
+ // While the panel is visible, this is the XUL <panel> we use to display it.
+ // Otherwise, it's null.
+ get _xulPanel() this.__xulPanel,
+ set _xulPanel(value) {
+ let xulPanel = this.__xulPanel;
+ if (value === xulPanel) return;
+ if (xulPanel) {
+ xulPanel.removeEventListener(ON_HIDE, this._onHide, false);
+ xulPanel.removeEventListener(ON_SHOW, this._onShow, false);
+ xulPanel.parentNode.removeChild(xulPanel);
+ }
+ if (value) {
+ value.addEventListener(ON_HIDE, this._onHide, false);
+ value.addEventListener(ON_SHOW, this._onShow, false);
+ }
+ this.__xulPanel = value;
+ },
+ __xulPanel: null,
+ get _viewFrame() this.__xulPanel.children[0],
+ /**
+ * When the XUL panel becomes hidden, we swap frame loaders back to move
+ * the content of the panel to the hidden frame & remove panel element.
+ */
+ _onHide: function _onHide() {
+ try {
+ this._frameLoadersSwapped = false;
+ this._xulPanel = null;
+ this._emit('hide');
+ } catch(e) {
+ this._emit('error', e);
+ }
+ },
+
+ /**
+ * Retrieve computed text color style in order to apply to the iframe
+ * document. As MacOS background is dark gray, we need to use skin's
+ * text color.
+ */
+ _applyStyleToDocument: function _applyStyleToDocument() {
+ try {
+ let win = this._xulPanel.ownerDocument.defaultView;
+ let node = win.document.getAnonymousElementByAttribute(
+ this._xulPanel, "class", "panel-arrowcontent");
+ if (!node) {
+ // Before bug 764755, anonymous content was different:
+ // TODO: Remove this when targeting FF16+
+ node = win.document.getAnonymousElementByAttribute(
+ this._xulPanel, "class", "panel-inner-arrowcontent");
+ }
+ let textColor = win.getComputedStyle(node).getPropertyValue("color");
+ let doc = this._xulPanel.firstChild.contentDocument;
+ let style = doc.createElement("style");
+ style.textContent = "body { color: " + textColor + "; }";
+ let container = doc.head ? doc.head : doc.documentElement;
+
+ if (container.firstChild)
+ container.insertBefore(style, container.firstChild);
+ else
+ container.appendChild(style);
+ }
+ catch(e) {
+ console.error("Unable to apply panel style");
+ console.exception(e);
+ }
+ },
+
+ /**
+ * When the XUL panel becomes shown, we swap frame loaders between panel
+ * frame and hidden frame to preserve state of the content dom.
+ */
+ _onShow: function _onShow() {
+ try {
+ if (!this._inited) { // defer if not initialized yet
+ this.on('inited', this._onShow.bind(this));
+ } else {
+ this._frameLoadersSwapped = true;
+ this._applyStyleToDocument();
+ this._emit('show');
+ }
+ } catch(e) {
+ this._emit('error', e);
+ }
+ },
+ /**
+ * Notification that panel was fully initialized.
+ */
+ _onInit: function _onInit() {
+ this._inited = true;
+
+ // Avoid panel document from resizing the browser window
+ // New platform capability added through bug 635673
+ if ("allowWindowControl" in this._frame.docShell)
+ this._frame.docShell.allowWindowControl = false;
+
+ // perform all deferred tasks like initSymbiont, show, hide ...
+ // TODO: We're publicly exposing a private event here; this
+ // 'inited' event should really be made private, somehow.
+ this._emit('inited');
+ },
+
+ // Catch document unload event in order to rebind load event listener with
+ // Symbiont._initFrame if Worker._documentUnload destroyed the worker
+ _documentUnload: function(subject, topic, data) {
+ if (this._workerDocumentUnload(subject, topic, data)) {
+ this._initFrame(this._frame);
+ return true;
+ }
+ return false;
+ },
+
+ _onChange: function _onChange(e) {
+ if ('contentURL' in e && this._frame) {
+ // Cleanup the worker before injecting the content script in the new
+ // document
+ this._workerCleanup();
+ this._initFrame(this._frame);
+ }
+ }
+});
+exports.Panel = function(options) Panel(options)
+exports.Panel.prototype = Panel.prototype;
+
+function getWindow(anchor) {
+ let window;
+
+ if (anchor) {
+ let anchorWindow = anchor.ownerDocument.defaultView.top;
+ let anchorDocument = anchorWindow.document;
+
+ let enumerator = windowMediator.getEnumerator("navigator:browser");
+ while (enumerator.hasMoreElements()) {
+ let enumWindow = enumerator.getNext();
+
+ // 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.
+ let browser = enumWindow.gBrowser.getBrowserForDocument(anchorDocument);
+ if (browser) {
+ window = enumWindow;
+ break;
+ }
+
+ // 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();
+
+ return window;
+}
+
diff --git a/tools/addon-sdk-1.12/lib/sdk/passwords.js b/tools/addon-sdk-1.12/lib/sdk/passwords.js
new file mode 100644
index 0000000..7aeb22a
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/passwords.js
@@ -0,0 +1,62 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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": "stable"
+};
+
+const { search, remove, store } = require("./passwords/utils");
+const { defer, delay } = require("./lang/functional");
+
+/**
+ * Utility function that returns `onComplete` and `onError` callbacks form the
+ * given `options` objects. Also properties are removed from the passed
+ * `options` objects.
+ * @param {Object} options
+ * Object that is passed to the exported functions of this module.
+ * @returns {Function[]}
+ * Array with two elements `onComplete` and `onError` functions.
+ */
+function getCallbacks(options) {
+ let value = [
+ 'onComplete' in options ? options.onComplete : null,
+ 'onError' in options ? defer(options.onError) : console.exception
+ ];
+
+ delete options.onComplete;
+ delete options.onError;
+
+ return value;
+};
+
+/**
+ * Creates a wrapper function that tries to call `onComplete` with a return
+ * value of the wrapped function or falls back to `onError` if wrapped function
+ * throws an exception.
+ */
+function createWrapperMethod(wrapped) {
+ return function (options) {
+ let [ onComplete, onError ] = getCallbacks(options);
+ try {
+ let value = wrapped(options);
+ if (onComplete) {
+ delay(function() {
+ try {
+ onComplete(value);
+ } catch (exception) {
+ onError(exception);
+ }
+ });
+ }
+ } catch (exception) {
+ onError(exception);
+ }
+ };
+}
+
+exports.search = createWrapperMethod(search);
+exports.store = createWrapperMethod(store);
+exports.remove = createWrapperMethod(remove);
diff --git a/tools/addon-sdk-1.12/lib/sdk/passwords/utils.js b/tools/addon-sdk-1.12/lib/sdk/passwords/utils.js
new file mode 100644
index 0000000..8cc2230
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/passwords/utils.js
@@ -0,0 +1,105 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Cc, Ci, CC } = require("chrome");
+const { uri: ADDON_URI } = require("../self");
+const loginManager = Cc["@mozilla.org/login-manager;1"].
+ getService(Ci.nsILoginManager);
+const { URL: parseURL } = require("../url");
+const LoginInfo = CC("@mozilla.org/login-manager/loginInfo;1",
+ "nsILoginInfo", "init");
+
+function filterMatchingLogins(loginInfo)
+ Object.keys(this).every(function(key) loginInfo[key] === this[key], this);
+
+/**
+ * Removes `user`, `password` and `path` fields from the given `url` if it's
+ * 'http', 'https' or 'ftp'. All other URLs are returned unchanged.
+ * @example
+ * http://user:pass@www.site.com/foo/?bar=baz#bang -> http://www.site.com
+ */
+function normalizeURL(url) {
+ let { scheme, host, port } = parseURL(url);
+ // We normalize URL only if it's `http`, `https` or `ftp`. All other types of
+ // URLs (`resource`, `chrome`, etc..) should not be normalized as they are
+ // used with add-on associated credentials path.
+ return scheme === "http" || scheme === "https" || scheme === "ftp" ?
+ scheme + "://" + (host || "") + (port ? ":" + port : "") :
+ url
+}
+
+function Login(options) {
+ let login = Object.create(Login.prototype);
+ Object.keys(options || {}).forEach(function(key) {
+ if (key === 'url')
+ login.hostname = normalizeURL(options.url);
+ else if (key === 'formSubmitURL')
+ login.formSubmitURL = options.formSubmitURL ?
+ normalizeURL(options.formSubmitURL) : null;
+ else if (key === 'realm')
+ login.httpRealm = options.realm;
+ else
+ login[key] = options[key];
+ });
+
+ return login;
+}
+Login.prototype.toJSON = function toJSON() {
+ return {
+ url: this.hostname || ADDON_URI,
+ realm: this.httpRealm || null,
+ formSubmitURL: this.formSubmitURL || null,
+ username: this.username || null,
+ password: this.password || null,
+ usernameField: this.usernameField || '',
+ passwordField: this.passwordField || '',
+ }
+};
+Login.prototype.toLoginInfo = function toLoginInfo() {
+ let { url, realm, formSubmitURL, username, password, usernameField,
+ passwordField } = this.toJSON();
+
+ return new LoginInfo(url, formSubmitURL, realm, username, password,
+ usernameField, passwordField);
+};
+
+function loginToJSON(value) Login(value).toJSON()
+
+/**
+ * Returns array of `nsILoginInfo` objects that are stored in the login manager
+ * and have all the properties with matching values as a given `options` object.
+ * @param {Object} options
+ * @returns {nsILoginInfo[]}
+ */
+exports.search = function search(options) {
+ return loginManager.getAllLogins()
+ .filter(filterMatchingLogins, Login(options))
+ .map(loginToJSON);
+};
+
+/**
+ * Stores login info created from the given `options` to the applications
+ * built-in login management system.
+ * @param {Object} options.
+ */
+exports.store = function store(options) {
+ loginManager.addLogin(Login(options).toLoginInfo());
+};
+
+/**
+ * Removes login info from the applications built-in login management system.
+ * _Please note: When removing a login info the specified properties must
+ * exactly match to the one that is already stored or exception will be thrown._
+ * @param {Object} options.
+ */
+exports.remove = function remove(options) {
+ loginManager.removeLogin(Login(options).toLoginInfo());
+};
diff --git a/tools/addon-sdk-1.12/lib/sdk/platform/xpcom.js b/tools/addon-sdk-1.12/lib/sdk/platform/xpcom.js
new file mode 100644
index 0000000..279d234
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/platform/xpcom.js
@@ -0,0 +1,229 @@
+/* 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, Cr, Cm, components: { classesByID } } = require('chrome');
+const { registerFactory, unregisterFactory, isCIDRegistered } =
+ Cm.QueryInterface(Ci.nsIComponentRegistrar);
+
+const { merge } = require('../util/object');
+const { Class, extend, mix } = require('../core/heritage');
+const { uuid } = require('../util/uuid');
+
+// This is a base prototype, that provides bare bones of XPCOM. JS based
+// components can be easily implement by extending it.
+const Unknown = new function() {
+ function hasInterface(component, iid) {
+ return component && component.interfaces &&
+ ( component.interfaces.some(function(id) iid.equals(Ci[id])) ||
+ component.implements.some(function($) hasInterface($, iid)) ||
+ hasInterface(Object.getPrototypeOf(component), iid));
+ }
+
+ return Class({
+ /**
+ * The `QueryInterface` method provides runtime type discovery used by XPCOM.
+ * This method return queried instance of `this` if given `iid` is listed in
+ * the `interfaces` property or in equivalent properties of objects in it's
+ * prototype chain. In addition it will look up in the prototypes under
+ * `implements` array property, this ways compositions made via `Class`
+ * utility will carry interfaces implemented by composition components.
+ */
+ QueryInterface: function QueryInterface(iid) {
+ // For some reason there are cases when `iid` is `null`. In such cases we
+ // just return `this`. Otherwise we verify that component implements given
+ // `iid` interface. This will be no longer necessary once Bug 748003 is
+ // fixed.
+ if (iid && !hasInterface(this, iid))
+ throw Cr.NS_ERROR_NO_INTERFACE;
+
+ return this;
+ },
+ /**
+ * Array of `XPCOM` interfaces (as strings) implemented by this component.
+ * All components implement `nsISupports` by default which is default value
+ * here. Provide array of interfaces implemented by an object when
+ * extending, to append them to this list (Please note that there is no
+ * need to repeat interfaces implemented by super as they will be added
+ * automatically).
+ */
+ interfaces: Object.freeze([ 'nsISupports' ])
+ });
+}
+exports.Unknown = Unknown;
+
+// Base exemplar for creating instances implementing `nsIFactory` interface,
+// that maybe registered into runtime via `register` function. Instances of
+// this factory create instances of enclosed component on `createInstance`.
+const Factory = Class({
+ extends: Unknown,
+ interfaces: [ 'nsIFactory' ],
+ /**
+ * All the descendants will get auto generated `id` (also known as `classID`
+ * in XPCOM world) unless one is manually provided.
+ */
+ get id() { throw Error('Factory must implement `id` property') },
+ /**
+ * XPCOM `contractID` may optionally be provided to associate this factory
+ * with it. `contract` is a unique string that has a following format:
+ * '@vendor.com/unique/id;1'.
+ */
+ contract: null,
+ /**
+ * Class description that is being registered. This value is intended as a
+ * human-readable description for the given class and does not needs to be
+ * globally unique.
+ */
+ description: 'Jetpack generated factory',
+ /**
+ * This method is required by `nsIFactory` interfaces, but as in most
+ * implementations it does nothing interesting.
+ */
+ lockFactory: function lockFactory(lock) undefined,
+ /**
+ * If property is `true` XPCOM service / factory will be registered
+ * automatically on creation.
+ */
+ register: true,
+ /**
+ * If property is `true` XPCOM factory will be unregistered prior to add-on
+ * unload.
+ */
+ unregister: true,
+ /**
+ * Method is called on `Service.new(options)` passing given `options` to
+ * it. Options is expected to have `component` property holding XPCOM
+ * component implementation typically decedent of `Unknown` or any custom
+ * implementation with a `new` method and optional `register`, `unregister`
+ * flags. Unless `register` is `false` Service / Factory will be
+ * automatically registered. Unless `unregister` is `false` component will
+ * be automatically unregistered on add-on unload.
+ */
+ initialize: function initialize(options) {
+ merge(this, {
+ id: 'id' in options ? options.id : uuid(),
+ register: 'register' in options ? options.register : this.register,
+ unregister: 'unregister' in options ? options.unregister : this.unregister,
+ contract: 'contract' in options ? options.contract : null,
+ Component: options.Component
+ });
+
+ // If service / factory has auto registration enabled then register.
+ if (this.register)
+ register(this);
+ },
+ /**
+ * Creates an instance of the class associated with this factory.
+ */
+ createInstance: function createInstance(outer, iid) {
+ try {
+ if (outer)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ return this.create().QueryInterface(iid);
+ }
+ catch (error) {
+ throw error instanceof Ci.nsIException ? error : Cr.NS_ERROR_FAILURE;
+ }
+ },
+ create: function create() this.Component()
+});
+exports.Factory = Factory;
+
+// Exemplar for creating services that implement `nsIFactory` interface, that
+// can be registered into runtime via call to `register`. This services return
+// enclosed `component` on `getService`.
+const Service = Class({
+ extends: Factory,
+ initialize: function initialize(options) {
+ this.component = options.Component();
+ Factory.prototype.initialize.call(this, options);
+ },
+ description: 'Jetpack generated service',
+ /**
+ * Creates an instance of the class associated with this factory.
+ */
+ create: function create() this.component
+});
+exports.Service = Service;
+
+function isRegistered({ id }) isCIDRegistered(id)
+exports.isRegistered = isRegistered;
+
+/**
+ * Registers given `component` object to be used to instantiate a particular
+ * class identified by `component.id`, and creates an association of class
+ * name and `component.contract` with the class.
+ */
+function register(factory) {
+ if (!(factory instanceof Factory)) {
+ throw new Error("xpcom.register() expect a Factory instance.\n" +
+ "Please refactor your code to new xpcom module if you" +
+ " are repacking an addon from SDK <= 1.5:\n" +
+ "https://addons.mozilla.org/en-US/developers/docs/sdk/latest/packages/api-utils/xpcom.html");
+ }
+
+ registerFactory(factory.id, factory.description, factory.contract, factory);
+
+ if (factory.unregister)
+ require('../system/unload').when(unregister.bind(null, factory));
+}
+exports.register = register;
+
+/**
+ * Unregister a factory associated with a particular class identified by
+ * `factory.classID`.
+ */
+function unregister(factory) {
+ if (isRegistered(factory))
+ unregisterFactory(factory.id, factory);
+}
+exports.unregister = unregister;
+
+function autoRegister(path) {
+ // TODO: This assumes that the url points to a directory
+ // that contains subdirectories corresponding to OS/ABI and then
+ // further subdirectories corresponding to Gecko platform version.
+ // we should probably either behave intelligently here or allow
+ // the caller to pass-in more options if e.g. there aren't
+ // Gecko-specific binaries for a component (which will be the case
+ // if only frozen interfaces are used).
+
+ var runtime = require("../system/runtime");
+ var osDirName = runtime.OS + "_" + runtime.XPCOMABI;
+ var platformVersion = require("../system/xul-app").platformVersion.substring(0, 5);
+
+ var file = Cc['@mozilla.org/file/local;1']
+ .createInstance(Ci.nsILocalFile);
+ file.initWithPath(path);
+ file.append(osDirName);
+ file.append(platformVersion);
+
+ if (!(file.exists() && file.isDirectory()))
+ throw new Error("component not available for OS/ABI " +
+ osDirName + " and platform " + platformVersion);
+
+ Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ Cm.autoRegister(file);
+}
+exports.autoRegister = autoRegister;
+
+/**
+ * Returns registered factory that has a given `id` or `null` if not found.
+ */
+function factoryByID(id) classesByID[id] || null
+exports.factoryByID = factoryByID;
+
+/**
+ * Returns factory registered with a given `contract` or `null` if not found.
+ * In contrast to `Cc[contract]` that does ignores new factory registration
+ * with a given `contract` this will return a factory currently associated
+ * with a `contract`.
+ */
+function factoryByContract(contract) factoryByID(Cm.contractIDToCID(contract))
+exports.factoryByContract = factoryByContract;
diff --git a/tools/addon-sdk-1.12/lib/sdk/preferences/event-target.js b/tools/addon-sdk-1.12/lib/sdk/preferences/event-target.js
new file mode 100644
index 0000000..fe9e07a
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/preferences/event-target.js
@@ -0,0 +1,60 @@
+/* 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 { Class } = require('../core/heritage');
+const { EventTarget } = require('../event/target');
+const { Branch } = require('./service');
+const { emit, off } = require('../event/core');
+const { when: unload } = require('../system/unload');
+
+const prefTargetNS = require('../core/namespace').ns();
+
+const PrefsTarget = Class({
+ extends: EventTarget,
+ initialize: function(options) {
+ options = options || {};
+ EventTarget.prototype.initialize.call(this, options);
+
+ let branchName = options.branchName || '';
+ let branch = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService).
+ getBranch(branchName).
+ QueryInterface(Ci.nsIPrefBranch2);
+ prefTargetNS(this).branch = branch;
+
+ // provides easy access to preference values
+ this.prefs = Branch(branchName);
+
+ // start listening to preference changes
+ let observer = prefTargetNS(this).observer = onChange.bind(this);
+ branch.addObserver('', observer, false);
+
+ // Make sure to destroy this on unload
+ unload(destroy.bind(this));
+ }
+});
+exports.PrefsTarget = PrefsTarget;
+
+/* HELPERS */
+
+function onChange(subject, topic, name) {
+ if (topic === 'nsPref:changed')
+ emit(this, name, name);
+ emit(this, '', name);
+}
+
+function destroy() {
+ off(this);
+
+ // stop listening to preference changes
+ let branch = prefTargetNS(this).branch;
+ branch.removeObserver('', prefTargetNS(this).observer, false);
+ prefTargetNS(this).observer = null;
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/preferences/service.js b/tools/addon-sdk-1.12/lib/sdk/preferences/service.js
new file mode 100644
index 0000000..4303fc1
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/preferences/service.js
@@ -0,0 +1,174 @@
+/* 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"
+};
+
+// The minimum and maximum integers that can be set as preferences.
+// The range of valid values is narrower than the range of valid JS values
+// because the native preferences code treats integers as NSPR PRInt32s,
+// which are 32-bit signed integers on all platforms.
+const MAX_INT = 0x7FFFFFFF;
+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, "");
+ });
+ }
+
+ 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) {
+ return {
+ value: get(branchName + name)
+ };
+ },
+ enumerate: getPrefKeys,
+ keys: getPrefKeys
+ }, Branch.prototype);
+}
+
+function get(name, defaultValue) {
+ switch (prefSvc.getPrefType(name)) {
+ case Ci.nsIPrefBranch.PREF_STRING:
+ return prefSvc.getComplexValue(name, Ci.nsISupportsString).data;
+
+ case Ci.nsIPrefBranch.PREF_INT:
+ return prefSvc.getIntPref(name);
+
+ case Ci.nsIPrefBranch.PREF_BOOL:
+ return prefSvc.getBoolPref(name);
+
+ case Ci.nsIPrefBranch.PREF_INVALID:
+ return defaultValue;
+
+ default:
+ // This should never happen.
+ throw new Error("Error getting pref " + name +
+ "; its value's type is " +
+ prefSvc.getPrefType(name) +
+ ", which I don't know " +
+ "how to handle.");
+ }
+}
+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");
+ }
+}
+exports.set = set;
+
+function has(name) {
+ return (prefSvc.getPrefType(name) != Ci.nsIPrefBranch.PREF_INVALID);
+}
+exports.has = has;
+
+function keys(root) {
+ return prefSvc.getChildList(root);
+}
+exports.keys = keys;
+
+function isSet(name) {
+ return (has(name) && prefSvc.prefHasUserValue(name));
+}
+exports.isSet = isSet;
+
+function reset(name) {
+ try {
+ prefSvc.clearUserPref(name);
+ } catch (e if e.result == Cr.NS_ERROR_UNEXPECTED) {
+ // The pref service throws NS_ERROR_UNEXPECTED when the caller tries
+ // to reset a pref that doesn't exist or is already set to its default
+ // value. This interface fails silently in those cases, so callers
+ // can unconditionally reset a pref without having to check if it needs
+ // resetting first or trap exceptions after the fact. It passes through
+ // other exceptions, however, so callers know about them, since we don't
+ // know what other exceptions might be thrown and what they might mean.
+ }
+}
+exports.reset = reset;
+
+function getLocalized(name, defaultValue) {
+ let value = null;
+ try {
+ value = prefSvc.getComplexValue(name, Ci.nsIPrefLocalizedString).data;
+ }
+ finally {
+ return value || defaultValue;
+ }
+}
+exports.getLocalized = getLocalized;
+
+function setLocalized(name, value) {
+ // We can't use `prefs.set` here as we have to use `getDefaultBranch`
+ // (instead of `getBranch`) in order to have `mIsDefault` set to true, here:
+ // http://mxr.mozilla.org/mozilla-central/source/modules/libpref/src/nsPrefBranch.cpp#233
+ // Otherwise, we do not enter into this expected condition:
+ // http://mxr.mozilla.org/mozilla-central/source/modules/libpref/src/nsPrefBranch.cpp#244
+ defaultBranch.setCharPref(name, value);
+}
+exports.setLocalized = setLocalized;
+
+exports.Branch = Branch;
+
diff --git a/tools/addon-sdk-1.12/lib/sdk/private-browsing.js b/tools/addon-sdk-1.12/lib/sdk/private-browsing.js
new file mode 100644
index 0000000..6d2a709
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/private-browsing.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": "stable"
+};
+
+const { setMode, getMode, on: onStateChange } = require('./private-browsing/utils');
+const { emit, on, once, off } = require('./event/core');
+const { when: unload } = require('./system/unload');
+const observers = require('./deprecated/observer-service');
+
+onStateChange('start', function onStart() {
+ emit(exports, 'start');
+});
+
+onStateChange('stop', function onStop() {
+ emit(exports, 'stop');
+});
+
+// Make sure listeners are cleaned up.
+unload(function() off(exports));
+
+Object.defineProperty(exports, "isActive", { get: function() getMode() });
+exports.activate = function activate() setMode(true);
+exports.deactivate = function deactivate() setMode(false);
+exports.on = on.bind(null, exports);
+exports.once = once.bind(null, exports);
+exports.removeListener = function removeListener(type, listener) {
+ // Note: We can't just bind `off` as we do it for other methods cause skipping
+ // a listener argument will remove all listeners for the given event type
+ // causing misbehavior. This way we make sure all arguments are passed.
+ off(exports, type, listener);
+};
diff --git a/tools/addon-sdk-1.12/lib/sdk/private-browsing/utils.js b/tools/addon-sdk-1.12/lib/sdk/private-browsing/utils.js
new file mode 100644
index 0000000..1f57ca0
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/private-browsing/utils.js
@@ -0,0 +1,74 @@
+/* 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 { defer } = require('../lang/functional');
+const observers = require('../deprecated/observer-service');
+const { emit, on, once, off } = require('../event/core');
+const { when: unload } = require('../system/unload');
+const { getWindowLoadingContext } = require('../window/utils');
+const { deprecateFunction } = require('../util/deprecate');
+
+let deferredEmit = defer(emit);
+
+let pbService;
+
+// Currently, only Firefox implements the private browsing service.
+if (require("../system/xul-app").is("Firefox")) {
+ pbService = Cc["@mozilla.org/privatebrowsing;1"].
+ getService(Ci.nsIPrivateBrowsingService);
+
+ // set up an observer for private browsing switches.
+ observers.add('private-browsing-transition-complete', function onChange() {
+ // Emit event with in next turn of event loop.
+ deferredEmit(exports, pbService.privateBrowsingEnabled ? 'start' : 'stop');
+ });
+}
+
+// checks that per-window private browsing implemented
+let isWindowPBEnabled = function isWindowPBEnabled(chromeWin) {
+ return !!(chromeWin &&
+ 'gPrivateBrowsingUI' in chromeWin &&
+ 'privateWindow' in chromeWin.gPrivateBrowsingUI);
+}
+exports.isWindowPBEnabled = isWindowPBEnabled;
+
+// We toggle private browsing mode asynchronously in order to work around
+// bug 659629. Since private browsing transitions are asynchronous
+// anyway, this doesn't significantly change the behavior of the API.
+// Note: this method should not be used with `chromeWin` argument until
+// the UI has been implemented. Bug 729865
+let setMode = defer(function setMode(value, chromeWin) {
+ value = !!value; // Cast to boolean.
+
+ if (isWindowPBEnabled(chromeWin))
+ return getWindowLoadingContext(chromeWin).usePrivateBrowsing = value;
+
+ // default
+ return pbService && (pbService.privateBrowsingEnabled = value);
+});
+exports.setMode = deprecateFunction(
+ setMode,
+ 'require("private-browsing").activate and require("private-browsing").deactivate ' +
+ 'is deprecated.'
+);
+
+let getMode = function getMode(chromeWin) {
+ if (isWindowPBEnabled(chromeWin))
+ return getWindowLoadingContext(chromeWin).usePrivateBrowsing;
+
+ // default
+ return pbService ? pbService.privateBrowsingEnabled : false;
+};
+exports.getMode = getMode;
+
+exports.on = on.bind(null, exports);
+
+// Make sure listeners are cleaned up.
+unload(function() off(exports));
diff --git a/tools/addon-sdk-1.12/lib/sdk/querystring.js b/tools/addon-sdk-1.12/lib/sdk/querystring.js
new file mode 100644
index 0000000..e0e2273
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/querystring.js
@@ -0,0 +1,121 @@
+/* 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"
+};
+
+let unescape = decodeURIComponent;
+exports.unescape = unescape;
+
+// encodes a string safely for application/x-www-form-urlencoded
+// adheres to RFC 3986
+// see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURIComponent
+function escape(query) {
+ return encodeURIComponent(query).
+ replace(/%20/g, '+').
+ replace(/!/g, '%21').
+ replace(/'/g, '%27').
+ replace(/\(/g, '%28').
+ replace(/\)/g, '%29').
+ replace(/\*/g, '%2A');
+}
+exports.escape = escape;
+
+// Converts an object of unordered key-vals to a string that can be passed
+// as part of a request
+function stringify(options, separator, assigner) {
+ separator = separator || '&';
+ assigner = assigner || '=';
+ // Explicitly return null if we have null, and empty string, or empty object.
+ if (!options)
+ return '';
+
+ // If content is already a string, just return it as is.
+ if (typeof(options) == 'string')
+ return options;
+
+ // At this point we have a k:v object. Iterate over it and encode each value.
+ // Arrays and nested objects will get encoded as needed. For example...
+ //
+ // { foo: [1, 2, { omg: 'bbq', 'all your base!': 'are belong to us' }], bar: 'baz' }
+ //
+ // will be encoded as
+ //
+ // foo[0]=1&foo[1]=2&foo[2][omg]=bbq&foo[2][all+your+base!]=are+belong+to+us&bar=baz
+ //
+ // Keys (including '[' and ']') and values will be encoded with
+ // `escape` before returning.
+ //
+ // Execution was inspired by jQuery, but some details have changed and numeric
+ // array keys are included (whereas they are not in jQuery).
+
+ let encodedContent = [];
+ function add(key, val) {
+ encodedContent.push(escape(key) + assigner + escape(val));
+ }
+
+ function make(key, value) {
+ if (value && typeof(value) === 'object')
+ Object.keys(value).forEach(function(name) {
+ make(key + '[' + name + ']', value[name]);
+ });
+ else
+ add(key, value);
+ }
+
+ Object.keys(options).forEach(function(name) { make(name, options[name]); });
+ return encodedContent.join(separator);
+
+ //XXXzpao In theory, we can just use a FormData object on 1.9.3, but I had
+ // trouble getting that working. It would also be nice to stay
+ // backwards-compat as long as possible. Keeping this in for now...
+ // let formData = Cc['@mozilla.org/files/formdata;1'].
+ // createInstance(Ci.nsIDOMFormData);
+ // for ([k, v] in Iterator(content)) {
+ // formData.append(k, v);
+ // }
+ // return formData;
+}
+exports.stringify = stringify;
+
+// Exporting aliases that nodejs implements just for the sake of
+// interoperability.
+exports.encode = stringify;
+exports.serialize = stringify;
+
+// Note: That `stringify` and `parse` aren't bijective as we use `stringify`
+// as it was implement in request module, but implement `parse` to match nodejs
+// behavior.
+// TODO: Make `stringify` implement API as in nodejs and figure out backwards
+// compatibility.
+function parse(query, separator, assigner) {
+ separator = separator || '&';
+ assigner = assigner || '=';
+ let result = {};
+
+ if (typeof query !== 'string' || query.length === 0)
+ return result;
+
+ query.split(separator).forEach(function(chunk) {
+ let pair = chunk.split(assigner);
+ let key = unescape(pair[0]);
+ let value = unescape(pair.slice(1).join(assigner));
+
+ if (!(key in result))
+ result[key] = value;
+ else if (Array.isArray(result[key]))
+ result[key].push(value);
+ else
+ result[key] = [result[key], value];
+ });
+
+ return result;
+};
+exports.parse = parse;
+// Exporting aliases that nodejs implements just for the sake of
+// interoperability.
+exports.decode = parse;
diff --git a/tools/addon-sdk-1.12/lib/sdk/request.js b/tools/addon-sdk-1.12/lib/sdk/request.js
new file mode 100644
index 0000000..da5be7f
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/request.js
@@ -0,0 +1,213 @@
+/* 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": "stable"
+};
+
+const { ns } = require("./core/namespace");
+const { emit } = require("./event/core");
+const { merge } = require("./util/object");
+const { stringify } = require("./querystring");
+const { EventTarget } = require("./event/target");
+const { Class } = require("./core/heritage");
+const { XMLHttpRequest } = require("./net/xhr");
+const apiUtils = require("./deprecated/api-utils");
+
+const response = ns();
+const request = ns();
+
+// Instead of creating a new validator for each request, just make one and
+// reuse it.
+const { validateOptions, validateSingleOption } = new OptionsValidator({
+ url: {
+ //XXXzpao should probably verify that url is a valid url as well
+ is: ["string"]
+ },
+ headers: {
+ map: function (v) v || {},
+ is: ["object"],
+ },
+ content: {
+ map: function (v) v || null,
+ is: ["string", "object", "null"],
+ },
+ contentType: {
+ map: function (v) v || "application/x-www-form-urlencoded",
+ is: ["string"],
+ },
+ overrideMimeType: {
+ map: function(v) v || null,
+ is: ["string", "null"],
+ }
+});
+
+const REUSE_ERROR = "This request object has been used already. You must " +
+ "create a new one to make a new request."
+
+// Utility function to prep the request since it's the same between GET and
+// POST
+function runRequest(mode, target) {
+ let source = request(target)
+ let { xhr, url, content, contentType, headers, overrideMimeType } = source;
+
+ // If this request has already been used, then we can't reuse it.
+ // Throw an error.
+ if (xhr)
+ throw new Error(REUSE_ERROR);
+
+ xhr = source.xhr = new XMLHttpRequest();
+
+ // Build the data to be set. For GET requests, we want to append that to
+ // the URL before opening the request.
+ let data = stringify(content);
+ // If the URL already has ? in it, then we want to just use &
+ if (mode == "GET" && data)
+ url = url + (/\?/.test(url) ? "&" : "?") + data;
+
+ // open the request
+ xhr.open(mode, url);
+
+ // request header must be set after open, but before send
+ xhr.setRequestHeader("Content-Type", contentType);
+
+ // set other headers
+ Object.keys(headers).forEach(function(name) {
+ xhr.setRequestHeader(name, headers[name]);
+ });
+
+ // set overrideMimeType
+ if (overrideMimeType)
+ xhr.overrideMimeType(overrideMimeType);
+
+ // handle the readystate, create the response, and call the callback
+ xhr.onreadystatechange = function onreadystatechange() {
+ if (xhr.readyState === 4) {
+ let response = Response(xhr);
+ source.response = response;
+ emit(target, 'complete', response);
+ }
+ };
+
+ // actually send the request.
+ // We don't want to send data on GET requests.
+ xhr.send(mode !== "GET" ? data : null);
+}
+
+const Request = Class({
+ extends: EventTarget,
+ initialize: function initialize(options) {
+ // `EventTarget.initialize` will set event listeners that are named
+ // like `onEvent` in this case `onComplete` listener will be set to
+ // `complete` event.
+ EventTarget.prototype.initialize.call(this, options);
+
+ // Copy normalized options.
+ merge(request(this), validateOptions(options));
+ },
+ get url() { return request(this).url; },
+ set url(value) { request(this).url = validateSingleOption('url', value); },
+ get headers() { return request(this).headers; },
+ set headers(value) {
+ return request(this).headers = validateSingleOption('headers', value);
+ },
+ get content() { return request(this).content; },
+ set content(value) {
+ request(this).content = validateSingleOption('content', value);
+ },
+ get contentType() { return request(this).contentType; },
+ set contentType(value) {
+ request(this).contentType = validateSingleOption('contentType', value);
+ },
+ get response() { return request(this).response; },
+ get: function() {
+ runRequest('GET', this);
+ return this;
+ },
+ post: function() {
+ runRequest('POST', this);
+ return this;
+ },
+ put: function() {
+ runRequest('PUT', this);
+ return this;
+ }
+});
+exports.Request = Request;
+
+const Response = Class({
+ initialize: function initialize(request) {
+ response(this).request = request;
+ },
+ get text() response(this).request.responseText,
+ get xml() {
+ throw new Error("Sorry, the 'xml' property is no longer available. " +
+ "see bug 611042 for more information.");
+ },
+ get status() response(this).request.status,
+ get statusText() response(this).request.statusText,
+ get json() {
+ try {
+ return JSON.parse(this.text);
+ } catch(error) {
+ return null;
+ }
+ },
+ get headers() {
+ let headers = {}, lastKey;
+ // Since getAllResponseHeaders() will return null if there are no headers,
+ // defend against it by defaulting to ""
+ let rawHeaders = response(this).request.getAllResponseHeaders() || "";
+ rawHeaders.split("\n").forEach(function (h) {
+ // According to the HTTP spec, the header string is terminated by an empty
+ // line, so we can just skip it.
+ if (!h.length) {
+ return;
+ }
+
+ let index = h.indexOf(":");
+ // The spec allows for leading spaces, so instead of assuming a single
+ // leading space, just trim the values.
+ let key = h.substring(0, index).trim(),
+ val = h.substring(index + 1).trim();
+
+ // For empty keys, that means that the header value spanned multiple lines.
+ // In that case we should append the value to the value of lastKey with a
+ // new line. We'll assume lastKey will be set because there should never
+ // be an empty key on the first pass.
+ if (key) {
+ headers[key] = val;
+ lastKey = key;
+ }
+ else {
+ headers[lastKey] += "\n" + val;
+ }
+ });
+ return headers;
+ }
+});
+
+// apiUtils.validateOptions doesn't give the ability to easily validate single
+// options, so this is a wrapper that provides that ability.
+function OptionsValidator(rules) {
+ return {
+ validateOptions: function (options) {
+ return apiUtils.validateOptions(options, rules);
+ },
+ validateSingleOption: function (field, value) {
+ // We need to create a single rule object from our listed rules. To avoid
+ // JavaScript String warnings, check for the field & default to an empty object.
+ let singleRule = {};
+ if (field in rules) {
+ singleRule[field] = rules[field];
+ }
+ let singleOption = {};
+ singleOption[field] = value;
+ // This should throw if it's invalid, which will bubble up & out.
+ return apiUtils.validateOptions(singleOption, singleRule)[field];
+ }
+ };
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/selection.js b/tools/addon-sdk-1.12/lib/sdk/selection.js
new file mode 100644
index 0000000..da94efe
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/selection.js
@@ -0,0 +1,481 @@
+/* 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": "stable"
+};
+
+if (!require("./system/xul-app").is("Firefox")) {
+ throw new Error([
+ "The selection module currently supports only Firefox. In the future ",
+ "we would like it to support other applications, however. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=560716 for more information."
+ ].join(""));
+}
+
+let { Ci, Cc } = require("chrome"),
+ { setTimeout } = require("./timers"),
+ { emit, off } = require("./event/core"),
+ { Unknown } = require("./platform/xpcom"),
+ { Class, obscure } = require("./core/heritage"),
+ { EventTarget } = require("./event/target"),
+ observers = require("./deprecated/observer-service"),
+ { ns } = require("./core/namespace");
+
+// When a document is not visible anymore the selection object is detached, and
+// a new selection object is created when it becomes visible again.
+// That makes the previous selection's listeners added previously totally
+// useless – the listeners are not notified anymore.
+// To fix that we're listening for `document-shown` event in order to add
+// the listeners to the new selection object created.
+//
+// See bug 665386 for further details.
+
+let selections = ns();
+
+observers.add("document-shown", function (document) {
+ var window = document.defaultView;
+
+ // We are not interested in documents without valid defaultView
+ if (!window)
+ return;
+
+ let selection = selections(window).selection;
+
+ // We want to handle only the windows where we added selection's listeners
+ if (selection) {
+ let currentSelection = window.getSelection();
+
+ // If the current selection for the window given is different from the one
+ // stored in the namespace, we need to add the listeners again, and replace
+ // the previous selection in our list with the new one.
+ //
+ // Notice that we don't have to remove the listeners from the old selection,
+ // because is detached. An attempt to remove the listener, will raise an
+ // error (see http://mxr.mozilla.org/mozilla-central/source/layout/generic/nsSelection.cpp#5343 )
+ //
+ // We ensure that the current selection is an instance of
+ // `nsISelectionPrivate` before working on it, in case is `null`.
+ if (currentSelection instanceof Ci.nsISelectionPrivate &&
+ currentSelection !== selection) {
+
+ currentSelection.addSelectionListener(SelectionListenerManager);
+ selections(window).selection = currentSelection;
+ }
+ }
+});
+
+const windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator);
+
+// The selection type HTML
+const HTML = 0x01;
+
+// The selection type TEXT
+const TEXT = 0x02;
+
+// The selection type DOM (internal use only)
+const DOM = 0x03;
+
+// A more developer-friendly message than the caught exception when is not
+// possible change a selection.
+const ERR_CANNOT_CHANGE_SELECTION =
+ "It isn't possible to change the selection, as there isn't currently a selection";
+
+const Selection = Class({
+ /**
+ * Creates an object from which a selection can be set, get, etc. Each
+ * object has an associated with a range number. Range numbers are the
+ * 0-indexed counter of selection ranges as explained at
+ * https://developer.mozilla.org/en/DOM/Selection.
+ *
+ * @param rangeNumber
+ * The zero-based range index into the selection
+ */
+ initialize: function initialize(rangeNumber) {
+ // In order to hide the private `rangeNumber` argument from API consumers
+ // while still enabling Selection getters/setters to access it, we define
+ // it as non enumerable, non configurable property. While consumers still
+ // may discover it they won't be able to do any harm which is good enough
+ // in this case.
+ Object.defineProperties(this, {
+ rangeNumber: {
+ enumerable: false,
+ configurable: false,
+ value: rangeNumber
+ }
+ });
+ },
+ get text() { return getSelection(TEXT, this.rangeNumber); },
+ set text(value) { setSelection(value, this.rangeNumber); },
+ get html() { return getSelection(HTML, this.rangeNumber); },
+ set html(value) { setSelection(value, this.rangeNumber); },
+ get isContiguous() {
+ let selection = getSelection(DOM);
+
+ // If there are multiple ranges, the selection is definitely discontiguous.
+ // It returns `false` also if there are no selection; and `true` if there is
+ // a single non empty range, or a selection in a text field - contiguous or
+ // not (text field selection APIs doesn't support multiple selections).
+
+ if (selection.rangeCount > 1)
+ return false;
+
+ return !!(safeGetRange(selection, 0) || getElementWithSelection());
+ }
+});
+
+/**
+ * Returns the most recent content window
+ */
+function context() {
+ // Overlay names should probably go into the xul-app module instead of here
+ return windowMediator.getMostRecentWindow("navigator:browser").document.
+ commandDispatcher.focusedWindow;
+}
+
+/**
+ * Returns the current selection from most recent content window. Depending on
+ * the specified |type|, the value returned can be a string of text, stringified
+ * HTML, or a DOM selection object as described at
+ * https://developer.mozilla.org/en/DOM/Selection.
+ *
+ * @param type
+ * Specifies the return type of the selection. Valid values are the one
+ * of the constants HTML, TEXT, or DOM.
+ *
+ * @param rangeNumber
+ * Specifies the zero-based range index of the returned selection.
+ */
+function getSelection(type, rangeNumber) {
+ let window, selection;
+ try {
+ window = context();
+ selection = window.getSelection();
+ }
+ catch (e) {
+ return null;
+ }
+
+ // Get the selected content as the specified type
+ if (type == DOM)
+ return selection;
+ else if (type == TEXT) {
+ let range = safeGetRange(selection, rangeNumber);
+
+ if (range)
+ return range.toString();
+
+ let node = getElementWithSelection(window);
+
+ if (!node)
+ return null;
+
+ return node.value.substring(node.selectionStart, node.selectionEnd);
+ }
+ else if (type == HTML) {
+ let range = safeGetRange(selection, rangeNumber);
+ // Another way, but this includes the xmlns attribute for all elements in
+ // Gecko 1.9.2+ :
+ // return Cc["@mozilla.org/xmlextras/xmlserializer;1"].
+ // createInstance(Ci.nsIDOMSerializer).serializeToSTring(range.
+ // cloneContents());
+ if (!range)
+ return null;
+ let node = window.document.createElement("span");
+ node.appendChild(range.cloneContents());
+ return node.innerHTML;
+ }
+ throw new Error("Type " + type + " is unrecognized.");
+}
+
+/**
+ * Returns the specified range in a selection without throwing an exception.
+ *
+ * @param selection
+ * A selection object as described at
+ * https://developer.mozilla.org/en/DOM/Selection
+ *
+ * @param rangeNumber
+ * Specifies the zero-based range index of the returned selection.
+ */
+function safeGetRange(selection, rangeNumber) {
+ try {
+ let range = selection.getRangeAt(rangeNumber);
+ if (!range || range.toString() == "")
+ return null;
+ return range;
+ }
+ catch (e) {
+ return null;
+ }
+}
+
+/**
+ * Returns a reference of the DOM's active element for the window given, if it
+ * supports the text field selection API and has a text selected.
+ *
+ * Note:
+ * we need this method because window.getSelection doesn't return a selection
+ * for text selected in a form field (see bug 85686)
+ *
+ * @param {nsIWindow} [window]
+ * A reference to a window
+ */
+function getElementWithSelection(window) {
+ let element;
+
+ try {
+ element = (window || context()).document.activeElement;
+ }
+ catch (e) {
+ element = null;
+ }
+
+ if (!element)
+ return null;
+
+ let { value, selectionStart, selectionEnd } = element;
+
+ let hasSelection = typeof value === "string" &&
+ !isNaN(selectionStart) &&
+ !isNaN(selectionEnd) &&
+ selectionStart !== selectionEnd;
+
+ return hasSelection ? element : null;
+}
+/**
+ * Sets the current selection of the most recent content document by changing
+ * the existing selected text/HTML range to the specified value.
+ *
+ * @param val
+ * The value for the new selection
+ *
+ * @param rangeNumber
+ * The zero-based range index of the selection to be set
+ *
+ */
+function setSelection(val, rangeNumber) {
+ // Make sure we have a window context & that there is a current selection.
+ // Selection cannot be set unless there is an existing selection.
+ let window, selection;
+
+ try {
+ window = context();
+ selection = window.getSelection();
+ }
+ catch (e) {
+ throw new Error(ERR_CANNOT_CHANGE_SELECTION);
+ }
+
+ let range = safeGetRange(selection, rangeNumber);
+
+ if (range) {
+ // Get rid of the current selection and insert our own
+ range.deleteContents();
+ let node = window.document.createElement("span");
+ range.surroundContents(node);
+
+ // Some relevant JEP-111 requirements:
+
+ // Setting the text property replaces the selection with the value to
+ // which the property is set and sets the html property to the same value
+ // to which the text property is being set.
+
+ // Setting the html property replaces the selection with the value to
+ // which the property is set and sets the text property to the text version
+ // of the HTML value.
+
+ // This sets both the HTML and text properties.
+ node.innerHTML = val;
+ } else {
+ let node = getElementWithSelection(window);
+
+ if (!node)
+ throw new Error(ERR_CANNOT_CHANGE_SELECTION);
+
+ let { value, selectionStart, selectionEnd } = node;
+
+ let newSelectionEnd = selectionStart + val.length;
+
+ node.value = value.substring(0, selectionStart) +
+ val +
+ value.substring(selectionEnd, value.length);
+
+ node.setSelectionRange(selectionStart, newSelectionEnd);
+ }
+}
+
+function onLoad(event) {
+ SelectionListenerManager.onLoad(event);
+}
+
+function onUnload(event) {
+ SelectionListenerManager.onUnload(event);
+}
+
+function onSelect() {
+ SelectionListenerManager.onSelect();
+}
+
+let SelectionListenerManager = Class({
+ extends: Unknown,
+ interfaces: [ 'nsISelectionListener' ],
+ /**
+ * This is the nsISelectionListener implementation. This function is called
+ * by Gecko when a selection is changed interactively.
+ *
+ * We only pay attention to the SELECTALL, KEYPRESS, and MOUSEUP selection
+ * reasons. All reasons are listed here:
+ *
+ * http://mxr.mozilla.org/mozilla1.9.2/source/content/base/public/
+ * nsISelectionListener.idl
+ *
+ * The other reasons (NO_REASON, DRAG_REASON, MOUSEDOWN_REASON) aren't
+ * applicable to us.
+ */
+ notifySelectionChanged: function notifySelectionChanged(document, selection,
+ reason) {
+ if (!["SELECTALL", "KEYPRESS", "MOUSEUP"].some(function(type) reason &
+ Ci.nsISelectionListener[type + "_REASON"]) || selection.toString() == "")
+ return;
+
+ this.onSelect();
+ },
+
+ onSelect : function onSelect() {
+ setTimeout(emit, 0, module.exports, "select");
+ },
+
+ /**
+ * Part of the Tracker implementation. This function is called by the
+ * tabs module when a browser is being tracked. Often, that means a new tab
+ * has been opened, but it can also mean an addon has been installed while
+ * tabs are already opened. In that case, this function is called for those
+ * already-opened tabs.
+ *
+ * @param browser
+ * The browser being tracked
+ */
+ onTrack: function onTrack(browser) {
+ browser.addEventListener("load", onLoad, true);
+ browser.addEventListener("unload", onUnload, true);
+ },
+
+ onLoad: function onLoad(event) {
+ // Nothing to do without a useful window
+ let window = event.target.defaultView;
+ if (!window)
+ return;
+
+ // Wrap the add selection call with some number of setTimeout 0 because some
+ // reason it's possible to add a selection listener "too early". 2 sometimes
+ // works for gmail, and more consistently with 3, so make it 5 to be safe.
+ let count = 0;
+ let self = this;
+ function wrap(count, func) {
+ if (count-- > 0)
+ setTimeout(wrap, 0);
+ else
+ self.addSelectionListener(window);
+ }
+ wrap();
+ },
+
+ addSelectionListener: function addSelectionListener(window) {
+ // Don't add the selection's listener more than once to the same window.
+ if ("selection" in selections(window))
+ return;
+
+ let selection = window.getSelection();
+
+ // We ensure that the current selection is an instance of
+ // `nsISelectionPrivate` before working on it, in case is `null`.
+ if (selection instanceof Ci.nsISelectionPrivate) {
+ selection.addSelectionListener(this);
+
+ // nsISelectionListener implementation seems not fire a notification if
+ // a selection is in a text field, therefore we need to add a listener to
+ // window.onselect, that is fired only for text fields.
+ // For consistency, we add it only when the nsISelectionListener is added.
+ //
+ // https://developer.mozilla.org/en/DOM/window.onselect
+ window.addEventListener("select", onSelect, true);
+
+ selections(window).selection = selection;
+ }
+ },
+
+ onUnload: function onUnload(event) {
+ // Nothing to do without a useful window
+ let window = event.target.defaultView;
+ if (!window)
+ return;
+ this.removeSelectionListener(window);
+ off(exports);
+ },
+
+ removeSelectionListener: function removeSelectionListener(window) {
+ // Don't remove the selection's listener to a window that wasn't handled.
+ if (!("selection" in selections(window)))
+ return;
+
+ let selection = window.getSelection();
+
+ // We ensure that the current selection is an instance of
+ // `nsISelectionPrivate` before working on it, in case is `null`.
+ if (selection instanceof Ci.nsISelectionPrivate) {
+ selection.removeSelectionListener(this);
+
+ window.removeEventListener("select", onSelect);
+
+ delete selections(window).selection;
+ }
+ },
+
+ /**
+ * Part of the TabTracker implementation. This function is called by the
+ * tabs module when a browser is being untracked. Usually, that means a tab
+ * has been closed.
+ *
+ * @param browser
+ * The browser being untracked
+ */
+ onUntrack: function onUntrack(browser) {
+ browser.removeEventListener("load", onLoad, true);
+ browser.removeEventListener("unload", onUnload, true);
+ }
+})();
+
+/**
+ * Install |SelectionListenerManager| as tab tracker in order to watch
+ * tab opening/closing
+ */
+require("./deprecated/tab-browser").Tracker(SelectionListenerManager);
+
+var SelectionIterator = Class(obscure({
+ /**
+ * Exports an iterator so that discontiguous selections can be iterated.
+ *
+ * If discontiguous selections are in a text field, only the first one
+ * is returned because the text field selection APIs doesn't support
+ * multiple selections.
+ */
+ __iterator__: function() {
+ let selection = getSelection(DOM);
+ let count = selection.rangeCount || (getElementWithSelection() ? 1 : 0);
+
+ for (let i = 0; i < count; i++)
+ yield Selection(i);
+ }
+}));
+
+var selection = Class({
+ extends: EventTarget,
+ implements: [ Selection, SelectionIterator ]
+})(0);
+
+module.exports = selection;
diff --git a/tools/addon-sdk-1.12/lib/sdk/self.js b/tools/addon-sdk-1.12/lib/sdk/self.js
new file mode 100644
index 0000000..f9fca1e
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/self.js
@@ -0,0 +1,36 @@
+/* vim:st=2:sts=2:sw=2:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { CC } = require('chrome');
+const { id, name, prefixURI, rootURI,
+ version, loadReason } = require('@loader/options');
+
+const { readURISync } = require('./net/url');
+
+const addonDataURI = prefixURI + name + '/data/';
+
+function uri(path) {
+ return addonDataURI + (path || '');
+}
+
+
+// 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.
+exports.uri = 'addon:' + id;
+exports.id = id;
+exports.name = name;
+exports.loadReason = loadReason;
+exports.version = version;
+// If `rootURI` is jar:file://...!/ than add-on is packed.
+exports.packed = rootURI.indexOf('jar:') === 0
+exports.data = Object.freeze({
+ url: uri,
+ load: function read(path) {
+ return readURISync(uri(path));
+ }
+});
diff --git a/tools/addon-sdk-1.12/lib/sdk/simple-prefs.js b/tools/addon-sdk-1.12/lib/sdk/simple-prefs.js
new file mode 100644
index 0000000..5cbde49
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/simple-prefs.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';
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { emit, off } = require("./event/core");
+const { when: unload } = require("./system/unload");
+const { PrefsTarget } = require("./preferences/event-target");
+const { id } = require("./self");
+const observers = require("./deprecated/observer-service");
+
+const ADDON_BRANCH = "extensions." + id + ".";
+const BUTTON_PRESSED = id + "-cmdPressed";
+
+const target = PrefsTarget({ branchName: ADDON_BRANCH });
+
+// Listen to clicks on buttons
+function buttonClick(subject, data) {
+ emit(target, data);
+}
+observers.add(BUTTON_PRESSED, buttonClick);
+
+// Make sure we cleanup listeners on unload.
+unload(function() {
+ observers.remove(BUTTON_PRESSED, buttonClick);
+});
+
+module.exports = target;
diff --git a/tools/addon-sdk-1.12/lib/sdk/simple-storage.js b/tools/addon-sdk-1.12/lib/sdk/simple-storage.js
new file mode 100644
index 0000000..2b88cc1
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/simple-storage.js
@@ -0,0 +1,237 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et filetype=javascript
+ * 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": "stable"
+};
+
+const { Cc, Ci } = require("chrome");
+const file = require("./io/file");
+const prefs = require("./preferences/service");
+const jpSelf = require("./self");
+const timer = require("./timers");
+const unload = require("./system/unload");
+const { emit, on, off } = require("./event/core");
+
+const WRITE_PERIOD_PREF = "extensions.addon-sdk.simple-storage.writePeriod";
+const WRITE_PERIOD_DEFAULT = 300000; // 5 minutes
+
+const QUOTA_PREF = "extensions.addon-sdk.simple-storage.quota";
+const QUOTA_DEFAULT = 5242880; // 5 MiB
+
+const JETPACK_DIR_BASENAME = "jetpack";
+
+Object.defineProperties(exports, {
+ storage: {
+ enumerable: true,
+ get: function() { return manager.root; },
+ set: function(value) { manager.root = value; }
+ },
+ quotaUsage: {
+ get: function() { return manager.quotaUsage; }
+ }
+});
+
+// A generic JSON store backed by a file on disk. This should be isolated
+// enough to move to its own module if need be...
+function JsonStore(options) {
+ this.filename = options.filename;
+ this.quota = options.quota;
+ this.writePeriod = options.writePeriod;
+ this.onOverQuota = options.onOverQuota;
+ this.onWrite = options.onWrite;
+
+ unload.ensure(this);
+
+ this.writeTimer = timer.setInterval(this.write.bind(this),
+ this.writePeriod);
+}
+
+JsonStore.prototype = {
+ // The store's root.
+ get root() {
+ return this.isRootInited ? this._root : {};
+ },
+
+ // Performs some type checking.
+ set root(val) {
+ let types = ["array", "boolean", "null", "number", "object", "string"];
+ if (types.indexOf(typeof(val)) < 0) {
+ throw new Error("storage must be one of the following types: " +
+ types.join(", "));
+ }
+ this._root = val;
+ return val;
+ },
+
+ // True if the root has ever been set (either via the root setter or by the
+ // backing file's having been read).
+ get isRootInited() {
+ return this._root !== undefined;
+ },
+
+ // Percentage of quota used, as a number [0, Inf). > 1 implies over quota.
+ // Undefined if there is no quota.
+ get quotaUsage() {
+ return this.quota > 0 ?
+ JSON.stringify(this.root).length / this.quota :
+ undefined;
+ },
+
+ // Removes the backing file and all empty subdirectories.
+ purge: function JsonStore_purge() {
+ try {
+ // This'll throw if the file doesn't exist.
+ file.remove(this.filename);
+ let parentPath = this.filename;
+ do {
+ parentPath = file.dirname(parentPath);
+ // This'll throw if the dir isn't empty.
+ file.rmdir(parentPath);
+ } while (file.basename(parentPath) !== JETPACK_DIR_BASENAME);
+ }
+ catch (err) {}
+ },
+
+ // Initializes the root by reading the backing file.
+ read: function JsonStore_read() {
+ try {
+ let str = file.read(this.filename);
+
+ // Ideally we'd log the parse error with console.error(), but logged
+ // errors cause tests to fail. Supporting "known" errors in the test
+ // harness appears to be non-trivial. Maybe later.
+ this.root = JSON.parse(str);
+ }
+ catch (err) {
+ this.root = {};
+ }
+ },
+
+ // If the store is under quota, writes the root to the backing file.
+ // Otherwise quota observers are notified and nothing is written.
+ write: function JsonStore_write() {
+ if (this.quotaUsage > 1)
+ this.onOverQuota(this);
+ else
+ this._write();
+ },
+
+ // Cleans up on unload. If unloading because of uninstall, the store is
+ // purged; otherwise it's written.
+ unload: function JsonStore_unload(reason) {
+ timer.clearInterval(this.writeTimer);
+ this.writeTimer = null;
+
+ if (reason === "uninstall")
+ this.purge();
+ else
+ this._write();
+ },
+
+ // True if the root is an empty object.
+ get _isEmpty() {
+ if (this.root && typeof(this.root) === "object") {
+ let empty = true;
+ for (let key in this.root) {
+ empty = false;
+ break;
+ }
+ return empty;
+ }
+ return false;
+ },
+
+ // Writes the root to the backing file, notifying write observers when
+ // complete. If the store is over quota or if it's empty and the store has
+ // never been written, nothing is written and write observers aren't notified.
+ _write: function JsonStore__write() {
+ // Don't write if the root is uninitialized or if the store is empty and the
+ // backing file doesn't yet exist.
+ if (!this.isRootInited || (this._isEmpty && !file.exists(this.filename)))
+ return;
+
+ // If the store is over quota, don't write. The current under-quota state
+ // should persist.
+ if (this.quotaUsage > 1)
+ return;
+
+ // Finally, write.
+ let stream = file.open(this.filename, "w");
+ try {
+ stream.writeAsync(JSON.stringify(this.root), function writeAsync(err) {
+ if (err)
+ console.error("Error writing simple storage file: " + this.filename);
+ else if (this.onWrite)
+ this.onWrite(this);
+ }.bind(this));
+ }
+ catch (err) {
+ // writeAsync closes the stream after it's done, so only close on error.
+ stream.close();
+ }
+ }
+};
+
+
+// This manages a JsonStore singleton and tailors its use to simple storage.
+// The root of the JsonStore is lazy-loaded: The backing file is only read the
+// first time the root's gotten.
+let manager = ({
+ jsonStore: null,
+
+ // The filename of the store, based on the profile dir and extension ID.
+ get filename() {
+ let storeFile = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("ProfD", Ci.nsIFile);
+ storeFile.append(JETPACK_DIR_BASENAME);
+ storeFile.append(jpSelf.id);
+ storeFile.append("simple-storage");
+ file.mkpath(storeFile.path);
+ storeFile.append("store.json");
+ return storeFile.path;
+ },
+
+ get quotaUsage() {
+ return this.jsonStore.quotaUsage;
+ },
+
+ get root() {
+ if (!this.jsonStore.isRootInited)
+ this.jsonStore.read();
+ return this.jsonStore.root;
+ },
+
+ set root(val) {
+ return this.jsonStore.root = val;
+ },
+
+ unload: function manager_unload() {
+ off(this);
+ },
+
+ new: function manager_constructor() {
+ let manager = Object.create(this);
+ unload.ensure(manager);
+
+ manager.jsonStore = new JsonStore({
+ filename: manager.filename,
+ writePeriod: prefs.get(WRITE_PERIOD_PREF, WRITE_PERIOD_DEFAULT),
+ quota: prefs.get(QUOTA_PREF, QUOTA_DEFAULT),
+ onOverQuota: emit.bind(null, exports, "OverQuota")
+ });
+
+ return manager;
+ }
+}).new();
+
+exports.on = on.bind(null, exports);
+exports.removeListener = function(type, listener) {
+ off(exports, type, listener);
+};
diff --git a/tools/addon-sdk-1.12/lib/sdk/system.js b/tools/addon-sdk-1.12/lib/sdk/system.js
new file mode 100644
index 0000000..1e374d3
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/system.js
@@ -0,0 +1,155 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Cc, Ci, CC } = require('chrome');
+const options = require('@loader/options');
+const file = require('./io/file');
+const runtime = require("./system/runtime");
+
+const appStartup = Cc['@mozilla.org/toolkit/app-startup;1'].
+ getService(Ci.nsIAppStartup);
+const appInfo = Cc["@mozilla.org/xre/app-info;1"].
+ getService(Ci.nsIXULAppInfo);
+const directoryService = Cc['@mozilla.org/file/directory_service;1'].
+ getService(Ci.nsIProperties);
+
+const PR_WRONLY = parseInt("0x02");
+const PR_CREATE_FILE = parseInt("0x08");
+const PR_APPEND = parseInt("0x10");
+const PR_TRUNCATE = parseInt("0x20");
+
+function openFile(path, mode) {
+ let file = Cc["@mozilla.org/file/local;1"].
+ createInstance(Ci.nsILocalFile);
+ file.initWithPath(path);
+ let stream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ stream.init(file, mode, -1, 0);
+ return stream
+}
+
+const { eAttemptQuit: E_ATTEMPT, eForceQuit: E_FORCE } = appStartup;
+
+/**
+ * Parsed JSON object that was passed via `cfx --static-args "{ foo: 'bar' }"`
+ */
+exports.staticArgs = options.staticArgs;
+
+/**
+ * Environment variables. Environment variables are non-enumerable properties
+ * of this object (key is name and value is value).
+ */
+exports.env = require('./system/environment').env;
+
+/**
+ * Ends the process with the specified `code`. If omitted, exit uses the
+ * 'success' code 0. To exit with failure use `1`.
+ * TODO: Improve platform to actually quit with an exit code.
+ */
+exports.exit = function exit(code) {
+ // This is used by 'cfx' to find out exit code.
+ if ('resultFile' in options && options.resultFile) {
+ let mode = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE;
+ let stream = openFile(options.resultFile, mode);
+ let status = code ? 'FAIL' : 'OK';
+ stream.write(status, status.length);
+ stream.flush();
+ stream.close();
+ }
+
+ appStartup.quit(code ? E_ATTEMPT : E_FORCE);
+};
+
+exports.stdout = new function() {
+ let write = dump
+ if ('logFile' in options && options.logFile) {
+ let mode = PR_WRONLY | PR_CREATE_FILE | PR_APPEND;
+ let stream = openFile(options.logFile, mode);
+ write = function write(data) {
+ let text = String(data);
+ stream.write(text, text.length);
+ stream.flush();
+ }
+ }
+ return Object.freeze({ write: write });
+};
+
+/**
+ * Returns a path of the system's or application's special directory / file
+ * associated with a given `id`. For list of possible `id`s please see:
+ * https://developer.mozilla.org/en/Code_snippets/File_I%2F%2FO#Getting_special_files
+ * http://mxr.mozilla.org/mozilla-central/source/xpcom/io/nsAppDirectoryServiceDefs.h
+ * @example
+ *
+ * // get firefox profile path
+ * let profilePath = require('system').pathFor('ProfD');
+ * // get OS temp files directory (/tmp)
+ * let temps = require('system').pathFor('TmpD');
+ * // get OS desktop path for an active user (~/Desktop on linux
+ * // or C:\Documents and Settings\username\Desktop on windows).
+ * let desktopPath = require('system').pathFor('Desk');
+ */
+exports.pathFor = function pathFor(id) {
+ return directoryService.get(id, Ci.nsIFile).path;
+};
+
+/**
+ * What platform you're running on (all lower case string).
+ * For possible values see:
+ * https://developer.mozilla.org/en/OS_TARGET
+ */
+exports.platform = runtime.OS.toLowerCase();
+
+/**
+ * What processor architecture you're running on:
+ * `'arm', 'ia32', or 'x64'`.
+ */
+exports.architecture = runtime.XPCOMABI.split('_')[0];
+
+/**
+ * What compiler used for build:
+ * `'msvc', 'n32', 'gcc2', 'gcc3', 'sunc', 'ibmc'...`
+ */
+exports.compiler = runtime.XPCOMABI.split('_')[1];
+
+/**
+ * The application's build ID/date, for example "2004051604".
+ */
+exports.build = appInfo.appBuildID;
+
+/**
+ * The XUL application's UUID.
+ * This has traditionally been in the form
+ * `{AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}` but for some applications it may
+ * be: "appname@vendor.tld".
+ */
+exports.id = appInfo.ID;
+
+/**
+ * The name of the application.
+ */
+exports.name = appInfo.name;
+
+/**
+ * The XUL application's version, for example "0.8.0+" or "3.7a1pre".
+ */
+exports.version = appInfo.version;
+
+/**
+ * XULRunner version.
+ */
+exports.platformVersion = appInfo.platformVersion;
+
+
+/**
+ * The name of the application vendor, for example "Mozilla".
+ */
+exports.vendor = appInfo.vendor;
diff --git a/tools/addon-sdk-1.12/lib/sdk/system/environment.js b/tools/addon-sdk-1.12/lib/sdk/system/environment.js
new file mode 100644
index 0000000..045bab1
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/system/environment.js
@@ -0,0 +1,58 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* 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": "stable"
+};
+
+const { Cc, Ci } = require('chrome');
+const { get, set, exists } = Cc['@mozilla.org/process/environment;1'].
+ getService(Ci.nsIEnvironment);
+
+exports.env = Proxy.create({
+ // XPCOM does not provides a way to enumerate environment variables, so we
+ // just don't support enumeration.
+ getPropertyNames: function() [],
+ getOwnPropertyNames: function() [],
+ enumerate: function() [],
+ keys: function() [],
+ // We do not support freezing, cause it would make it impossible to set new
+ // environment variables.
+ fix: function() undefined,
+ // We present all environment variables as own properties of this object,
+ // so we just delegate this call to `getOwnPropertyDescriptor`.
+ getPropertyDescriptor: function(name) this.getOwnPropertyDescriptor(name),
+ // If environment variable with this name is defined, we generate proprety
+ // descriptor for it, otherwise fall back to `undefined` so that for consumer
+ // this property does not exists.
+ getOwnPropertyDescriptor: function(name) {
+ return !exists(name) ? undefined : {
+ value: get(name),
+ enumerable: false, // Non-enumerable as we don't support enumeration.
+ configurable: true, // Configurable as it may be deleted.
+ writable: true // Writable as we do support set.
+ }
+ },
+
+ // New environment variables can be defined just by defining properties
+ // on this object.
+ defineProperty: function(name, { value }) set(name, value),
+ delete: function(name) set(name, null),
+
+ // We present all properties as own, there for we just delegate to `hasOwn`.
+ has: function(name) this.hasOwn(name),
+ // We do support checks for existence of an environment variable, via `in`
+ // operator on this object.
+ hasOwn: function(name) exists(name),
+
+ // On property get / set we do read / write appropriate environment variables,
+ // please note though, that variables with names of standard object properties
+ // intentionally (so that this behaves as normal object) can not be
+ // read / set.
+ get: function(proxy, name) Object.prototype[name] || get(name) || undefined,
+ set: function(proxy, name, value) Object.prototype[name] || set(name, value)
+});
diff --git a/tools/addon-sdk-1.12/lib/sdk/system/events.js b/tools/addon-sdk-1.12/lib/sdk/system/events.js
new file mode 100644
index 0000000..a75dddc
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/system/events.js
@@ -0,0 +1,113 @@
+/* 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 { Unknown } = require('../platform/xpcom');
+const { Class } = require('../core/heritage');
+const { ns } = require('../core/namespace');
+const { addObserver, removeObserver, notifyObservers } =
+ Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+
+const Subject = Class({
+ extends: Unknown,
+ initialize: function initialize(object) {
+ // Double-wrap the object and set a property identifying the
+ // wrappedJSObject as one of our wrappers to distinguish between
+ // subjects that are one of our wrappers (which we should unwrap
+ // when notifying our observers) and those that are real JS XPCOM
+ // components (which we should pass through unaltered).
+ this.wrappedJSObject = {
+ observersModuleSubjectWrapper: true,
+ object: object
+ };
+ },
+ getHelperForLanguage: function() {},
+ getInterfaces: function() {}
+});
+
+function emit(type, event) {
+ let subject = 'subject' in event ? Subject(event.subject) : null;
+ let data = 'data' in event ? event.data : null;
+ notifyObservers(subject, type, data);
+}
+exports.emit = emit;
+
+const Observer = Class({
+ extends: Unknown,
+ initialize: function initialize(listener) {
+ this.listener = listener;
+ },
+ interfaces: [ 'nsIObserver', 'nsISupportsWeakReference' ],
+ observe: function(subject, topic, data) {
+ // Extract the wrapped object for subjects that are one of our
+ // wrappers around a JS object. This way we support both wrapped
+ // subjects created using this module and those that are real
+ // XPCOM components.
+ if (subject && typeof(subject) == 'object' &&
+ ('wrappedJSObject' in subject) &&
+ ('observersModuleSubjectWrapper' in subject.wrappedJSObject))
+ subject = subject.wrappedJSObject.object;
+
+ try {
+ this.listener({
+ type: topic,
+ subject: subject,
+ data: data
+ });
+ }
+ catch (error) {
+ console.exception(error);
+ }
+ }
+});
+
+const subscribers = ns();
+
+function on(type, listener, strong) {
+ // Unless last optional argument is `true` we use a weak reference to a
+ // listener.
+ let weak = !strong;
+ // Take list of observers associated with given `listener` function.
+ let observers = subscribers(listener);
+ // If `observer` for the given `type` is not registered yet, then
+ // associate an `observer` and register it.
+ if (!(type in observers)) {
+ let observer = Observer(listener);
+ observers[type] = observer;
+ addObserver(observer, type, weak);
+ }
+}
+exports.on = on;
+
+function once(type, listener) {
+ // Note: this code assumes order in which listeners are called, which is fine
+ // as long as dispatch happens in same order as listener registration which
+ // is the case now. That being said we should be aware that this may break
+ // in a future if order will change.
+ on(type, listener);
+ on(type, function cleanup() {
+ off(type, listener);
+ off(type, cleanup);
+ }, true);
+}
+exports.once = once;
+
+function off(type, listener) {
+ // Take list of observers as with the given `listener`.
+ let observers = subscribers(listener);
+ // If `observer` for the given `type` is registered, then
+ // remove it & unregister.
+ if (type in observers) {
+ let observer = observers[type];
+ delete observers[type];
+ removeObserver(observer, type);
+ }
+}
+exports.off = off;
diff --git a/tools/addon-sdk-1.12/lib/sdk/system/globals.js b/tools/addon-sdk-1.12/lib/sdk/system/globals.js
new file mode 100644
index 0000000..df77295
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/system/globals.js
@@ -0,0 +1,54 @@
+/* vim:set ts=2 sw=2 sts=2 expandtab */
+/* 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"
+};
+
+let { Cc, Ci, CC } = require('chrome');
+let { PlainTextConsole } = require('../console/plain-text');
+let { stdout } = require('../system');
+let ScriptError = CC('@mozilla.org/scripterror;1', 'nsIScriptError');
+let consoleService = Cc['@mozilla.org/consoleservice;1'].getService().
+ QueryInterface(Ci.nsIConsoleService);
+
+// On windows dump does not writes into stdout so cfx can't read thous dumps.
+// To workaround this issue we write to a special file from which cfx will
+// read and print to the console.
+// For more details see: bug-673383
+exports.dump = stdout.write;
+
+// Bug 718230: We need to send console messages to stdout and JS Console
+function forsakenConsoleDump(msg, level) {
+ stdout.write(msg);
+
+ if (level === 'error') {
+ let error = ScriptError();
+ msg = msg.replace(/^error: /, '');
+ error.init(msg, null, null, 0, 0, 0, 'Add-on SDK');
+ consoleService.logMessage(error);
+ }
+ else
+ consoleService.logStringMessage(msg);
+};
+exports.console = new PlainTextConsole(forsakenConsoleDump);
+
+// Provide CommonJS `define` to allow authoring modules in a format that can be
+// loaded both into jetpack and into browser via AMD loaders.
+Object.defineProperty(exports, 'define', {
+ // `define` is provided as a lazy getter that binds below defined `define`
+ // function to the module scope, so that require, exports and module
+ // variables remain accessible.
+ configurable: true,
+ get: function() {
+ let sandbox = this;
+ return function define(factory) {
+ factory = Array.slice(arguments).pop();
+ factory.call(sandbox, sandbox.require, sandbox.exports, sandbox.module);
+ }
+ }
+});
diff --git a/tools/addon-sdk-1.12/lib/sdk/system/runtime.js b/tools/addon-sdk-1.12/lib/sdk/system/runtime.js
new file mode 100644
index 0000000..e64b663
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/system/runtime.js
@@ -0,0 +1,19 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
+
+exports.inSafeMode = runtime.inSafeMode;
+exports.OS = runtime.OS;
+exports.processType = runtime.processType;
+exports.widgetToolkit = runtime.widgetToolkit;
+exports.XPCOMABI = runtime.XPCOMABI;
diff --git a/tools/addon-sdk-1.12/lib/sdk/system/unload.js b/tools/addon-sdk-1.12/lib/sdk/system/unload.js
new file mode 100644
index 0000000..4d2575b
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/system/unload.js
@@ -0,0 +1,82 @@
+/* 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/. */
+
+// Parts of this module were taken from narwhal:
+//
+// http://narwhaljs.org
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { on, off } = require('./events');
+const unloadSubject = require('@loader/unload');
+
+const observers = [];
+const unloaders = [];
+
+var when = exports.when = function when(observer) {
+ if (observers.indexOf(observer) != -1)
+ return;
+ observers.unshift(observer);
+};
+
+var ensure = exports.ensure = function ensure(obj, destructorName) {
+ if (!destructorName)
+ destructorName = "unload";
+ if (!(destructorName in obj))
+ throw new Error("object has no '" + destructorName + "' property");
+
+ let called = false;
+ let originalDestructor = obj[destructorName];
+
+ function unloadWrapper(reason) {
+ if (!called) {
+ called = true;
+ let index = unloaders.indexOf(unloadWrapper);
+ if (index == -1)
+ throw new Error("internal error: unloader not found");
+ unloaders.splice(index, 1);
+ originalDestructor.call(obj, reason);
+ originalDestructor = null;
+ destructorName = null;
+ obj = null;
+ }
+ };
+
+ // TODO: Find out why the order is inverted here. It seems that
+ // it may be causing issues!
+ unloaders.push(unloadWrapper);
+
+ obj[destructorName] = unloadWrapper;
+};
+
+function unload(reason) {
+ observers.forEach(function(observer) {
+ try {
+ observer(reason);
+ }
+ catch (error) {
+ console.exception(error);
+ }
+ });
+}
+
+when(function(reason) {
+ unloaders.slice().forEach(function(unloadWrapper) {
+ unloadWrapper(reason);
+ });
+});
+
+on('sdk:loader:destroy', function onunload({ subject, data: reason }) {
+ // If this loader is unload then `subject.wrappedJSObject` will be
+ // `destructor`.
+ if (subject.wrappedJSObject === unloadSubject) {
+ off('sdk:loader:destroy', onunload);
+ unload(reason);
+ }
+// Note that we use strong reference to listener here to make sure it's not
+// GC-ed, which may happen otherwise since nothing keeps reference to `onunolad`
+// function.
+}, true);
diff --git a/tools/addon-sdk-1.12/lib/sdk/system/xul-app.js b/tools/addon-sdk-1.12/lib/sdk/system/xul-app.js
new file mode 100644
index 0000000..6e8b4e8
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/system/xul-app.js
@@ -0,0 +1,67 @@
+/* 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 {Cc, Ci} = require("chrome");
+
+var appInfo = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULAppInfo);
+
+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}"
+};
+
+var is = exports.is = function is(name) {
+ if (!(name in ids))
+ throw new Error("Unkown Mozilla Application: " + name);
+ return ID == ids[name];
+};
+
+var isOneOf = exports.isOneOf = function isOneOf(names) {
+ for (var i = 0; i < names.length; i++)
+ if (is(names[i]))
+ return true;
+ return false;
+};
+
+/**
+ * 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) {
+ var vc = Cc["@mozilla.org/xpcom/version-comparator;1"]
+ .getService(Ci.nsIVersionComparator);
+ return (vc.compare(version, lowInclusive) >= 0) &&
+ (vc.compare(version, highExclusive) < 0);
+}
+
diff --git a/tools/addon-sdk-1.12/lib/sdk/tabs.js b/tools/addon-sdk-1.12/lib/sdk/tabs.js
new file mode 100644
index 0000000..f006073
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/tabs.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';
+
+module.metadata = {
+ 'stability': 'stable'
+};
+
+module.exports = require('./tabs/tabs');
diff --git a/tools/addon-sdk-1.12/lib/sdk/tabs/common.js b/tools/addon-sdk-1.12/lib/sdk/tabs/common.js
new file mode 100644
index 0000000..dd5bb22
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/tabs/common.js
@@ -0,0 +1,23 @@
+/* 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 { validateOptions } = require('../deprecated/api-utils');
+
+function Options(options) {
+ if ('string' === typeof options)
+ options = { url: options };
+
+ return validateOptions(options, {
+ url: { is: ["string"] },
+ inBackground: { is: ["undefined", "boolean"] },
+ isPinned: { is: ["undefined", "boolean"] },
+ onOpen: { is: ["undefined", "function"] },
+ onClose: { is: ["undefined", "function"] },
+ onReady: { is: ["undefined", "function"] },
+ onActivate: { is: ["undefined", "function"] },
+ onDeactivate: { is: ["undefined", "function"] }
+ });
+}
+exports.Options = Options;
diff --git a/tools/addon-sdk-1.12/lib/sdk/tabs/events.js b/tools/addon-sdk-1.12/lib/sdk/tabs/events.js
new file mode 100644
index 0000000..eb99f40
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/tabs/events.js
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const ON_PREFIX = "on";
+const TAB_PREFIX = "Tab";
+
+const EVENTS = {
+ ready: "DOMContentLoaded",
+ open: "TabOpen",
+ close: "TabClose",
+ activate: "TabSelect",
+ deactivate: null,
+ pinned: "TabPinned",
+ unpinned: "TabUnpinned"
+}
+exports.EVENTS = EVENTS;
+
+Object.keys(EVENTS).forEach(function(name) {
+ EVENTS[name] = {
+ name: name,
+ listener: ON_PREFIX + name.charAt(0).toUpperCase() + name.substr(1),
+ dom: EVENTS[name]
+ }
+});
diff --git a/tools/addon-sdk-1.12/lib/sdk/tabs/helpers.js b/tools/addon-sdk-1.12/lib/sdk/tabs/helpers.js
new file mode 100644
index 0000000..aaff5cb
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/tabs/helpers.js
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+const { getTabForContentWindow } = require('./utils');
+const { Tab } = require('./tab');
+
+function getTabForWindow(win) {
+ let tab = getTabForContentWindow(win);
+ // We were unable to find the related tab!
+ if (!tab)
+ return null;
+
+ return Tab({ tab: tab });
+}
+exports.getTabForWindow = getTabForWindow;
diff --git a/tools/addon-sdk-1.12/lib/sdk/tabs/namespace.js b/tools/addon-sdk-1.12/lib/sdk/tabs/namespace.js
new file mode 100644
index 0000000..d359cee
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/tabs/namespace.js
@@ -0,0 +1,9 @@
+/* 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 { ns } = require('../core/namespace');
+
+exports.tabsNS = ns();
+exports.tabNS = ns();
diff --git a/tools/addon-sdk-1.12/lib/sdk/tabs/observer.js b/tools/addon-sdk-1.12/lib/sdk/tabs/observer.js
new file mode 100644
index 0000000..19a048c
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/tabs/observer.js
@@ -0,0 +1,99 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 { EventEmitterTrait: EventEmitter } = require("../deprecated/events");
+const { DOMEventAssembler } = require("../deprecated/events/assembler");
+const { Trait } = require("../deprecated/light-traits");
+const { getActiveTab, getTabs, getTabContainer } = require("./utils");
+const { browserWindowIterator } = require("../deprecated/window-utils");
+const { isBrowser } = require('../window/utils');
+const { observer: windowObserver } = require("../windows/observer");
+
+const EVENTS = {
+ "TabOpen": "open",
+ "TabClose": "close",
+ "TabSelect": "select",
+ "TabMove": "move",
+ "TabPinned": "pinned",
+ "TabUnpinned": "unpinned"
+};
+
+
+// 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,
+ /**
+ * 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);
+ }
+});
+
+// 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 each (let window in browserWindowIterator()) onWindowOpen(window);
+
+exports.observer = observer;
diff --git a/tools/addon-sdk-1.12/lib/sdk/tabs/tab-fennec.js b/tools/addon-sdk-1.12/lib/sdk/tabs/tab-fennec.js
new file mode 100644
index 0000000..42bf91b
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/tabs/tab-fennec.js
@@ -0,0 +1,141 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+const { Cc, Ci } = require('chrome');
+const { Class } = require('../core/heritage');
+const { tabNS } = require('./namespace');
+const { EventTarget } = require('../event/target');
+const { activateTab, getTabTitle, setTabTitle, closeTab, getTabURL,
+ setTabURL, getOwnerWindow, getTabContentType } = require('./utils');
+const { emit } = require('../event/core');
+const { when: unload } = require('../system/unload');
+
+const { EVENTS } = require('./events');
+const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec';
+
+const Tab = Class({
+ extends: EventTarget,
+ initialize: function initialize(options) {
+ options = options.tab ? options : { tab: options };
+
+ EventTarget.prototype.initialize.call(this, options);
+ let tabInternals = tabNS(this);
+
+ tabInternals.window = options.window || getOwnerWindow(options.tab);
+ tabInternals.tab = options.tab;
+ },
+
+ /**
+ * The title of the page currently loaded in the tab.
+ * Changing this property changes an actual title.
+ * @type {String}
+ */
+ get title() getTabTitle(tabNS(this).tab),
+ set title(title) setTabTitle(tabNS(this).tab, title),
+
+ /**
+ * Location of the page currently loaded in this tab.
+ * Changing this property will loads page under under the specified location.
+ * @type {String}
+ */
+ get url() getTabURL(tabNS(this).tab),
+ set url(url) setTabURL(tabNS(this).tab, url),
+
+ /**
+ * URI of the favicon for the page currently loaded in this tab.
+ * @type {String}
+ */
+ get favicon() {
+ // TODO: provide the real favicon when it is available
+ console.error(ERR_FENNEC_MSG);
+
+ // return 16x16 blank default
+ return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAEklEQVQ4jWNgGAWjYBSMAggAAAQQAAF/TXiOAAAAAElFTkSuQmCC';
+ },
+
+ getThumbnail: function() {
+ // TODO: implement!
+ console.error(ERR_FENNEC_MSG);
+
+ // return 80x45 blank default
+ return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAAtCAYAAAA5reyyAAAAJElEQVRoge3BAQEAAACCIP+vbkhAAQAAAAAAAAAAAAAAAADXBjhtAAGQ0AF/AAAAAElFTkSuQmCC';
+ },
+
+ /**
+ * The index of the tab relative to other tabs in the application window.
+ * Changing this property will change order of the actual position of the tab.
+ * @type {Number}
+ */
+ get index() {
+ let tabs = tabNS(this).window.BrowserApp.tabs;
+ let tab = tabNS(this).tab;
+ for (var i = tabs.length; i >= 0; i--) {
+ if (tabs[i] === tab)
+ return i;
+ }
+ return null;
+ },
+ set index(value) {
+ console.error(ERR_FENNEC_MSG); // TODO
+ },
+
+ /**
+ * Whether or not tab is pinned (Is an app-tab).
+ * @type {Boolean}
+ */
+ get isPinned() {
+ console.error(ERR_FENNEC_MSG); // TODO
+ return false; // TODO
+ },
+ pin: function pin() {
+ console.error(ERR_FENNEC_MSG); // TODO
+ },
+ unpin: function unpin() {
+ console.error(ERR_FENNEC_MSG); // TODO
+ },
+
+ /**
+ * Returns the MIME type that the document loaded in the tab is being
+ * rendered as.
+ * @type {String}
+ */
+ get contentType() getTabContentType(tabNS(this).tab),
+
+ /**
+ * Create a worker for this tab, first argument is options given to Worker.
+ * @type {Worker}
+ */
+ attach: function attach(options) {
+ // BUG 792946 https://bugzilla.mozilla.org/show_bug.cgi?id=792946
+ // TODO: fix this circular dependency
+ let { Worker } = require('./worker');
+ return Worker(options, tabNS(this).tab.browser.contentWindow);
+ },
+
+ /**
+ * Make this tab active.
+ */
+ activate: function activate() {
+ activateTab(tabNS(this).tab, tabNS(this).window);
+ },
+
+ /**
+ * Close the tab
+ */
+ close: function close(callback) {
+ if (callback)
+ this.once(EVENTS.close.name, callback);
+
+ closeTab(tabNS(this).tab);
+ },
+
+ /**
+ * Reload the tab
+ */
+ reload: function reload() {
+ tabNS(this).tab.browser.reload();
+ }
+});
+exports.Tab = Tab;
diff --git a/tools/addon-sdk-1.12/lib/sdk/tabs/tab-firefox.js b/tools/addon-sdk-1.12/lib/sdk/tabs/tab-firefox.js
new file mode 100644
index 0000000..5a36302
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/tabs/tab-firefox.js
@@ -0,0 +1,201 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+const { Trait } = require("../deprecated/traits");
+const { EventEmitter } = require("../deprecated/events");
+const { defer } = require("../lang/functional");
+const { EVENTS } = require("./events");
+const { getThumbnailURIForWindow } = require("../content/thumbnail");
+const { getFaviconURIForLocation } = require("../io/data");
+const { activateTab, getOwnerWindow, getBrowserForTab, getTabTitle, setTabTitle,
+ getTabURL, setTabURL, getTabContentType } = require('./utils');
+
+// Array of the inner instances of all the wrapped tabs.
+const TABS = [];
+
+/**
+ * Trait used to create tab wrappers.
+ */
+const TabTrait = Trait.compose(EventEmitter, {
+ on: Trait.required,
+ _emit: Trait.required,
+ /**
+ * Tab DOM element that is being wrapped.
+ */
+ _tab: null,
+ /**
+ * Window wrapper whose tab this object represents.
+ */
+ window: null,
+ constructor: function Tab(options) {
+ this._onReady = this._onReady.bind(this);
+ this._tab = options.tab;
+ let window = this.window = options.window || getOwnerWindow(this._tab);
+
+ // Setting event listener if was passed.
+ for each (let type in EVENTS) {
+ let listener = options[type.listener];
+ if (listener)
+ this.on(type.name, options[type.listener]);
+ if ('ready' != type.name) // window spreads this event.
+ window.tabs.on(type.name, this._onEvent.bind(this, type.name));
+ }
+
+ this.on(EVENTS.close.name, this.destroy.bind(this));
+ this._browser.addEventListener(EVENTS.ready.dom, this._onReady, true);
+
+ if (options.isPinned)
+ this.pin();
+
+ // Since we will have to identify tabs by a DOM elements facade function
+ // is used as constructor that collects all the instances and makes sure
+ // that they more then one wrapper is not created per tab.
+ return this;
+ },
+ destroy: function destroy() {
+ this._removeAllListeners();
+ this._browser.removeEventListener(EVENTS.ready.dom, this._onReady, true);
+ },
+
+ /**
+ * Internal listener that emits public event 'ready' when the page of this
+ * tab is loaded.
+ */
+ _onReady: function _onReady(event) {
+ // IFrames events will bubble so we need to ignore those.
+ if (event.target == this._contentDocument)
+ this._emit(EVENTS.ready.name, this._public);
+ },
+ /**
+ * Internal tab event router. Window will emit tab related events for all it's
+ * tabs, this listener will propagate all the events for this tab to it's
+ * listeners.
+ */
+ _onEvent: function _onEvent(type, tab) {
+ if (tab == this._public)
+ this._emit(type, tab);
+ },
+ /**
+ * Browser DOM element where page of this tab is currently loaded.
+ */
+ get _browser() getBrowserForTab(this._tab),
+ /**
+ * Window DOM element containing this tab.
+ */
+ get _window() getOwnerWindow(this._tab),
+ /**
+ * Document object of the page that is currently loaded in this tab.
+ */
+ get _contentDocument() this._browser.contentDocument,
+ /**
+ * Window object of the page that is currently loaded in this tab.
+ */
+ get _contentWindow() this._browser.contentWindow,
+
+ /**
+ * The title of the page currently loaded in the tab.
+ * Changing this property changes an actual title.
+ * @type {String}
+ */
+ get title() getTabTitle(this._tab),
+ set title(title) setTabTitle(this._tab, title),
+
+ /**
+ * Returns the MIME type that the document loaded in the tab is being
+ * rendered as.
+ * @type {String}
+ */
+ get contentType() getTabContentType(this._tab),
+
+ /**
+ * Location of the page currently loaded in this tab.
+ * Changing this property will loads page under under the specified location.
+ * @type {String}
+ */
+ get url() getTabURL(this._tab),
+ set url(url) setTabURL(this._tab, url),
+ /**
+ * URI of the favicon for the page currently loaded in this tab.
+ * @type {String}
+ */
+ get favicon() getFaviconURIForLocation(this.url),
+ /**
+ * The CSS style for the tab
+ */
+ get style() null, // TODO
+ /**
+ * The index of the tab relative to other tabs in the application window.
+ * Changing this property will change order of the actual position of the tab.
+ * @type {Number}
+ */
+ get index()
+ this._window.gBrowser.getBrowserIndexForDocument(this._contentDocument),
+ set index(value) this._window.gBrowser.moveTabTo(this._tab, value),
+ /**
+ * Thumbnail data URI of the page currently loaded in this tab.
+ * @type {String}
+ */
+ getThumbnail: function getThumbnail()
+ getThumbnailURIForWindow(this._contentWindow),
+ /**
+ * Whether or not tab is pinned (Is an app-tab).
+ * @type {Boolean}
+ */
+ get isPinned() this._tab.pinned,
+ pin: function pin() {
+ this._window.gBrowser.pinTab(this._tab);
+ },
+ unpin: function unpin() {
+ this._window.gBrowser.unpinTab(this._tab);
+ },
+
+ /**
+ * Create a worker for this tab, first argument is options given to Worker.
+ * @type {Worker}
+ */
+ attach: function attach(options) {
+ // BUG 792946 https://bugzilla.mozilla.org/show_bug.cgi?id=792946
+ // TODO: fix this circular dependency
+ let { Worker } = require('./worker');
+ return Worker(options, this._contentWindow);
+ },
+
+ /**
+ * Make this tab active.
+ * Please note: That this function is called asynchronous since in E10S that
+ * will be the case. Besides this function is called from a constructor where
+ * we would like to return instance before firing a 'TabActivated' event.
+ */
+ activate: defer(function activate() {
+ activateTab(this._tab);
+ }),
+ /**
+ * Close the tab
+ */
+ close: function close(callback) {
+ if (callback)
+ this.once(EVENTS.close.name, callback);
+ this._window.gBrowser.removeTab(this._tab);
+ },
+ /**
+ * Reload the tab
+ */
+ reload: function reload() {
+ this._window.gBrowser.reloadTab(this._tab);
+ }
+});
+
+function Tab(options) {
+ let chromeTab = options.tab;
+ for each (let tab in TABS) {
+ if (chromeTab == tab._tab)
+ return tab._public;
+ }
+ let tab = TabTrait(options);
+ TABS.push(tab);
+ return tab._public;
+}
+Tab.prototype = TabTrait.prototype;
+exports.Tab = Tab;
diff --git a/tools/addon-sdk-1.12/lib/sdk/tabs/tab.js b/tools/addon-sdk-1.12/lib/sdk/tabs/tab.js
new file mode 100644
index 0000000..420baee
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/tabs/tab.js
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+module.metadata = {
+ 'stability': 'unstable'
+};
+
+if (require('../system/xul-app').is('Firefox')) {
+ module.exports = require('./tab-firefox');
+}
+else if (require('../system/xul-app').is('Fennec')) {
+ module.exports = require('./tab-fennec');
+}
+else {
+ throw new Error([
+ 'The tabs module currently supports only Firefox & Fennec. In the future',
+ ' we would like it to support other applications, however.'
+ ].join(''));
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/tabs/tabs-firefox.js b/tools/addon-sdk-1.12/lib/sdk/tabs/tabs-firefox.js
new file mode 100644
index 0000000..92834c2
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/tabs/tabs-firefox.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';
+
+// TODO: BUG 792670 - remove dependency below
+const { browserWindows } = require('../windows');
+const { tabs } = require('../windows/tabs-firefox');
+
+Object.defineProperties(tabs, {
+ open: { value: function open(options) {
+ if (options.inNewWindow)
+ // `tabs` option is under review and may be removed.
+ return browserWindows.open({ tabs: [ options ] });
+ // Open in active window if new window was not required.
+ return browserWindows.activeWindow.tabs.open(options);
+ }}
+});
+
+// Workaround for bug 674195. Freezing objects from other compartments fail,
+// so we use `Object.freeze` from the same component as objects
+// `hasOwnProperty`. Since `hasOwnProperty` here will be from other component
+// we override it to support our workaround.
+module.exports = Object.create(tabs, {
+ isPrototypeOf: { value: Object.prototype.isPrototypeOf }
+});
diff --git a/tools/addon-sdk-1.12/lib/sdk/tabs/tabs.js b/tools/addon-sdk-1.12/lib/sdk/tabs/tabs.js
new file mode 100644
index 0000000..fcb3868
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/tabs/tabs.js
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+module.metadata = {
+ 'stability': 'unstable'
+};
+
+if (require('../system/xul-app').is('Firefox')) {
+ module.exports = require('./tabs-firefox');
+}
+else if (require('../system/xul-app').is('Fennec')) {
+ module.exports = require('../windows/tabs-fennec').tabs;
+}
+else {
+ throw new Error([
+ 'The tabs module currently supports only Firefox & Fennec. In the future',
+ ' we would like it to support other applications, however.'
+ ].join(''));
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/tabs/utils.js b/tools/addon-sdk-1.12/lib/sdk/tabs/utils.js
new file mode 100644
index 0000000..d923e4b
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/tabs/utils.js
@@ -0,0 +1,222 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+module.metadata = {
+ 'stability': 'unstable'
+};
+
+const { defer } = require("../lang/functional");
+const { windows } = require('../window/utils');
+const { Ci } = require('chrome');
+
+function activateTab(tab, window) {
+ let gBrowser = getTabBrowserForTab(tab);
+
+ // normal case
+ if (gBrowser) {
+ gBrowser.selectedTab = tab;
+ }
+ // fennec ?
+ else if (window && window.BrowserApp) {
+ window.BrowserApp.selectTab(tab);
+ }
+ return null;
+}
+exports.activateTab = activateTab;
+
+function getTabBrowser(window) {
+ return window.gBrowser;
+}
+exports.getTabBrowser = getTabBrowser;
+
+function getTabContainer(window) {
+ return getTabBrowser(window).tabContainer;
+}
+exports.getTabContainer = getTabContainer;
+
+function getTabs(window) {
+ // fennec
+ if (window.BrowserApp)
+ return window.BrowserApp.tabs;
+
+ // firefox - default
+ return Array.slice(getTabContainer(window).children);
+}
+exports.getTabs = getTabs;
+
+function getActiveTab(window) {
+ return window.gBrowser.selectedTab;
+}
+exports.getActiveTab = getActiveTab;
+
+function getOwnerWindow(tab) {
+ // normal case
+ if (tab.ownerDocument)
+ return tab.ownerDocument.defaultView;
+
+ // try fennec case
+ return getWindowHoldingTab(tab);
+}
+exports.getOwnerWindow = getOwnerWindow;
+
+// fennec
+function getWindowHoldingTab(rawTab) {
+ for each (let window in windows()) {
+ // this function may be called when not using fennec
+ if (!window.BrowserApp)
+ continue;
+
+ for each (let tab in window.BrowserApp.tabs) {
+ if (tab === rawTab)
+ return window;
+ }
+ }
+
+ return null;
+}
+
+function openTab(window, url, options) {
+ options = options || {};
+
+ // fennec?
+ if (window.BrowserApp) {
+ return window.BrowserApp.addTab(url, {
+ selected: options.inBackground ? false : true,
+ pinned: options.isPinned || false
+ });
+ }
+ return window.gBrowser.addTab(url);
+};
+exports.openTab = openTab;
+
+function isTabOpen(tab) {
+ // try normal case then fennec case
+ return !!((tab.linkedBrowser) || getWindowHoldingTab(tab));
+}
+exports.isTabOpen = isTabOpen;
+
+function closeTab(tab) {
+ let gBrowser = getTabBrowserForTab(tab);
+ // normal case?
+ if (gBrowser)
+ return gBrowser.removeTab(tab);
+
+ let window = getWindowHoldingTab(tab);
+ // fennec?
+ if (window && window.BrowserApp)
+ return window.BrowserApp.closeTab(tab);
+ return null;
+}
+exports.closeTab = closeTab;
+
+function getURI(tab) {
+ if (tab.browser) // fennec
+ return tab.browser.currentURI.spec;
+ return tab.linkedBrowser.currentURI.spec;
+}
+exports.getURI = getURI;
+
+function getTabBrowserForTab(tab) {
+ let outerWin = getOwnerWindow(tab);
+ if (outerWin)
+ return getOwnerWindow(tab).gBrowser;
+ return null;
+}
+exports.getTabBrowserForTab = getTabBrowserForTab;
+
+function getBrowserForTab(tab) {
+ if (tab.browser) // fennec
+ return tab.browser;
+
+ return tab.linkedBrowser;
+}
+exports.getBrowserForTab = getBrowserForTab;
+
+function getTabTitle(tab) {
+ return getBrowserForTab(tab).contentDocument.title || tab.label || "";
+}
+exports.getTabTitle = getTabTitle;
+
+function setTabTitle(tab, title) {
+ title = String(title);
+ if (tab.browser)
+ tab.browser.contentDocument.title = title;
+ tab.label = String(title);
+}
+exports.setTabTitle = setTabTitle;
+
+function getTabContentWindow(tab) {
+ return getBrowserForTab(tab).contentWindow;
+}
+exports.getTabContentWindow = getTabContentWindow;
+
+function getTabForContentWindow(window) {
+ // Retrieve the topmost frame container. It can be either <xul:browser>,
+ // <xul:iframe/> or <html:iframe/>. But in our case, it should be xul:browser.
+ let browser = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ // Is null for toplevel documents
+ if (!browser)
+ return false;
+ // Retrieve the owner window, should be browser.xul one
+ let chromeWindow = browser.ownerDocument.defaultView;
+
+ // Ensure that it is top-level browser window.
+ // We need extra checks because of Mac hidden window that has a broken
+ // `gBrowser` global attribute.
+ if ('gBrowser' in chromeWindow && chromeWindow.gBrowser &&
+ 'browsers' in chromeWindow.gBrowser) {
+ // Looks like we are on Firefox Desktop
+ // Then search for the position in tabbrowser in order to get the tab object
+ let browsers = chromeWindow.gBrowser.browsers;
+ let i = browsers.indexOf(browser);
+ if (i !== -1)
+ return chromeWindow.gBrowser.tabs[i];
+ return null;
+ }
+ else if ('BrowserApp' in chromeWindow) {
+ // Looks like we are on Firefox Mobile
+ return chromeWindow.BrowserApp.getTabForWindow(window)
+ }
+
+ return null;
+}
+exports.getTabForContentWindow = getTabForContentWindow;
+
+function getTabURL(tab) {
+ if (tab.browser) // fennec
+ return String(tab.browser.currentURI.spec);
+ return String(getBrowserForTab(tab).currentURI.spec);
+}
+exports.getTabURL = getTabURL;
+
+function setTabURL(tab, url) {
+ url = String(url);
+ if (tab.browser)
+ return tab.browser.loadURI(url);
+ return getBrowserForTab(tab).loadURI(url);
+}
+// "TabOpen" event is fired when it's still "about:blank" is loaded in the
+// changing `location` property of the `contentDocument` has no effect since
+// seems to be either ignored or overridden by internal listener, there for
+// location change is enqueued for the next turn of event loop.
+exports.setTabURL = defer(setTabURL);
+
+function getTabContentType(tab) {
+ return getBrowserForTab(tab).contentDocument.contentType;
+}
+exports.getTabContentType = getTabContentType;
+
+function getSelectedTab(window) {
+ if (window.BrowserApp) // fennec?
+ return window.BrowserApp.selectedTab;
+ if (window.gBrowser)
+ return window.gBrowser.selectedTab;
+ return null;
+}
+exports.getSelectedTab = getSelectedTab;
diff --git a/tools/addon-sdk-1.12/lib/sdk/tabs/worker.js b/tools/addon-sdk-1.12/lib/sdk/tabs/worker.js
new file mode 100644
index 0000000..d2ba336
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/tabs/worker.js
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+const ContentWorker = require('../content/worker').Worker;
+
+function Worker(options, window) {
+ options.window = window;
+
+ let worker = ContentWorker(options);
+ worker.once("detach", function detach() {
+ worker.destroy();
+ });
+ return worker;
+}
+exports.Worker = Worker; \ No newline at end of file
diff --git a/tools/addon-sdk-1.12/lib/sdk/test.js b/tools/addon-sdk-1.12/lib/sdk/test.js
new file mode 100644
index 0000000..0a322d6
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/test.js
@@ -0,0 +1,112 @@
+/* vim:ts=2:sts=2:sw=2:
+ * 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 BaseAssert = require("./test/assert").Assert;
+const { isFunction, isObject } = require("./lang/type");
+
+function extend(target) {
+ let descriptor = {}
+ Array.slice(arguments, 1).forEach(function(source) {
+ Object.getOwnPropertyNames(source).forEach(function onEach(name) {
+ descriptor[name] = Object.getOwnPropertyDescriptor(source, name);
+ });
+ });
+ return Object.create(target, descriptor);
+}
+
+/**
+ * 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
+ * all the test names.
+ */
+function defineTestSuite(target, suite, prefix) {
+ prefix = prefix || "";
+ // If suite defines `Assert` that's what `assert` object have to be created
+ // from and passed to a test function (This allows custom assertion functions)
+ // See for details: http://wiki.commonjs.org/wiki/Unit_Testing/1.1
+ let Assert = suite.Assert || BaseAssert;
+ // Going through each item in the test suite and wrapping it into a
+ // Jetpack test format.
+ Object.keys(suite).forEach(function(key) {
+ // If name starts with test then it's a test function or suite.
+ if (key.indexOf("test") === 0) {
+ let test = suite[key];
+
+ // For each test function so we create a wrapper test function in a
+ // jetpack format and copy that to a `target` exports.
+ if (isFunction(test)) {
+
+ // Since names of the test may match across suites we use full object
+ // path as a name to avoid overriding same function.
+ target[prefix + key] = function(options) {
+
+ // Creating `assert` functions for this test.
+ let assert = Assert(options);
+
+ // 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.
+ 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();
+ });
+ }
+
+ // Otherwise CommonJS test is synchronous so we call it only with
+ // one argument.
+ else {
+ test(assert);
+ }
+ }
+ }
+
+ // If it's an object then it's a test suite containing test function
+ // and / or nested test suites. In that case we just extend prefix used
+ // and call this function to copy and wrap tests from nested suite.
+ else if (isObject(test)) {
+ // We need to clone `tests` instead of modifying it, since it's very
+ // likely that it is frozen (usually test suites imported modules).
+ test = extend(Object.prototype, test, {
+ Assert: test.Assert || Assert
+ });
+ defineTestSuite(target, test, prefix + key + ".");
+ }
+ }
+ });
+}
+
+/**
+ * This function is a CommonJS test runner function, but since Jetpack test
+ * runner and test format is different from CommonJS this function shims given
+ * `exports` with all its tests into a Jetpack test format so that the built-in
+ * test runner will be able to run CommonJS test without manual changes.
+ */
+exports.run = function run(exports) {
+
+ // We can't leave old properties on exports since those are test in a CommonJS
+ // format that why we move everything to a new `suite` object.
+ let suite = {};
+ Object.keys(exports).forEach(function(key) {
+ suite[key] = exports[key];
+ delete exports[key];
+ });
+
+ // Now we wrap all the CommonJS tests to a Jetpack format and define
+ // those to a given `exports` object since that where jetpack test runner
+ // will look for them.
+ defineTestSuite(exports, suite);
+};
diff --git a/tools/addon-sdk-1.12/lib/sdk/test/assert.js b/tools/addon-sdk-1.12/lib/sdk/test/assert.js
new file mode 100644
index 0000000..5eca7d4
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/test/assert.js
@@ -0,0 +1,349 @@
+/* vim:ts=2:sts=2:sw=2:
+ * 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 { isFunction, isNull, isObject, isString,
+ isRegExp, isArray, isDate, isPrimitive,
+ isUndefined, instanceOf, source } = require("../lang/type");
+
+/**
+ * The `AssertionError` is defined in assert.
+ * @extends Error
+ * @example
+ * new assert.AssertionError({
+ * message: message,
+ * actual: actual,
+ * expected: expected
+ * })
+ */
+function AssertionError(options) {
+ let assertionError = Object.create(AssertionError.prototype);
+
+ if (isString(options))
+ options = { message: options };
+ if ("actual" in options)
+ assertionError.actual = options.actual;
+ if ("expected" in options)
+ assertionError.expected = options.expected;
+ if ("operator" in options)
+ assertionError.operator = options.operator;
+
+ assertionError.message = options.message;
+ assertionError.stack = new Error().stack;
+ return assertionError;
+}
+AssertionError.prototype = Object.create(Error.prototype, {
+ constructor: { value: AssertionError },
+ name: { value: "AssertionError", enumerable: true },
+ toString: { value: function toString() {
+ let value;
+ if (this.message) {
+ value = this.name + " : " + this.message;
+ }
+ else {
+ value = [
+ this.name + " : ",
+ source(this.expected),
+ this.operator,
+ source(this.actual)
+ ].join(" ");
+ }
+ return value;
+ }}
+});
+exports.AssertionError = AssertionError;
+
+function Assert(logger) {
+ return Object.create(Assert.prototype, { _log: { value: logger }});
+}
+Assert.prototype = {
+ fail: function fail(e) {
+ if (!e || typeof(e) !== 'object') {
+ this._log.fail(e);
+ return;
+ }
+ let message = e.message;
+ if ('operator' in e) {
+ message += [
+ " -",
+ source(e.expected),
+ e.operator,
+ source(e.actual)
+ ].join(" ");
+ }
+ this._log.fail(message);
+ },
+ pass: function pass(message) {
+ this._log.pass(message);
+ },
+ error: function error(e) {
+ this._log.exception(e);
+ },
+ ok: function ok(value, message) {
+ if (!!!value) {
+ this.fail({
+ actual: value,
+ expected: true,
+ message: message,
+ operator: "=="
+ });
+ }
+ else {
+ this.pass(message);
+ }
+ },
+
+ /**
+ * 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);
+ }
+ else {
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "=="
+ });
+ }
+ },
+
+ /**
+ * 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);
+ }
+ else {
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "!=",
+ });
+ }
+ },
+
+ /**
+ * 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);
+ }
+ else {
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "deepEqual"
+ });
+ }
+ },
+
+ /**
+ * 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);
+ }
+ else {
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "notDeepEqual"
+ });
+ }
+ },
+
+ /**
+ * 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);
+ }
+ else {
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "==="
+ });
+ }
+ },
+
+ /**
+ * 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);
+ }
+ else {
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "!=="
+ })
+ }
+ },
+
+ /**
+ * 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
+ * Function that is expected to throw.
+ * @param {Error|RegExp} [Error]
+ * Error constructor that is expected to be thrown or a string that
+ * must be contained by a message of the thrown exception, or a RegExp
+ * matching a message of the thrown exception.
+ * @param {String} message
+ * Description message
+ *
+ * @examples
+ *
+ * assert.throws(function block() {
+ * doSomething(4)
+ * }, "Object is expected", "Incorrect argument is passed");
+ *
+ * assert.throws(function block() {
+ * Object.create(5)
+ * }, TypeError, "TypeError is thrown");
+ */
+ throws: function throws(block, Error, message) {
+ let threw = false;
+ let exception = null;
+
+ // If third argument is not provided and second argument is a string it
+ // means that optional `Error` argument was not passed, so we shift
+ // arguments.
+ if (isString(Error) && isUndefined(message)) {
+ message = Error;
+ Error = undefined;
+ }
+
+ // Executing given `block`.
+ try {
+ block();
+ }
+ catch (e) {
+ threw = true;
+ 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)) ||
+ // If passed `Error` is a constructor function testing if
+ // thrown exception is an instance of it.
+ (isFunction(Error) && instanceOf(exception, Error))))
+ {
+ this.pass(message);
+ }
+
+ // Otherwise we report assertion failure.
+ else {
+ let failure = {
+ message: message,
+ operator: "throws"
+ };
+
+ if (exception)
+ failure.actual = exception;
+
+ if (Error)
+ failure.expected = Error;
+
+ this.fail(failure);
+ }
+ }
+};
+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)) {
+ return actual.getTime() === expected.getTime();
+ }
+
+ // XXX specification bug: this should be specified
+ else if (isPrimitive(actual) || isPrimitive(expected)) {
+ return expected === actual;
+ }
+
+ // 7.3. Other pairs that do not both pass typeof value == "object",
+ // equivalence is determined by ==.
+ else if (!isObject(actual) && !isObject(expected)) {
+ return actual == expected;
+ }
+
+ // 7.4. For all other Object pairs, including Array objects, equivalence is
+ // determined by having the same number of owned properties (as verified
+ // with Object.prototype.hasOwnProperty.call), the same set of keys
+ // (although not necessarily the same order), equivalent values for every
+ // corresponding key, and an identical "prototype" property. Note: this
+ // accounts for both named and indexed properties on Arrays.
+ else {
+ return actual.prototype === expected.prototype &&
+ isEquivalent(actual, expected);
+ }
+}
+
+function isEquivalent(a, b, stack) {
+ let aKeys = Object.keys(a);
+ let bKeys = Object.keys(b);
+
+ return aKeys.length === bKeys.length &&
+ isArrayEquivalent(aKeys.sort(), bKeys.sort()) &&
+ aKeys.every(function(key) {
+ return isDeepEqual(a[key], b[key], stack)
+ });
+}
+
+function isArrayEquivalent(a, b, stack) {
+ return isArray(a) && isArray(b) &&
+ a.every(function(value, index) {
+ return isDeepEqual(value, b[index]);
+ });
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/test/harness.js b/tools/addon-sdk-1.12/lib/sdk/test/harness.js
new file mode 100644
index 0000000..46584ca
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/test/harness.js
@@ -0,0 +1,327 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc,Ci } = require("chrome");
+const { Loader } = require('./loader');
+const { setTimeout } = require('../timers');
+const memory = require('../deprecated/memory');
+const { PlainTextConsole } = require("../console/plain-text");
+const { when: unload } = require("../system/unload");
+const { format } = require("../console/traceback");
+const system = require("../system");
+
+// Trick manifest builder to make it think we need these modules ?
+const unit = require("../deprecated/unit-test");
+const test = require("../test");
+const url = require("../url");
+
+var cService = Cc['@mozilla.org/consoleservice;1'].getService()
+ .QueryInterface(Ci.nsIConsoleService);
+
+// Cuddlefish loader in which we load and execute tests.
+var loader;
+
+// Function to call when we're done running tests.
+var onDone;
+
+// Function to print text to a console, w/o CR at the end.
+var print;
+
+// How many more times to run all tests.
+var iterationsLeft;
+
+// Whether to report memory profiling information.
+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: []
+};
+
+// 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;
+
+function analyzeRawProfilingData(data) {
+ var graph = data.graph;
+ var shapes = {};
+
+ // Convert keys in the graph from strings to ints.
+ // TODO: Can we get rid of this ridiculousness?
+ var newGraph = {};
+ for (id in graph) {
+ newGraph[parseInt(id)] = graph[id];
+ }
+ graph = newGraph;
+
+ var modules = 0;
+ var moduleIds = [];
+ var moduleObjs = {UNKNOWN: 0};
+ for (let name in data.namedObjects) {
+ moduleObjs[name] = 0;
+ moduleIds[data.namedObjects[name]] = name;
+ modules++;
+ }
+
+ var count = 0;
+ for (id in graph) {
+ var parent = graph[id].parent;
+ while (parent) {
+ if (parent in moduleIds) {
+ var name = moduleIds[parent];
+ moduleObjs[name]++;
+ break;
+ }
+ if (!(parent in graph)) {
+ moduleObjs.UNKNOWN++;
+ break;
+ }
+ parent = graph[parent].parent;
+ }
+ count++;
+ }
+
+ print("\nobject count is " + count + " in " + modules + " modules" +
+ " (" + data.totalObjectCount + " across entire JS runtime)\n");
+ if (lastMemoryUsage) {
+ var last = JSON.parse(lastMemoryUsage);
+ var diff = {
+ moduleObjs: dictDiff(last.moduleObjs, moduleObjs),
+ totalObjectClasses: dictDiff(last.totalObjectClasses,
+ data.totalObjectClasses)
+ };
+
+ for (let name in diff.moduleObjs)
+ print(" " + diff.moduleObjs[name] + " in " + name + "\n");
+ for (let name in diff.totalObjectClasses)
+ print(" " + diff.totalObjectClasses[name] + " instances of " +
+ name + "\n");
+ }
+ lastMemoryUsage = JSON.stringify(
+ {moduleObjs: moduleObjs,
+ totalObjectClasses: data.totalObjectClasses}
+ );
+}
+
+function dictDiff(last, curr) {
+ var diff = {};
+
+ for (let name in last) {
+ var result = (curr[name] || 0) - last[name];
+ if (result)
+ diff[name] = (result > 0 ? "+" : "") + result;
+ }
+ for (let name in curr) {
+ var result = curr[name] - (last[name] || 0);
+ if (result)
+ diff[name] = (result > 0 ? "+" : "") + result;
+ }
+ return diff;
+}
+
+function reportMemoryUsage() {
+ memory.gc();
+
+ var mgr = Cc["@mozilla.org/memory-reporter-manager;1"]
+ .getService(Ci.nsIMemoryReporterManager);
+ var reporters = mgr.enumerateReporters();
+ if (reporters.hasMoreElements())
+ print("\n");
+ while (reporters.hasMoreElements()) {
+ var reporter = reporters.getNext();
+ reporter.QueryInterface(Ci.nsIMemoryReporter);
+ print(reporter.description + ": " + reporter.memoryUsed + "\n");
+ }
+
+ var weakrefs = [info.weakref.get()
+ for each (info in memory.getObjects())];
+ weakrefs = [weakref for each (weakref in weakrefs) if (weakref)];
+ print("Tracked memory objects in testing sandbox: " +
+ weakrefs.length + "\n");
+}
+
+var gWeakrefInfo;
+
+function showResults() {
+ memory.gc();
+
+ if (gWeakrefInfo) {
+ gWeakrefInfo.forEach(
+ function(info) {
+ var ref = info.weakref.get();
+ if (ref !== null) {
+ var data = ref.__url__ ? ref.__url__ : ref;
+ var warning = data == "[object Object]"
+ ? "[object " + data.constructor.name + "(" +
+ [p for (p in data)].join(", ") + ")]"
+ : data;
+ console.warn("LEAK", warning, info.bin);
+ }
+ }
+ );
+ }
+
+ onDone(results);
+}
+
+function cleanup() {
+ try {
+ for (let name in loader.modules)
+ memory.track(loader.modules[name],
+ "module global scope: " + name);
+ memory.track(loader, "Cuddlefish Loader");
+
+ if (profileMemory) {
+ gWeakrefInfo = [{ weakref: info.weakref, bin: info.bin }
+ for each (info in memory.getObjects())];
+ }
+
+ loader.unload();
+
+ if (loader.globals.console.errorsLogged && !results.failed) {
+ results.failed++;
+ console.error("warnings and/or errors were logged.");
+ }
+
+ if (consoleListener.errorsLogged && !results.failed) {
+ console.warn(consoleListener.errorsLogged + " " +
+ "warnings or errors were logged to the " +
+ "platform's nsIConsoleService, which could " +
+ "be of no consequence; however, they could also " +
+ "be indicative of aberrant behavior.");
+ }
+
+ consoleListener.errorsLogged = 0;
+ loader = null;
+
+ memory.gc();
+ } catch (e) {
+ results.failed++;
+ console.error("unload.send() threw an exception.");
+ console.exception(e);
+ };
+
+ setTimeout(showResults, 1);
+}
+
+function nextIteration(tests) {
+ if (tests) {
+ results.passed += tests.passed;
+ results.failed += tests.failed;
+
+ if (profileMemory)
+ reportMemoryUsage();
+
+ let testRun = [];
+ for each (let test in tests.testRunSummary) {
+ let testCopy = {};
+ for (let info in test) {
+ testCopy[info] = test[info];
+ }
+ testRun.push(testCopy);
+ }
+
+ results.testRuns.push(testRun);
+ iterationsLeft--;
+ }
+
+ if (iterationsLeft && (!stopOnError || results.failed == 0)) {
+ // Pass the loader which has a hooked console that doesn't dispatch
+ // errors to the JS console and avoid firing false alarm in our
+ // console listener
+ findAndRunTests(loader, nextIteration);
+ }
+ else {
+ setTimeout(cleanup, 0);
+ }
+}
+
+var POINTLESS_ERRORS = [
+ 'Invalid chrome URI:',
+ 'OpenGL LayerManager Initialized Succesfully.',
+ '[JavaScript Error: "TelemetryStopwatch:',
+ '[JavaScript Warning: "ReferenceErrorL reference to undefined property',
+ '[Javascript Warning: "Error: Failed to preserve wrapper of wrapped ' +
+ 'native weak map key',
+ '[JavaScript Warning: "Duplicate resource declaration for'
+];
+
+var consoleListener = {
+ errorsLogged: 0,
+ observe: function(object) {
+ if (!(object instanceof Ci.nsIScriptError))
+ return;
+ this.errorsLogged++;
+ var message = object.QueryInterface(Ci.nsIConsoleMessage).message;
+ var pointless = [err for each (err in POINTLESS_ERRORS)
+ if (message.indexOf(err) == 0)];
+ if (pointless.length == 0 && message)
+ print("console: " + message + "\n");
+ }
+};
+
+function TestRunnerConsole(base, options) {
+ this.__proto__ = {
+ errorsLogged: 0,
+ warn: function warn() {
+ this.errorsLogged++;
+ base.warn.apply(base, arguments);
+ },
+ error: function error() {
+ this.errorsLogged++;
+ base.error.apply(base, arguments);
+ },
+ info: function info(first) {
+ if (options.verbose)
+ base.info.apply(base, arguments);
+ else
+ if (first == "pass:")
+ print(".");
+ },
+ __proto__: base
+ };
+}
+
+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;
+
+ try {
+ cService.registerListener(consoleListener);
+ print("Running tests on " + system.name + " " + system.version +
+ "/Gecko " + system.platformVersion + " (" +
+ system.id + ") under " +
+ system.platform + "/" + system.architecture + ".\n");
+
+
+ loader = Loader(module, {
+ console: new TestRunnerConsole(new PlainTextConsole(print), options)
+ });
+
+ nextIteration();
+ } catch (e) {
+ print(format(e) + "\n" + e + "\n");
+ onDone({passed: 0, failed: 1});
+ }
+};
+
+unload(function() {
+ cService.unregisterListener(consoleListener);
+});
+
diff --git a/tools/addon-sdk-1.12/lib/sdk/test/httpd.js b/tools/addon-sdk-1.12/lib/sdk/test/httpd.js
new file mode 100644
index 0000000..48d3516
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/test/httpd.js
@@ -0,0 +1,5174 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+/*
+* An implementation of an HTTP server both as a loadable script and as an XPCOM
+* component. See the accompanying README file for user documentation on
+* httpd.js.
+*/
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { components, CC, Cc, Ci, Cr, Cu } = require("chrome");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+
+const PR_UINT32_MAX = Math.pow(2, 32) - 1;
+
+/** True if debugging output is enabled, false otherwise. */
+var DEBUG = false; // non-const *only* so tweakable in server tests
+
+/** True if debugging output should be timestamped. */
+var DEBUG_TIMESTAMP = false; // non-const so tweakable in server tests
+
+var gGlobalObject = Cc["@mozilla.org/systemprincipal;1"].createInstance();
+
+/**
+* Asserts that the given condition holds. If it doesn't, the given message is
+* dumped, a stack trace is printed, and an exception is thrown to attempt to
+* stop execution (which unfortunately must rely upon the exception not being
+* accidentally swallowed by the code that uses it).
+*/
+function NS_ASSERT(cond, msg)
+{
+ if (DEBUG && !cond)
+ {
+ dumpn("###!!!");
+ dumpn("###!!! ASSERTION" + (msg ? ": " + msg : "!"));
+ dumpn("###!!! Stack follows:");
+
+ var stack = new Error().stack.split(/\n/);
+ dumpn(stack.map(function(val) { return "###!!! " + val; }).join("\n"));
+
+ throw Cr.NS_ERROR_ABORT;
+ }
+}
+
+/** Constructs an HTTP error object. */
+function HttpError(code, description)
+{
+ this.code = code;
+ this.description = description;
+}
+HttpError.prototype =
+{
+ toString: function()
+ {
+ return this.code + " " + this.description;
+ }
+};
+
+/**
+* Errors thrown to trigger specific HTTP server responses.
+*/
+const HTTP_400 = new HttpError(400, "Bad Request");
+const HTTP_401 = new HttpError(401, "Unauthorized");
+const HTTP_402 = new HttpError(402, "Payment Required");
+const HTTP_403 = new HttpError(403, "Forbidden");
+const HTTP_404 = new HttpError(404, "Not Found");
+const HTTP_405 = new HttpError(405, "Method Not Allowed");
+const HTTP_406 = new HttpError(406, "Not Acceptable");
+const HTTP_407 = new HttpError(407, "Proxy Authentication Required");
+const HTTP_408 = new HttpError(408, "Request Timeout");
+const HTTP_409 = new HttpError(409, "Conflict");
+const HTTP_410 = new HttpError(410, "Gone");
+const HTTP_411 = new HttpError(411, "Length Required");
+const HTTP_412 = new HttpError(412, "Precondition Failed");
+const HTTP_413 = new HttpError(413, "Request Entity Too Large");
+const HTTP_414 = new HttpError(414, "Request-URI Too Long");
+const HTTP_415 = new HttpError(415, "Unsupported Media Type");
+const HTTP_417 = new HttpError(417, "Expectation Failed");
+
+const HTTP_500 = new HttpError(500, "Internal Server Error");
+const HTTP_501 = new HttpError(501, "Not Implemented");
+const HTTP_502 = new HttpError(502, "Bad Gateway");
+const HTTP_503 = new HttpError(503, "Service Unavailable");
+const HTTP_504 = new HttpError(504, "Gateway Timeout");
+const HTTP_505 = new HttpError(505, "HTTP Version Not Supported");
+
+/** Creates a hash with fields corresponding to the values in arr. */
+function array2obj(arr)
+{
+ var obj = {};
+ for (var i = 0; i < arr.length; i++)
+ obj[arr[i]] = arr[i];
+ return obj;
+}
+
+/** Returns an array of the integers x through y, inclusive. */
+function range(x, y)
+{
+ var arr = [];
+ for (var i = x; i <= y; i++)
+ arr.push(i);
+ return arr;
+}
+
+/** An object (hash) whose fields are the numbers of all HTTP error codes. */
+const HTTP_ERROR_CODES = array2obj(range(400, 417).concat(range(500, 505)));
+
+
+/**
+* The character used to distinguish hidden files from non-hidden files, a la
+* the leading dot in Apache. Since that mechanism also hides files from
+* easy display in LXR, ls output, etc. however, we choose instead to use a
+* suffix character. If a requested file ends with it, we append another
+* when getting the file on the server. If it doesn't, we just look up that
+* file. Therefore, any file whose name ends with exactly one of the character
+* is "hidden" and available for use by the server.
+*/
+const HIDDEN_CHAR = "^";
+
+/**
+* The file name suffix indicating the file containing overridden headers for
+* a requested file.
+*/
+const HEADERS_SUFFIX = HIDDEN_CHAR + "headers" + HIDDEN_CHAR;
+
+/** Type used to denote SJS scripts for CGI-like functionality. */
+const SJS_TYPE = "sjs";
+
+/** Base for relative timestamps produced by dumpn(). */
+var firstStamp = 0;
+
+/** dump(str) with a trailing "\n" -- only outputs if DEBUG. */
+function dumpn(str)
+{
+ if (DEBUG)
+ {
+ var prefix = "HTTPD-INFO | ";
+ if (DEBUG_TIMESTAMP)
+ {
+ if (firstStamp === 0)
+ firstStamp = Date.now();
+
+ var elapsed = Date.now() - firstStamp; // milliseconds
+ var min = Math.floor(elapsed / 60000);
+ var sec = (elapsed % 60000) / 1000;
+
+ if (sec < 10)
+ prefix += min + ":0" + sec.toFixed(3) + " | ";
+ else
+ prefix += min + ":" + sec.toFixed(3) + " | ";
+ }
+
+ dump(prefix + str + "\n");
+ }
+}
+
+/** Dumps the current JS stack if DEBUG. */
+function dumpStack()
+{
+ // peel off the frames for dumpStack() and Error()
+ var stack = new Error().stack.split(/\n/).slice(2);
+ stack.forEach(dumpn);
+}
+
+
+/** The XPCOM thread manager. */
+var gThreadManager = null;
+
+/** The XPCOM prefs service. */
+var gRootPrefBranch = null;
+function getRootPrefBranch()
+{
+ if (!gRootPrefBranch)
+ {
+ gRootPrefBranch = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ }
+ return gRootPrefBranch;
+}
+
+/**
+* JavaScript constructors for commonly-used classes; precreating these is a
+* speedup over doing the same from base principles. See the docs at
+* http://developer.mozilla.org/en/docs/components.Constructor for details.
+*/
+const ServerSocket = CC("@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "init");
+const ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1",
+ "nsIScriptableInputStream",
+ "init");
+const Pipe = CC("@mozilla.org/pipe;1",
+ "nsIPipe",
+ "init");
+const FileInputStream = CC("@mozilla.org/network/file-input-stream;1",
+ "nsIFileInputStream",
+ "init");
+const ConverterInputStream = CC("@mozilla.org/intl/converter-input-stream;1",
+ "nsIConverterInputStream",
+ "init");
+const WritablePropertyBag = CC("@mozilla.org/hash-property-bag;1",
+ "nsIWritablePropertyBag2");
+const SupportsString = CC("@mozilla.org/supports-string;1",
+ "nsISupportsString");
+
+/* These two are non-const only so a test can overwrite them. */
+var BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+var BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
+ "nsIBinaryOutputStream",
+ "setOutputStream");
+
+/**
+* Returns the RFC 822/1123 representation of a date.
+*
+* @param date : Number
+* the date, in milliseconds from midnight (00:00:00), January 1, 1970 GMT
+* @returns string
+* the representation of the given date
+*/
+function toDateString(date)
+{
+ //
+ // rfc1123-date = wkday "," SP date1 SP time SP "GMT"
+ // date1 = 2DIGIT SP month SP 4DIGIT
+ // ; day month year (e.g., 02 Jun 1982)
+ // time = 2DIGIT ":" 2DIGIT ":" 2DIGIT
+ // ; 00:00:00 - 23:59:59
+ // wkday = "Mon" | "Tue" | "Wed"
+ // | "Thu" | "Fri" | "Sat" | "Sun"
+ // month = "Jan" | "Feb" | "Mar" | "Apr"
+ // | "May" | "Jun" | "Jul" | "Aug"
+ // | "Sep" | "Oct" | "Nov" | "Dec"
+ //
+
+ const wkdayStrings = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+ const monthStrings = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
+
+ /**
+* Processes a date and returns the encoded UTC time as a string according to
+* the format specified in RFC 2616.
+*
+* @param date : Date
+* the date to process
+* @returns string
+* a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59"
+*/
+ function toTime(date)
+ {
+ var hrs = date.getUTCHours();
+ var rv = (hrs < 10) ? "0" + hrs : hrs;
+
+ var mins = date.getUTCMinutes();
+ rv += ":";
+ rv += (mins < 10) ? "0" + mins : mins;
+
+ var secs = date.getUTCSeconds();
+ rv += ":";
+ rv += (secs < 10) ? "0" + secs : secs;
+
+ return rv;
+ }
+
+ /**
+* Processes a date and returns the encoded UTC date as a string according to
+* the date1 format specified in RFC 2616.
+*
+* @param date : Date
+* the date to process
+* @returns string
+* a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59"
+*/
+ function toDate1(date)
+ {
+ var day = date.getUTCDate();
+ var month = date.getUTCMonth();
+ var year = date.getUTCFullYear();
+
+ var rv = (day < 10) ? "0" + day : day;
+ rv += " " + monthStrings[month];
+ rv += " " + year;
+
+ return rv;
+ }
+
+ date = new Date(date);
+
+ const fmtString = "%wkday%, %date1% %time% GMT";
+ var rv = fmtString.replace("%wkday%", wkdayStrings[date.getUTCDay()]);
+ rv = rv.replace("%time%", toTime(date));
+ return rv.replace("%date1%", toDate1(date));
+}
+
+/**
+* Prints out a human-readable representation of the object o and its fields,
+* omitting those whose names begin with "_" if showMembers != true (to ignore
+* "private" properties exposed via getters/setters).
+*/
+function printObj(o, showMembers)
+{
+ var s = "******************************\n";
+ s += "o = {\n";
+ for (var i in o)
+ {
+ if (typeof(i) != "string" ||
+ (showMembers || (i.length > 0 && i[0] != "_")))
+ s+= " " + i + ": " + o[i] + ",\n";
+ }
+ s += " };\n";
+ s += "******************************";
+ dumpn(s);
+}
+
+/**
+* Instantiates a new HTTP server.
+*/
+function nsHttpServer()
+{
+ if (!gThreadManager)
+ gThreadManager = Cc["@mozilla.org/thread-manager;1"].getService();
+
+ /** The port on which this server listens. */
+ this._port = undefined;
+
+ /** The socket associated with this. */
+ this._socket = null;
+
+ /** The handler used to process requests to this server. */
+ this._handler = new ServerHandler(this);
+
+ /** Naming information for this server. */
+ this._identity = new ServerIdentity();
+
+ /**
+* Indicates when the server is to be shut down at the end of the request.
+*/
+ this._doQuit = false;
+
+ /**
+* True if the socket in this is closed (and closure notifications have been
+* sent and processed if the socket was ever opened), false otherwise.
+*/
+ this._socketClosed = true;
+
+ /**
+* Used for tracking existing connections and ensuring that all connections
+* are properly cleaned up before server shutdown; increases by 1 for every
+* new incoming connection.
+*/
+ this._connectionGen = 0;
+
+ /**
+* Hash of all open connections, indexed by connection number at time of
+* creation.
+*/
+ this._connections = {};
+}
+nsHttpServer.prototype =
+{
+ classID: components.ID("{54ef6f81-30af-4b1d-ac55-8ba811293e41}"),
+
+ // NSISERVERSOCKETLISTENER
+
+ /**
+* Processes an incoming request coming in on the given socket and contained
+* in the given transport.
+*
+* @param socket : nsIServerSocket
+* the socket through which the request was served
+* @param trans : nsISocketTransport
+* the transport for the request/response
+* @see nsIServerSocketListener.onSocketAccepted
+*/
+ onSocketAccepted: function(socket, trans)
+ {
+ dumpn("*** onSocketAccepted(socket=" + socket + ", trans=" + trans + ")");
+
+ dumpn(">>> new connection on " + trans.host + ":" + trans.port);
+
+ const SEGMENT_SIZE = 8192;
+ const SEGMENT_COUNT = 1024;
+ try
+ {
+ var input = trans.openInputStream(0, SEGMENT_SIZE, SEGMENT_COUNT)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ var output = trans.openOutputStream(0, 0, 0);
+ }
+ catch (e)
+ {
+ dumpn("*** error opening transport streams: " + e);
+ trans.close(Cr.NS_BINDING_ABORTED);
+ return;
+ }
+
+ var connectionNumber = ++this._connectionGen;
+
+ try
+ {
+ var conn = new Connection(input, output, this, socket.port, trans.port,
+ connectionNumber);
+ var reader = new RequestReader(conn);
+
+ // XXX add request timeout functionality here!
+
+ // Note: must use main thread here, or we might get a GC that will cause
+ // threadsafety assertions. We really need to fix XPConnect so that
+ // you can actually do things in multi-threaded JS. :-(
+ input.asyncWait(reader, 0, 0, gThreadManager.mainThread);
+ }
+ catch (e)
+ {
+ // Assume this connection can't be salvaged and bail on it completely;
+ // don't attempt to close it so that we can assert that any connection
+ // being closed is in this._connections.
+ dumpn("*** error in initial request-processing stages: " + e);
+ trans.close(Cr.NS_BINDING_ABORTED);
+ return;
+ }
+
+ this._connections[connectionNumber] = conn;
+ dumpn("*** starting connection " + connectionNumber);
+ },
+
+ /**
+* Called when the socket associated with this is closed.
+*
+* @param socket : nsIServerSocket
+* the socket being closed
+* @param status : nsresult
+* the reason the socket stopped listening (NS_BINDING_ABORTED if the server
+* was stopped using nsIHttpServer.stop)
+* @see nsIServerSocketListener.onStopListening
+*/
+ onStopListening: function(socket, status)
+ {
+ dumpn(">>> shutting down server on port " + socket.port);
+ this._socketClosed = true;
+ if (!this._hasOpenConnections())
+ {
+ dumpn("*** no open connections, notifying async from onStopListening");
+
+ // Notify asynchronously so that any pending teardown in stop() has a
+ // chance to run first.
+ var self = this;
+ var stopEvent =
+ {
+ run: function()
+ {
+ dumpn("*** _notifyStopped async callback");
+ self._notifyStopped();
+ }
+ };
+ gThreadManager.currentThread
+ .dispatch(stopEvent, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ },
+
+ // NSIHTTPSERVER
+
+ //
+ // see nsIHttpServer.start
+ //
+ start: function(port)
+ {
+ this._start(port, "localhost")
+ },
+
+ _start: function(port, host)
+ {
+ if (this._socket)
+ throw Cr.NS_ERROR_ALREADY_INITIALIZED;
+
+ this._port = port;
+ this._doQuit = this._socketClosed = false;
+
+ this._host = host;
+
+ // The listen queue needs to be long enough to handle
+ // network.http.max-persistent-connections-per-server concurrent connections,
+ // plus a safety margin in case some other process is talking to
+ // the server as well.
+ var prefs = getRootPrefBranch();
+ var maxConnections;
+ try {
+ // Bug 776860: The original pref was removed in favor of this new one:
+ maxConnections = prefs.getIntPref("network.http.max-persistent-connections-per-server") + 5;
+ }
+ catch(e) {
+ maxConnections = prefs.getIntPref("network.http.max-connections-per-server") + 5;
+ }
+
+ try
+ {
+ var loopback = true;
+ if (this._host != "127.0.0.1" && this._host != "localhost") {
+ var loopback = false;
+ }
+
+ var socket = new ServerSocket(this._port,
+ loopback, // true = localhost, false = everybody
+ maxConnections);
+ dumpn(">>> listening on port " + socket.port + ", " + maxConnections +
+ " pending connections");
+ socket.asyncListen(this);
+ this._identity._initialize(port, host, true);
+ this._socket = socket;
+ }
+ catch (e)
+ {
+ dumpn("!!! could not start server on port " + port + ": " + e);
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ }
+ },
+
+ //
+ // see nsIHttpServer.stop
+ //
+ stop: function(callback)
+ {
+ if (!callback)
+ throw Cr.NS_ERROR_NULL_POINTER;
+ if (!this._socket)
+ throw Cr.NS_ERROR_UNEXPECTED;
+
+ this._stopCallback = typeof callback === "function"
+ ? callback
+ : function() { callback.onStopped(); };
+
+ dumpn(">>> stopping listening on port " + this._socket.port);
+ this._socket.close();
+ this._socket = null;
+
+ // We can't have this identity any more, and the port on which we're running
+ // this server now could be meaningless the next time around.
+ this._identity._teardown();
+
+ this._doQuit = false;
+
+ // socket-close notification and pending request completion happen async
+ },
+
+ //
+ // see nsIHttpServer.registerFile
+ //
+ registerFile: function(path, file)
+ {
+ if (file && (!file.exists() || file.isDirectory()))
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ this._handler.registerFile(path, file);
+ },
+
+ //
+ // see nsIHttpServer.registerDirectory
+ //
+ registerDirectory: function(path, directory)
+ {
+ // XXX true path validation!
+ if (path.charAt(0) != "/" ||
+ path.charAt(path.length - 1) != "/" ||
+ (directory &&
+ (!directory.exists() || !directory.isDirectory())))
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ // XXX determine behavior of nonexistent /foo/bar when a /foo/bar/ mapping
+ // exists!
+
+ this._handler.registerDirectory(path, directory);
+ },
+
+ //
+ // see nsIHttpServer.registerPathHandler
+ //
+ registerPathHandler: function(path, handler)
+ {
+ this._handler.registerPathHandler(path, handler);
+ },
+
+ //
+ // see nsIHttpServer.registerErrorHandler
+ //
+ registerErrorHandler: function(code, handler)
+ {
+ this._handler.registerErrorHandler(code, handler);
+ },
+
+ //
+ // see nsIHttpServer.setIndexHandler
+ //
+ setIndexHandler: function(handler)
+ {
+ this._handler.setIndexHandler(handler);
+ },
+
+ //
+ // see nsIHttpServer.registerContentType
+ //
+ registerContentType: function(ext, type)
+ {
+ this._handler.registerContentType(ext, type);
+ },
+
+ //
+ // see nsIHttpServer.serverIdentity
+ //
+ get identity()
+ {
+ return this._identity;
+ },
+
+ //
+ // see nsIHttpServer.getState
+ //
+ getState: function(path, k)
+ {
+ return this._handler._getState(path, k);
+ },
+
+ //
+ // see nsIHttpServer.setState
+ //
+ setState: function(path, k, v)
+ {
+ return this._handler._setState(path, k, v);
+ },
+
+ //
+ // see nsIHttpServer.getSharedState
+ //
+ getSharedState: function(k)
+ {
+ return this._handler._getSharedState(k);
+ },
+
+ //
+ // see nsIHttpServer.setSharedState
+ //
+ setSharedState: function(k, v)
+ {
+ return this._handler._setSharedState(k, v);
+ },
+
+ //
+ // see nsIHttpServer.getObjectState
+ //
+ getObjectState: function(k)
+ {
+ return this._handler._getObjectState(k);
+ },
+
+ //
+ // see nsIHttpServer.setObjectState
+ //
+ setObjectState: function(k, v)
+ {
+ return this._handler._setObjectState(k, v);
+ },
+
+
+ // NSISUPPORTS
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIServerSocketListener) || iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // NON-XPCOM PUBLIC API
+
+ /**
+* Returns true iff this server is not running (and is not in the process of
+* serving any requests still to be processed when the server was last
+* stopped after being run).
+*/
+ isStopped: function()
+ {
+ return this._socketClosed && !this._hasOpenConnections();
+ },
+
+ // PRIVATE IMPLEMENTATION
+
+ /** True if this server has any open connections to it, false otherwise. */
+ _hasOpenConnections: function()
+ {
+ //
+ // If we have any open connections, they're tracked as numeric properties on
+ // |this._connections|. The non-standard __count__ property could be used
+ // to check whether there are any properties, but standard-wise, even
+ // looking forward to ES5, there's no less ugly yet still O(1) way to do
+ // this.
+ //
+ for (var n in this._connections)
+ return true;
+ return false;
+ },
+
+ /** Calls the server-stopped callback provided when stop() was called. */
+ _notifyStopped: function()
+ {
+ NS_ASSERT(this._stopCallback !== null, "double-notifying?");
+ NS_ASSERT(!this._hasOpenConnections(), "should be done serving by now");
+
+ //
+ // NB: We have to grab this now, null out the member, *then* call the
+ // callback here, or otherwise the callback could (indirectly) futz with
+ // this._stopCallback by starting and immediately stopping this, at
+ // which point we'd be nulling out a field we no longer have a right to
+ // modify.
+ //
+ var callback = this._stopCallback;
+ this._stopCallback = null;
+ try
+ {
+ callback();
+ }
+ catch (e)
+ {
+ // not throwing because this is specified as being usually (but not
+ // always) asynchronous
+ dump("!!! error running onStopped callback: " + e + "\n");
+ }
+ },
+
+ /**
+* Notifies this server that the given connection has been closed.
+*
+* @param connection : Connection
+* the connection that was closed
+*/
+ _connectionClosed: function(connection)
+ {
+ NS_ASSERT(connection.number in this._connections,
+ "closing a connection " + this + " that we never added to the " +
+ "set of open connections?");
+ NS_ASSERT(this._connections[connection.number] === connection,
+ "connection number mismatch? " +
+ this._connections[connection.number]);
+ delete this._connections[connection.number];
+
+ // Fire a pending server-stopped notification if it's our responsibility.
+ if (!this._hasOpenConnections() && this._socketClosed)
+ this._notifyStopped();
+ },
+
+ /**
+* Requests that the server be shut down when possible.
+*/
+ _requestQuit: function()
+ {
+ dumpn(">>> requesting a quit");
+ dumpStack();
+ this._doQuit = true;
+ }
+};
+
+
+//
+// RFC 2396 section 3.2.2:
+//
+// host = hostname | IPv4address
+// hostname = *( domainlabel "." ) toplabel [ "." ]
+// domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
+// toplabel = alpha | alpha *( alphanum | "-" ) alphanum
+// IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit
+//
+
+const HOST_REGEX =
+ new RegExp("^(?:" +
+ // *( domainlabel "." )
+ "(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)*" +
+ // toplabel
+ "[a-z](?:[a-z0-9-]*[a-z0-9])?" +
+ "|" +
+ // IPv4 address
+ "\\d+\\.\\d+\\.\\d+\\.\\d+" +
+ ")$",
+ "i");
+
+
+/**
+* Represents the identity of a server. An identity consists of a set of
+* (scheme, host, port) tuples denoted as locations (allowing a single server to
+* serve multiple sites or to be used behind both HTTP and HTTPS proxies for any
+* host/port). Any incoming request must be to one of these locations, or it
+* will be rejected with an HTTP 400 error. One location, denoted as the
+* primary location, is the location assigned in contexts where a location
+* cannot otherwise be endogenously derived, such as for HTTP/1.0 requests.
+*
+* A single identity may contain at most one location per unique host/port pair;
+* other than that, no restrictions are placed upon what locations may
+* constitute an identity.
+*/
+function ServerIdentity()
+{
+ /** The scheme of the primary location. */
+ this._primaryScheme = "http";
+
+ /** The hostname of the primary location. */
+ this._primaryHost = "127.0.0.1"
+
+ /** The port number of the primary location. */
+ this._primaryPort = -1;
+
+ /**
+* The current port number for the corresponding server, stored so that a new
+* primary location can always be set if the current one is removed.
+*/
+ this._defaultPort = -1;
+
+ /**
+* Maps hosts to maps of ports to schemes, e.g. the following would represent
+* https://example.com:789/ and http://example.org/:
+*
+* {
+* "xexample.com": { 789: "https" },
+* "xexample.org": { 80: "http" }
+* }
+*
+* Note the "x" prefix on hostnames, which prevents collisions with special
+* JS names like "prototype".
+*/
+ this._locations = { "xlocalhost": {} };
+}
+ServerIdentity.prototype =
+{
+ // NSIHTTPSERVERIDENTITY
+
+ //
+ // see nsIHttpServerIdentity.primaryScheme
+ //
+ get primaryScheme()
+ {
+ if (this._primaryPort === -1)
+ throw Cr.NS_ERROR_NOT_INITIALIZED;
+ return this._primaryScheme;
+ },
+
+ //
+ // see nsIHttpServerIdentity.primaryHost
+ //
+ get primaryHost()
+ {
+ if (this._primaryPort === -1)
+ throw Cr.NS_ERROR_NOT_INITIALIZED;
+ return this._primaryHost;
+ },
+
+ //
+ // see nsIHttpServerIdentity.primaryPort
+ //
+ get primaryPort()
+ {
+ if (this._primaryPort === -1)
+ throw Cr.NS_ERROR_NOT_INITIALIZED;
+ return this._primaryPort;
+ },
+
+ //
+ // see nsIHttpServerIdentity.add
+ //
+ add: function(scheme, host, port)
+ {
+ this._validate(scheme, host, port);
+
+ var entry = this._locations["x" + host];
+ if (!entry)
+ this._locations["x" + host] = entry = {};
+
+ entry[port] = scheme;
+ },
+
+ //
+ // see nsIHttpServerIdentity.remove
+ //
+ remove: function(scheme, host, port)
+ {
+ this._validate(scheme, host, port);
+
+ var entry = this._locations["x" + host];
+ if (!entry)
+ return false;
+
+ var present = port in entry;
+ delete entry[port];
+
+ if (this._primaryScheme == scheme &&
+ this._primaryHost == host &&
+ this._primaryPort == port &&
+ this._defaultPort !== -1)
+ {
+ // Always keep at least one identity in existence at any time, unless
+ // we're in the process of shutting down (the last condition above).
+ this._primaryPort = -1;
+ this._initialize(this._defaultPort, host, false);
+ }
+
+ return present;
+ },
+
+ //
+ // see nsIHttpServerIdentity.has
+ //
+ has: function(scheme, host, port)
+ {
+ this._validate(scheme, host, port);
+
+ return "x" + host in this._locations &&
+ scheme === this._locations["x" + host][port];
+ },
+
+ //
+ // see nsIHttpServerIdentity.has
+ //
+ getScheme: function(host, port)
+ {
+ this._validate("http", host, port);
+
+ var entry = this._locations["x" + host];
+ if (!entry)
+ return "";
+
+ return entry[port] || "";
+ },
+
+ //
+ // see nsIHttpServerIdentity.setPrimary
+ //
+ setPrimary: function(scheme, host, port)
+ {
+ this._validate(scheme, host, port);
+
+ this.add(scheme, host, port);
+
+ this._primaryScheme = scheme;
+ this._primaryHost = host;
+ this._primaryPort = port;
+ },
+
+
+ // NSISUPPORTS
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIHttpServerIdentity) || iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // PRIVATE IMPLEMENTATION
+
+ /**
+* Initializes the primary name for the corresponding server, based on the
+* provided port number.
+*/
+ _initialize: function(port, host, addSecondaryDefault)
+ {
+ this._host = host;
+ if (this._primaryPort !== -1)
+ this.add("http", host, port);
+ else
+ this.setPrimary("http", "localhost", port);
+ this._defaultPort = port;
+
+ // Only add this if we're being called at server startup
+ if (addSecondaryDefault && host != "127.0.0.1")
+ this.add("http", "127.0.0.1", port);
+ },
+
+ /**
+* Called at server shutdown time, unsets the primary location only if it was
+* the default-assigned location and removes the default location from the
+* set of locations used.
+*/
+ _teardown: function()
+ {
+ if (this._host != "127.0.0.1") {
+ // Not the default primary location, nothing special to do here
+ this.remove("http", "127.0.0.1", this._defaultPort);
+ }
+
+ // This is a *very* tricky bit of reasoning here; make absolutely sure the
+ // tests for this code pass before you commit changes to it.
+ if (this._primaryScheme == "http" &&
+ this._primaryHost == this._host &&
+ this._primaryPort == this._defaultPort)
+ {
+ // Make sure we don't trigger the readding logic in .remove(), then remove
+ // the default location.
+ var port = this._defaultPort;
+ this._defaultPort = -1;
+ this.remove("http", this._host, port);
+
+ // Ensure a server start triggers the setPrimary() path in ._initialize()
+ this._primaryPort = -1;
+ }
+ else
+ {
+ // No reason not to remove directly as it's not our primary location
+ this.remove("http", this._host, this._defaultPort);
+ }
+ },
+
+ /**
+* Ensures scheme, host, and port are all valid with respect to RFC 2396.
+*
+* @throws NS_ERROR_ILLEGAL_VALUE
+* if any argument doesn't match the corresponding production
+*/
+ _validate: function(scheme, host, port)
+ {
+ if (scheme !== "http" && scheme !== "https")
+ {
+ dumpn("*** server only supports http/https schemes: '" + scheme + "'");
+ dumpStack();
+ throw Cr.NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (!HOST_REGEX.test(host))
+ {
+ dumpn("*** unexpected host: '" + host + "'");
+ throw Cr.NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (port < 0 || port > 65535)
+ {
+ dumpn("*** unexpected port: '" + port + "'");
+ throw Cr.NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+};
+
+
+/**
+* Represents a connection to the server (and possibly in the future the thread
+* on which the connection is processed).
+*
+* @param input : nsIInputStream
+* stream from which incoming data on the connection is read
+* @param output : nsIOutputStream
+* stream to write data out the connection
+* @param server : nsHttpServer
+* the server handling the connection
+* @param port : int
+* the port on which the server is running
+* @param outgoingPort : int
+* the outgoing port used by this connection
+* @param number : uint
+* a serial number used to uniquely identify this connection
+*/
+function Connection(input, output, server, port, outgoingPort, number)
+{
+ dumpn("*** opening new connection " + number + " on port " + outgoingPort);
+
+ /** Stream of incoming data. */
+ this.input = input;
+
+ /** Stream for outgoing data. */
+ this.output = output;
+
+ /** The server associated with this request. */
+ this.server = server;
+
+ /** The port on which the server is running. */
+ this.port = port;
+
+ /** The outgoing poort used by this connection. */
+ this._outgoingPort = outgoingPort;
+
+ /** The serial number of this connection. */
+ this.number = number;
+
+ /**
+* The request for which a response is being generated, null if the
+* incoming request has not been fully received or if it had errors.
+*/
+ this.request = null;
+
+ /** State variables for debugging. */
+ this._closed = this._processed = false;
+}
+Connection.prototype =
+{
+ /** Closes this connection's input/output streams. */
+ close: function()
+ {
+ dumpn("*** closing connection " + this.number +
+ " on port " + this._outgoingPort);
+
+ this.input.close();
+ this.output.close();
+ this._closed = true;
+
+ var server = this.server;
+ server._connectionClosed(this);
+
+ // If an error triggered a server shutdown, act on it now
+ if (server._doQuit)
+ server.stop(function() { /* not like we can do anything better */ });
+ },
+
+ /**
+* Initiates processing of this connection, using the data in the given
+* request.
+*
+* @param request : Request
+* the request which should be processed
+*/
+ process: function(request)
+ {
+ NS_ASSERT(!this._closed && !this._processed);
+
+ this._processed = true;
+
+ this.request = request;
+ this.server._handler.handleResponse(this);
+ },
+
+ /**
+* Initiates processing of this connection, generating a response with the
+* given HTTP error code.
+*
+* @param code : uint
+* an HTTP code, so in the range [0, 1000)
+* @param request : Request
+* incomplete data about the incoming request (since there were errors
+* during its processing
+*/
+ processError: function(code, request)
+ {
+ NS_ASSERT(!this._closed && !this._processed);
+
+ this._processed = true;
+ this.request = request;
+ this.server._handler.handleError(code, this);
+ },
+
+ /** Converts this to a string for debugging purposes. */
+ toString: function()
+ {
+ return "<Connection(" + this.number +
+ (this.request ? ", " + this.request.path : "") +"): " +
+ (this._closed ? "closed" : "open") + ">";
+ }
+};
+
+
+
+/** Returns an array of count bytes from the given input stream. */
+function readBytes(inputStream, count)
+{
+ return new BinaryInputStream(inputStream).readByteArray(count);
+}
+
+
+
+/** Request reader processing states; see RequestReader for details. */
+const READER_IN_REQUEST_LINE = 0;
+const READER_IN_HEADERS = 1;
+const READER_IN_BODY = 2;
+const READER_FINISHED = 3;
+
+
+/**
+* Reads incoming request data asynchronously, does any necessary preprocessing,
+* and forwards it to the request handler. Processing occurs in three states:
+*
+* READER_IN_REQUEST_LINE Reading the request's status line
+* READER_IN_HEADERS Reading headers in the request
+* READER_IN_BODY Reading the body of the request
+* READER_FINISHED Entire request has been read and processed
+*
+* During the first two stages, initial metadata about the request is gathered
+* into a Request object. Once the status line and headers have been processed,
+* we start processing the body of the request into the Request. Finally, when
+* the entire body has been read, we create a Response and hand it off to the
+* ServerHandler to be given to the appropriate request handler.
+*
+* @param connection : Connection
+* the connection for the request being read
+*/
+function RequestReader(connection)
+{
+ /** Connection metadata for this request. */
+ this._connection = connection;
+
+ /**
+* A container providing line-by-line access to the raw bytes that make up the
+* data which has been read from the connection but has not yet been acted
+* upon (by passing it to the request handler or by extracting request
+* metadata from it).
+*/
+ this._data = new LineData();
+
+ /**
+* The amount of data remaining to be read from the body of this request.
+* After all headers in the request have been read this is the value in the
+* Content-Length header, but as the body is read its value decreases to zero.
+*/
+ this._contentLength = 0;
+
+ /** The current state of parsing the incoming request. */
+ this._state = READER_IN_REQUEST_LINE;
+
+ /** Metadata constructed from the incoming request for the request handler. */
+ this._metadata = new Request(connection.port);
+
+ /**
+* Used to preserve state if we run out of line data midway through a
+* multi-line header. _lastHeaderName stores the name of the header, while
+* _lastHeaderValue stores the value we've seen so far for the header.
+*
+* These fields are always either both undefined or both strings.
+*/
+ this._lastHeaderName = this._lastHeaderValue = undefined;
+}
+RequestReader.prototype =
+{
+ // NSIINPUTSTREAMCALLBACK
+
+ /**
+* Called when more data from the incoming request is available. This method
+* then reads the available data from input and deals with that data as
+* necessary, depending upon the syntax of already-downloaded data.
+*
+* @param input : nsIAsyncInputStream
+* the stream of incoming data from the connection
+*/
+ onInputStreamReady: function(input)
+ {
+ dumpn("*** onInputStreamReady(input=" + input + ") on thread " +
+ gThreadManager.currentThread + " (main is " +
+ gThreadManager.mainThread + ")");
+ dumpn("*** this._state == " + this._state);
+
+ // Handle cases where we get more data after a request error has been
+ // discovered but *before* we can close the connection.
+ var data = this._data;
+ if (!data)
+ return;
+
+ try
+ {
+ data.appendBytes(readBytes(input, input.available()));
+ }
+ catch (e)
+ {
+ if (streamClosed(e))
+ {
+ dumpn("*** WARNING: unexpected error when reading from socket; will " +
+ "be treated as if the input stream had been closed");
+ dumpn("*** WARNING: actual error was: " + e);
+ }
+
+ // We've lost a race -- input has been closed, but we're still expecting
+ // to read more data. available() will throw in this case, and since
+ // we're dead in the water now, destroy the connection.
+ dumpn("*** onInputStreamReady called on a closed input, destroying " +
+ "connection");
+ this._connection.close();
+ return;
+ }
+
+ switch (this._state)
+ {
+ default:
+ NS_ASSERT(false, "invalid state: " + this._state);
+ break;
+
+ case READER_IN_REQUEST_LINE:
+ if (!this._processRequestLine())
+ break;
+ /* fall through */
+
+ case READER_IN_HEADERS:
+ if (!this._processHeaders())
+ break;
+ /* fall through */
+
+ case READER_IN_BODY:
+ this._processBody();
+ }
+
+ if (this._state != READER_FINISHED)
+ input.asyncWait(this, 0, 0, gThreadManager.currentThread);
+ },
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: function(aIID)
+ {
+ if (aIID.equals(Ci.nsIInputStreamCallback) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // PRIVATE API
+
+ /**
+* Processes unprocessed, downloaded data as a request line.
+*
+* @returns boolean
+* true iff the request line has been fully processed
+*/
+ _processRequestLine: function()
+ {
+ NS_ASSERT(this._state == READER_IN_REQUEST_LINE);
+
+ // Servers SHOULD ignore any empty line(s) received where a Request-Line
+ // is expected (section 4.1).
+ var data = this._data;
+ var line = {};
+ var readSuccess;
+ while ((readSuccess = data.readLine(line)) && line.value == "")
+ dumpn("*** ignoring beginning blank line...");
+
+ // if we don't have a full line, wait until we do
+ if (!readSuccess)
+ return false;
+
+ // we have the first non-blank line
+ try
+ {
+ this._parseRequestLine(line.value);
+ this._state = READER_IN_HEADERS;
+ return true;
+ }
+ catch (e)
+ {
+ this._handleError(e);
+ return false;
+ }
+ },
+
+ /**
+* Processes stored data, assuming it is either at the beginning or in
+* the middle of processing request headers.
+*
+* @returns boolean
+* true iff header data in the request has been fully processed
+*/
+ _processHeaders: function()
+ {
+ NS_ASSERT(this._state == READER_IN_HEADERS);
+
+ // XXX things to fix here:
+ //
+ // - need to support RFC 2047-encoded non-US-ASCII characters
+
+ try
+ {
+ var done = this._parseHeaders();
+ if (done)
+ {
+ var request = this._metadata;
+
+ // XXX this is wrong for requests with transfer-encodings applied to
+ // them, particularly chunked (which by its nature can have no
+ // meaningful Content-Length header)!
+ this._contentLength = request.hasHeader("Content-Length")
+ ? parseInt(request.getHeader("Content-Length"), 10)
+ : 0;
+ dumpn("_processHeaders, Content-length=" + this._contentLength);
+
+ this._state = READER_IN_BODY;
+ }
+ return done;
+ }
+ catch (e)
+ {
+ this._handleError(e);
+ return false;
+ }
+ },
+
+ /**
+* Processes stored data, assuming it is either at the beginning or in
+* the middle of processing the request body.
+*
+* @returns boolean
+* true iff the request body has been fully processed
+*/
+ _processBody: function()
+ {
+ NS_ASSERT(this._state == READER_IN_BODY);
+
+ // XXX handle chunked transfer-coding request bodies!
+
+ try
+ {
+ if (this._contentLength > 0)
+ {
+ var data = this._data.purge();
+ var count = Math.min(data.length, this._contentLength);
+ dumpn("*** loading data=" + data + " len=" + data.length +
+ " excess=" + (data.length - count));
+
+ var bos = new BinaryOutputStream(this._metadata._bodyOutputStream);
+ bos.writeByteArray(data, count);
+ this._contentLength -= count;
+ }
+
+ dumpn("*** remaining body data len=" + this._contentLength);
+ if (this._contentLength == 0)
+ {
+ this._validateRequest();
+ this._state = READER_FINISHED;
+ this._handleResponse();
+ return true;
+ }
+
+ return false;
+ }
+ catch (e)
+ {
+ this._handleError(e);
+ return false;
+ }
+ },
+
+ /**
+* Does various post-header checks on the data in this request.
+*
+* @throws : HttpError
+* if the request was malformed in some way
+*/
+ _validateRequest: function()
+ {
+ NS_ASSERT(this._state == READER_IN_BODY);
+
+ dumpn("*** _validateRequest");
+
+ var metadata = this._metadata;
+ var headers = metadata._headers;
+
+ // 19.6.1.1 -- servers MUST report 400 to HTTP/1.1 requests w/o Host header
+ var identity = this._connection.server.identity;
+ if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1))
+ {
+ if (!headers.hasHeader("Host"))
+ {
+ dumpn("*** malformed HTTP/1.1 or greater request with no Host header!");
+ throw HTTP_400;
+ }
+
+ // If the Request-URI wasn't absolute, then we need to determine our host.
+ // We have to determine what scheme was used to access us based on the
+ // server identity data at this point, because the request just doesn't
+ // contain enough data on its own to do this, sadly.
+ if (!metadata._host)
+ {
+ var host, port;
+ var hostPort = headers.getHeader("Host");
+ var colon = hostPort.indexOf(":");
+ if (colon < 0)
+ {
+ host = hostPort;
+ port = "";
+ }
+ else
+ {
+ host = hostPort.substring(0, colon);
+ port = hostPort.substring(colon + 1);
+ }
+
+ // NB: We allow an empty port here because, oddly, a colon may be
+ // present even without a port number, e.g. "example.com:"; in this
+ // case the default port applies.
+ if (!HOST_REGEX.test(host) || !/^\d*$/.test(port))
+ {
+ dumpn("*** malformed hostname (" + hostPort + ") in Host " +
+ "header, 400 time");
+ throw HTTP_400;
+ }
+
+ // If we're not given a port, we're stuck, because we don't know what
+ // scheme to use to look up the correct port here, in general. Since
+ // the HTTPS case requires a tunnel/proxy and thus requires that the
+ // requested URI be absolute (and thus contain the necessary
+ // information), let's assume HTTP will prevail and use that.
+ port = +port || 80;
+
+ var scheme = identity.getScheme(host, port);
+ if (!scheme)
+ {
+ dumpn("*** unrecognized hostname (" + hostPort + ") in Host " +
+ "header, 400 time");
+ throw HTTP_400;
+ }
+
+ metadata._scheme = scheme;
+ metadata._host = host;
+ metadata._port = port;
+ }
+ }
+ else
+ {
+ NS_ASSERT(metadata._host === undefined,
+ "HTTP/1.0 doesn't allow absolute paths in the request line!");
+
+ metadata._scheme = identity.primaryScheme;
+ metadata._host = identity.primaryHost;
+ metadata._port = identity.primaryPort;
+ }
+
+ NS_ASSERT(identity.has(metadata._scheme, metadata._host, metadata._port),
+ "must have a location we recognize by now!");
+ },
+
+ /**
+* Handles responses in case of error, either in the server or in the request.
+*
+* @param e
+* the specific error encountered, which is an HttpError in the case where
+* the request is in some way invalid or cannot be fulfilled; if this isn't
+* an HttpError we're going to be paranoid and shut down, because that
+* shouldn't happen, ever
+*/
+ _handleError: function(e)
+ {
+ // Don't fall back into normal processing!
+ this._state = READER_FINISHED;
+
+ var server = this._connection.server;
+ if (e instanceof HttpError)
+ {
+ var code = e.code;
+ }
+ else
+ {
+ dumpn("!!! UNEXPECTED ERROR: " + e +
+ (e.lineNumber ? ", line " + e.lineNumber : ""));
+
+ // no idea what happened -- be paranoid and shut down
+ code = 500;
+ server._requestQuit();
+ }
+
+ // make attempted reuse of data an error
+ this._data = null;
+
+ this._connection.processError(code, this._metadata);
+ },
+
+ /**
+* Now that we've read the request line and headers, we can actually hand off
+* the request to be handled.
+*
+* This method is called once per request, after the request line and all
+* headers and the body, if any, have been received.
+*/
+ _handleResponse: function()
+ {
+ NS_ASSERT(this._state == READER_FINISHED);
+
+ // We don't need the line-based data any more, so make attempted reuse an
+ // error.
+ this._data = null;
+
+ this._connection.process(this._metadata);
+ },
+
+
+ // PARSING
+
+ /**
+* Parses the request line for the HTTP request associated with this.
+*
+* @param line : string
+* the request line
+*/
+ _parseRequestLine: function(line)
+ {
+ NS_ASSERT(this._state == READER_IN_REQUEST_LINE);
+
+ dumpn("*** _parseRequestLine('" + line + "')");
+
+ var metadata = this._metadata;
+
+ // clients and servers SHOULD accept any amount of SP or HT characters
+ // between fields, even though only a single SP is required (section 19.3)
+ var request = line.split(/[ \t]+/);
+ if (!request || request.length != 3)
+ throw HTTP_400;
+
+ metadata._method = request[0];
+
+ // get the HTTP version
+ var ver = request[2];
+ var match = ver.match(/^HTTP\/(\d+\.\d+)$/);
+ if (!match)
+ throw HTTP_400;
+
+ // determine HTTP version
+ try
+ {
+ metadata._httpVersion = new nsHttpVersion(match[1]);
+ if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_0))
+ throw "unsupported HTTP version";
+ }
+ catch (e)
+ {
+ // we support HTTP/1.0 and HTTP/1.1 only
+ throw HTTP_501;
+ }
+
+
+ var fullPath = request[1];
+ var serverIdentity = this._connection.server.identity;
+
+ var scheme, host, port;
+
+ if (fullPath.charAt(0) != "/")
+ {
+ // No absolute paths in the request line in HTTP prior to 1.1
+ if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1))
+ throw HTTP_400;
+
+ try
+ {
+ var uri = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService)
+ .newURI(fullPath, null, null);
+ fullPath = uri.path;
+ scheme = uri.scheme;
+ host = metadata._host = uri.asciiHost;
+ port = uri.port;
+ if (port === -1)
+ {
+ if (scheme === "http")
+ port = 80;
+ else if (scheme === "https")
+ port = 443;
+ else
+ throw HTTP_400;
+ }
+ }
+ catch (e)
+ {
+ // If the host is not a valid host on the server, the response MUST be a
+ // 400 (Bad Request) error message (section 5.2). Alternately, the URI
+ // is malformed.
+ throw HTTP_400;
+ }
+
+ if (!serverIdentity.has(scheme, host, port) || fullPath.charAt(0) != "/")
+ throw HTTP_400;
+ }
+
+ var splitter = fullPath.indexOf("?");
+ if (splitter < 0)
+ {
+ // _queryString already set in ctor
+ metadata._path = fullPath;
+ }
+ else
+ {
+ metadata._path = fullPath.substring(0, splitter);
+ metadata._queryString = fullPath.substring(splitter + 1);
+ }
+
+ metadata._scheme = scheme;
+ metadata._host = host;
+ metadata._port = port;
+ },
+
+ /**
+* Parses all available HTTP headers in this until the header-ending CRLFCRLF,
+* adding them to the store of headers in the request.
+*
+* @throws
+* HTTP_400 if the headers are malformed
+* @returns boolean
+* true if all headers have now been processed, false otherwise
+*/
+ _parseHeaders: function()
+ {
+ NS_ASSERT(this._state == READER_IN_HEADERS);
+
+ dumpn("*** _parseHeaders");
+
+ var data = this._data;
+
+ var headers = this._metadata._headers;
+ var lastName = this._lastHeaderName;
+ var lastVal = this._lastHeaderValue;
+
+ var line = {};
+ while (true)
+ {
+ NS_ASSERT(!((lastVal === undefined) ^ (lastName === undefined)),
+ lastName === undefined ?
+ "lastVal without lastName? lastVal: '" + lastVal + "'" :
+ "lastName without lastVal? lastName: '" + lastName + "'");
+
+ if (!data.readLine(line))
+ {
+ // save any data we have from the header we might still be processing
+ this._lastHeaderName = lastName;
+ this._lastHeaderValue = lastVal;
+ return false;
+ }
+
+ var lineText = line.value;
+ var firstChar = lineText.charAt(0);
+
+ // blank line means end of headers
+ if (lineText == "")
+ {
+ // we're finished with the previous header
+ if (lastName)
+ {
+ try
+ {
+ headers.setHeader(lastName, lastVal, true);
+ }
+ catch (e)
+ {
+ dumpn("*** e == " + e);
+ throw HTTP_400;
+ }
+ }
+ else
+ {
+ // no headers in request -- valid for HTTP/1.0 requests
+ }
+
+ // either way, we're done processing headers
+ this._state = READER_IN_BODY;
+ return true;
+ }
+ else if (firstChar == " " || firstChar == "\t")
+ {
+ // multi-line header if we've already seen a header line
+ if (!lastName)
+ {
+ // we don't have a header to continue!
+ throw HTTP_400;
+ }
+
+ // append this line's text to the value; starts with SP/HT, so no need
+ // for separating whitespace
+ lastVal += lineText;
+ }
+ else
+ {
+ // we have a new header, so set the old one (if one existed)
+ if (lastName)
+ {
+ try
+ {
+ headers.setHeader(lastName, lastVal, true);
+ }
+ catch (e)
+ {
+ dumpn("*** e == " + e);
+ throw HTTP_400;
+ }
+ }
+
+ var colon = lineText.indexOf(":"); // first colon must be splitter
+ if (colon < 1)
+ {
+ // no colon or missing header field-name
+ throw HTTP_400;
+ }
+
+ // set header name, value (to be set in the next loop, usually)
+ lastName = lineText.substring(0, colon);
+ lastVal = lineText.substring(colon + 1);
+ } // empty, continuation, start of header
+ } // while (true)
+ }
+};
+
+
+/** The character codes for CR and LF. */
+const CR = 0x0D, LF = 0x0A;
+
+/**
+* Calculates the number of characters before the first CRLF pair in array, or
+* -1 if the array contains no CRLF pair.
+*
+* @param array : Array
+* an array of numbers in the range [0, 256), each representing a single
+* character; the first CRLF is the lowest index i where
+* |array[i] == "\r".charCodeAt(0)| and |array[i+1] == "\n".charCodeAt(0)|,
+* if such an |i| exists, and -1 otherwise
+* @returns int
+* the index of the first CRLF if any were present, -1 otherwise
+*/
+function findCRLF(array)
+{
+ for (var i = array.indexOf(CR); i >= 0; i = array.indexOf(CR, i + 1))
+ {
+ if (array[i + 1] == LF)
+ return i;
+ }
+ return -1;
+}
+
+
+/**
+* A container which provides line-by-line access to the arrays of bytes with
+* which it is seeded.
+*/
+function LineData()
+{
+ /** An array of queued bytes from which to get line-based characters. */
+ this._data = [];
+}
+LineData.prototype =
+{
+ /**
+* Appends the bytes in the given array to the internal data cache maintained
+* by this.
+*/
+ appendBytes: function(bytes)
+ {
+ Array.prototype.push.apply(this._data, bytes);
+ },
+
+ /**
+* Removes and returns a line of data, delimited by CRLF, from this.
+*
+* @param out
+* an object whose "value" property will be set to the first line of text
+* present in this, sans CRLF, if this contains a full CRLF-delimited line
+* of text; if this doesn't contain enough data, the value of the property
+* is undefined
+* @returns boolean
+* true if a full line of data could be read from the data in this, false
+* otherwise
+*/
+ readLine: function(out)
+ {
+ var data = this._data;
+ var length = findCRLF(data);
+ if (length < 0)
+ return false;
+
+ //
+ // We have the index of the CR, so remove all the characters, including
+ // CRLF, from the array with splice, and convert the removed array into the
+ // corresponding string, from which we then strip the trailing CRLF.
+ //
+ // Getting the line in this matter acknowledges that substring is an O(1)
+ // operation in SpiderMonkey because strings are immutable, whereas two
+ // splices, both from the beginning of the data, are less likely to be as
+ // cheap as a single splice plus two extra character conversions.
+ //
+ var line = String.fromCharCode.apply(null, data.splice(0, length + 2));
+ out.value = line.substring(0, length);
+
+ return true;
+ },
+
+ /**
+* Removes the bytes currently within this and returns them in an array.
+*
+* @returns Array
+* the bytes within this when this method is called
+*/
+ purge: function()
+ {
+ var data = this._data;
+ this._data = [];
+ return data;
+ }
+};
+
+
+
+/**
+* Creates a request-handling function for an nsIHttpRequestHandler object.
+*/
+function createHandlerFunc(handler)
+{
+ return function(metadata, response) { handler.handle(metadata, response); };
+}
+
+
+/**
+* The default handler for directories; writes an HTML response containing a
+* slightly-formatted directory listing.
+*/
+function defaultIndexHandler(metadata, response)
+{
+ response.setHeader("Content-Type", "text/html", false);
+
+ var path = htmlEscape(decodeURI(metadata.path));
+
+ //
+ // Just do a very basic bit of directory listings -- no need for too much
+ // fanciness, especially since we don't have a style sheet in which we can
+ // stick rules (don't want to pollute the default path-space).
+ //
+
+ var body = '<html>\
+<head>\
+<title>' + path + '</title>\
+</head>\
+<body>\
+<h1>' + path + '</h1>\
+<ol style="list-style-type: none">';
+
+ var directory = metadata.getProperty("directory").QueryInterface(Ci.nsILocalFile);
+ NS_ASSERT(directory && directory.isDirectory());
+
+ var fileList = [];
+ var files = directory.directoryEntries;
+ while (files.hasMoreElements())
+ {
+ var f = files.getNext().QueryInterface(Ci.nsIFile);
+ var name = f.leafName;
+ if (!f.isHidden() &&
+ (name.charAt(name.length - 1) != HIDDEN_CHAR ||
+ name.charAt(name.length - 2) == HIDDEN_CHAR))
+ fileList.push(f);
+ }
+
+ fileList.sort(fileSort);
+
+ for (var i = 0; i < fileList.length; i++)
+ {
+ var file = fileList[i];
+ try
+ {
+ var name = file.leafName;
+ if (name.charAt(name.length - 1) == HIDDEN_CHAR)
+ name = name.substring(0, name.length - 1);
+ var sep = file.isDirectory() ? "/" : "";
+
+ // Note: using " to delimit the attribute here because encodeURIComponent
+ // passes through '.
+ var item = '<li><a href="' + encodeURIComponent(name) + sep + '">' +
+ htmlEscape(name) + sep +
+ '</a></li>';
+
+ body += item;
+ }
+ catch (e) { /* some file system error, ignore the file */ }
+ }
+
+ body += ' </ol>\
+</body>\
+</html>';
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+/**
+* Sorts a and b (nsIFile objects) into an aesthetically pleasing order.
+*/
+function fileSort(a, b)
+{
+ var dira = a.isDirectory(), dirb = b.isDirectory();
+
+ if (dira && !dirb)
+ return -1;
+ if (dirb && !dira)
+ return 1;
+
+ var namea = a.leafName.toLowerCase(), nameb = b.leafName.toLowerCase();
+ return nameb > namea ? -1 : 1;
+}
+
+
+/**
+* Converts an externally-provided path into an internal path for use in
+* determining file mappings.
+*
+* @param path
+* the path to convert
+* @param encoded
+* true if the given path should be passed through decodeURI prior to
+* conversion
+* @throws URIError
+* if path is incorrectly encoded
+*/
+function toInternalPath(path, encoded)
+{
+ if (encoded)
+ path = decodeURI(path);
+
+ var comps = path.split("/");
+ for (var i = 0, sz = comps.length; i < sz; i++)
+ {
+ var comp = comps[i];
+ if (comp.charAt(comp.length - 1) == HIDDEN_CHAR)
+ comps[i] = comp + HIDDEN_CHAR;
+ }
+ return comps.join("/");
+}
+
+
+/**
+* Adds custom-specified headers for the given file to the given response, if
+* any such headers are specified.
+*
+* @param file
+* the file on the disk which is to be written
+* @param metadata
+* metadata about the incoming request
+* @param response
+* the Response to which any specified headers/data should be written
+* @throws HTTP_500
+* if an error occurred while processing custom-specified headers
+*/
+function maybeAddHeaders(file, metadata, response)
+{
+ var name = file.leafName;
+ if (name.charAt(name.length - 1) == HIDDEN_CHAR)
+ name = name.substring(0, name.length - 1);
+
+ var headerFile = file.parent;
+ headerFile.append(name + HEADERS_SUFFIX);
+
+ if (!headerFile.exists())
+ return;
+
+ const PR_RDONLY = 0x01;
+ var fis = new FileInputStream(headerFile, PR_RDONLY, parseInt("444", 8),
+ Ci.nsIFileInputStream.CLOSE_ON_EOF);
+
+ try
+ {
+ var lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0);
+ lis.QueryInterface(Ci.nsIUnicharLineInputStream);
+
+ var line = {value: ""};
+ var more = lis.readLine(line);
+
+ if (!more && line.value == "")
+ return;
+
+
+ // request line
+
+ var status = line.value;
+ if (status.indexOf("HTTP ") == 0)
+ {
+ status = status.substring(5);
+ var space = status.indexOf(" ");
+ var code, description;
+ if (space < 0)
+ {
+ code = status;
+ description = "";
+ }
+ else
+ {
+ code = status.substring(0, space);
+ description = status.substring(space + 1, status.length);
+ }
+
+ response.setStatusLine(metadata.httpVersion, parseInt(code, 10), description);
+
+ line.value = "";
+ more = lis.readLine(line);
+ }
+
+ // headers
+ while (more || line.value != "")
+ {
+ var header = line.value;
+ var colon = header.indexOf(":");
+
+ response.setHeader(header.substring(0, colon),
+ header.substring(colon + 1, header.length),
+ false); // allow overriding server-set headers
+
+ line.value = "";
+ more = lis.readLine(line);
+ }
+ }
+ catch (e)
+ {
+ dumpn("WARNING: error in headers for " + metadata.path + ": " + e);
+ throw HTTP_500;
+ }
+ finally
+ {
+ fis.close();
+ }
+}
+
+
+/**
+* An object which handles requests for a server, executing default and
+* overridden behaviors as instructed by the code which uses and manipulates it.
+* Default behavior includes the paths / and /trace (diagnostics), with some
+* support for HTTP error pages for various codes and fallback to HTTP 500 if
+* those codes fail for any reason.
+*
+* @param server : nsHttpServer
+* the server in which this handler is being used
+*/
+function ServerHandler(server)
+{
+ // FIELDS
+
+ /**
+* The nsHttpServer instance associated with this handler.
+*/
+ this._server = server;
+
+ /**
+* A FileMap object containing the set of path->nsILocalFile mappings for
+* all directory mappings set in the server (e.g., "/" for /var/www/html/,
+* "/foo/bar/" for /local/path/, and "/foo/bar/baz/" for /local/path2).
+*
+* Note carefully: the leading and trailing "/" in each path (not file) are
+* removed before insertion to simplify the code which uses this. You have
+* been warned!
+*/
+ this._pathDirectoryMap = new FileMap();
+
+ /**
+* Custom request handlers for the server in which this resides. Path-handler
+* pairs are stored as property-value pairs in this property.
+*
+* @see ServerHandler.prototype._defaultPaths
+*/
+ this._overridePaths = {};
+
+ /**
+* Custom request handlers for the error handlers in the server in which this
+* resides. Path-handler pairs are stored as property-value pairs in this
+* property.
+*
+* @see ServerHandler.prototype._defaultErrors
+*/
+ this._overrideErrors = {};
+
+ /**
+* Maps file extensions to their MIME types in the server, overriding any
+* mapping that might or might not exist in the MIME service.
+*/
+ this._mimeMappings = {};
+
+ /**
+* The default handler for requests for directories, used to serve directories
+* when no index file is present.
+*/
+ this._indexHandler = defaultIndexHandler;
+
+ /** Per-path state storage for the server. */
+ this._state = {};
+
+ /** Entire-server state storage. */
+ this._sharedState = {};
+
+ /** Entire-server state storage for nsISupports values. */
+ this._objectState = {};
+}
+ServerHandler.prototype =
+{
+ // PUBLIC API
+
+ /**
+* Handles a request to this server, responding to the request appropriately
+* and initiating server shutdown if necessary.
+*
+* This method never throws an exception.
+*
+* @param connection : Connection
+* the connection for this request
+*/
+ handleResponse: function(connection)
+ {
+ var request = connection.request;
+ var response = new Response(connection);
+
+ var path = request.path;
+ dumpn("*** path == " + path);
+
+ try
+ {
+ try
+ {
+ if (path in this._overridePaths)
+ {
+ // explicit paths first, then files based on existing directory mappings,
+ // then (if the file doesn't exist) built-in server default paths
+ dumpn("calling override for " + path);
+ this._overridePaths[path](request, response);
+ }
+ else
+ {
+ this._handleDefault(request, response);
+ }
+ }
+ catch (e)
+ {
+ if (response.partiallySent())
+ {
+ response.abort(e);
+ return;
+ }
+
+ if (!(e instanceof HttpError))
+ {
+ dumpn("*** unexpected error: e == " + e);
+ throw HTTP_500;
+ }
+ if (e.code !== 404)
+ throw e;
+
+ dumpn("*** default: " + (path in this._defaultPaths));
+
+ response = new Response(connection);
+ if (path in this._defaultPaths)
+ this._defaultPaths[path](request, response);
+ else
+ throw HTTP_404;
+ }
+ }
+ catch (e)
+ {
+ if (response.partiallySent())
+ {
+ response.abort(e);
+ return;
+ }
+
+ var errorCode = "internal";
+
+ try
+ {
+ if (!(e instanceof HttpError))
+ throw e;
+
+ errorCode = e.code;
+ dumpn("*** errorCode == " + errorCode);
+
+ response = new Response(connection);
+ if (e.customErrorHandling)
+ e.customErrorHandling(response);
+ this._handleError(errorCode, request, response);
+ return;
+ }
+ catch (e2)
+ {
+ dumpn("*** error handling " + errorCode + " error: " +
+ "e2 == " + e2 + ", shutting down server");
+
+ connection.server._requestQuit();
+ response.abort(e2);
+ return;
+ }
+ }
+
+ response.complete();
+ },
+
+ //
+ // see nsIHttpServer.registerFile
+ //
+ registerFile: function(path, file)
+ {
+ if (!file)
+ {
+ dumpn("*** unregistering '" + path + "' mapping");
+ delete this._overridePaths[path];
+ return;
+ }
+
+ dumpn("*** registering '" + path + "' as mapping to " + file.path);
+ file = file.clone();
+
+ var self = this;
+ this._overridePaths[path] =
+ function(request, response)
+ {
+ if (!file.exists())
+ throw HTTP_404;
+
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ self._writeFileResponse(request, file, response, 0, file.fileSize);
+ };
+ },
+
+ //
+ // see nsIHttpServer.registerPathHandler
+ //
+ registerPathHandler: function(path, handler)
+ {
+ // XXX true path validation!
+ if (path.charAt(0) != "/")
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ this._handlerToField(handler, this._overridePaths, path);
+ },
+
+ //
+ // see nsIHttpServer.registerDirectory
+ //
+ registerDirectory: function(path, directory)
+ {
+ // strip off leading and trailing '/' so that we can use lastIndexOf when
+ // determining exactly how a path maps onto a mapped directory --
+ // conditional is required here to deal with "/".substring(1, 0) being
+ // converted to "/".substring(0, 1) per the JS specification
+ var key = path.length == 1 ? "" : path.substring(1, path.length - 1);
+
+ // the path-to-directory mapping code requires that the first character not
+ // be "/", or it will go into an infinite loop
+ if (key.charAt(0) == "/")
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ key = toInternalPath(key, false);
+
+ if (directory)
+ {
+ dumpn("*** mapping '" + path + "' to the location " + directory.path);
+ this._pathDirectoryMap.put(key, directory);
+ }
+ else
+ {
+ dumpn("*** removing mapping for '" + path + "'");
+ this._pathDirectoryMap.put(key, null);
+ }
+ },
+
+ //
+ // see nsIHttpServer.registerErrorHandler
+ //
+ registerErrorHandler: function(err, handler)
+ {
+ if (!(err in HTTP_ERROR_CODES))
+ dumpn("*** WARNING: registering non-HTTP/1.1 error code " +
+ "(" + err + ") handler -- was this intentional?");
+
+ this._handlerToField(handler, this._overrideErrors, err);
+ },
+
+ //
+ // see nsIHttpServer.setIndexHandler
+ //
+ setIndexHandler: function(handler)
+ {
+ if (!handler)
+ handler = defaultIndexHandler;
+ else if (typeof(handler) != "function")
+ handler = createHandlerFunc(handler);
+
+ this._indexHandler = handler;
+ },
+
+ //
+ // see nsIHttpServer.registerContentType
+ //
+ registerContentType: function(ext, type)
+ {
+ if (!type)
+ delete this._mimeMappings[ext];
+ else
+ this._mimeMappings[ext] = headerUtils.normalizeFieldValue(type);
+ },
+
+ // PRIVATE API
+
+ /**
+* Sets or remove (if handler is null) a handler in an object with a key.
+*
+* @param handler
+* a handler, either function or an nsIHttpRequestHandler
+* @param dict
+* The object to attach the handler to.
+* @param key
+* The field name of the handler.
+*/
+ _handlerToField: function(handler, dict, key)
+ {
+ // for convenience, handler can be a function if this is run from xpcshell
+ if (typeof(handler) == "function")
+ dict[key] = handler;
+ else if (handler)
+ dict[key] = createHandlerFunc(handler);
+ else
+ delete dict[key];
+ },
+
+ /**
+* Handles a request which maps to a file in the local filesystem (if a base
+* path has already been set; otherwise the 404 error is thrown).
+*
+* @param metadata : Request
+* metadata for the incoming request
+* @param response : Response
+* an uninitialized Response to the given request, to be initialized by a
+* request handler
+* @throws HTTP_###
+* if an HTTP error occurred (usually HTTP_404); note that in this case the
+* calling code must handle post-processing of the response
+*/
+ _handleDefault: function(metadata, response)
+ {
+ dumpn("*** _handleDefault()");
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+
+ var path = metadata.path;
+ NS_ASSERT(path.charAt(0) == "/", "invalid path: <" + path + ">");
+
+ // determine the actual on-disk file; this requires finding the deepest
+ // path-to-directory mapping in the requested URL
+ var file = this._getFileForPath(path);
+
+ // the "file" might be a directory, in which case we either serve the
+ // contained index.html or make the index handler write the response
+ if (file.exists() && file.isDirectory())
+ {
+ file.append("index.html"); // make configurable?
+ if (!file.exists() || file.isDirectory())
+ {
+ metadata._ensurePropertyBag();
+ metadata._bag.setPropertyAsInterface("directory", file.parent);
+ this._indexHandler(metadata, response);
+ return;
+ }
+ }
+
+ // alternately, the file might not exist
+ if (!file.exists())
+ throw HTTP_404;
+
+ var start, end;
+ if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1) &&
+ metadata.hasHeader("Range") &&
+ this._getTypeFromFile(file) !== SJS_TYPE)
+ {
+ var rangeMatch = metadata.getHeader("Range").match(/^bytes=(\d+)?-(\d+)?$/);
+ if (!rangeMatch)
+ throw HTTP_400;
+
+ if (rangeMatch[1] !== undefined)
+ start = parseInt(rangeMatch[1], 10);
+
+ if (rangeMatch[2] !== undefined)
+ end = parseInt(rangeMatch[2], 10);
+
+ if (start === undefined && end === undefined)
+ throw HTTP_400;
+
+ // No start given, so the end is really the count of bytes from the
+ // end of the file.
+ if (start === undefined)
+ {
+ start = Math.max(0, file.fileSize - end);
+ end = file.fileSize - 1;
+ }
+
+ // start and end are inclusive
+ if (end === undefined || end >= file.fileSize)
+ end = file.fileSize - 1;
+
+ if (start !== undefined && start >= file.fileSize) {
+ var HTTP_416 = new HttpError(416, "Requested Range Not Satisfiable");
+ HTTP_416.customErrorHandling = function(errorResponse)
+ {
+ maybeAddHeaders(file, metadata, errorResponse);
+ };
+ throw HTTP_416;
+ }
+
+ if (end < start)
+ {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ start = 0;
+ end = file.fileSize - 1;
+ }
+ else
+ {
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ var contentRange = "bytes " + start + "-" + end + "/" + file.fileSize;
+ response.setHeader("Content-Range", contentRange);
+ }
+ }
+ else
+ {
+ start = 0;
+ end = file.fileSize - 1;
+ }
+
+ // finally...
+ dumpn("*** handling '" + path + "' as mapping to " + file.path + " from " +
+ start + " to " + end + " inclusive");
+ this._writeFileResponse(metadata, file, response, start, end - start + 1);
+ },
+
+ /**
+* Writes an HTTP response for the given file, including setting headers for
+* file metadata.
+*
+* @param metadata : Request
+* the Request for which a response is being generated
+* @param file : nsILocalFile
+* the file which is to be sent in the response
+* @param response : Response
+* the response to which the file should be written
+* @param offset: uint
+* the byte offset to skip to when writing
+* @param count: uint
+* the number of bytes to write
+*/
+ _writeFileResponse: function(metadata, file, response, offset, count)
+ {
+ const PR_RDONLY = 0x01;
+
+ var type = this._getTypeFromFile(file);
+ if (type === SJS_TYPE)
+ {
+ var fis = new FileInputStream(file, PR_RDONLY, parseInt("444", 8),
+ Ci.nsIFileInputStream.CLOSE_ON_EOF);
+
+ try
+ {
+ var sis = new ScriptableInputStream(fis);
+ var s = Cu.Sandbox(gGlobalObject);
+ s.importFunction(dump, "dump");
+
+ // Define a basic key-value state-preservation API across requests, with
+ // keys initially corresponding to the empty string.
+ var self = this;
+ var path = metadata.path;
+ s.importFunction(function getState(k)
+ {
+ return self._getState(path, k);
+ });
+ s.importFunction(function setState(k, v)
+ {
+ self._setState(path, k, v);
+ });
+ s.importFunction(function getSharedState(k)
+ {
+ return self._getSharedState(k);
+ });
+ s.importFunction(function setSharedState(k, v)
+ {
+ self._setSharedState(k, v);
+ });
+ s.importFunction(function getObjectState(k, callback)
+ {
+ callback(self._getObjectState(k));
+ });
+ s.importFunction(function setObjectState(k, v)
+ {
+ self._setObjectState(k, v);
+ });
+ s.importFunction(function registerPathHandler(p, h)
+ {
+ self.registerPathHandler(p, h);
+ });
+
+ // Make it possible for sjs files to access their location
+ this._setState(path, "__LOCATION__", file.path);
+
+ try
+ {
+ // Alas, the line number in errors dumped to console when calling the
+ // request handler is simply an offset from where we load the SJS file.
+ // Work around this in a reasonably non-fragile way by dynamically
+ // getting the line number where we evaluate the SJS file. Don't
+ // separate these two lines!
+ var line = new Error().lineNumber;
+ Cu.evalInSandbox(sis.read(file.fileSize), s);
+ }
+ catch (e)
+ {
+ dumpn("*** syntax error in SJS at " + file.path + ": " + e);
+ throw HTTP_500;
+ }
+
+ try
+ {
+ s.handleRequest(metadata, response);
+ }
+ catch (e)
+ {
+ dump("*** error running SJS at " + file.path + ": " +
+ e + " on line " +
+ (e instanceof Error
+ ? e.lineNumber + " in httpd.js"
+ : (e.lineNumber - line)) + "\n");
+ throw HTTP_500;
+ }
+ }
+ finally
+ {
+ fis.close();
+ }
+ }
+ else
+ {
+ try
+ {
+ response.setHeader("Last-Modified",
+ toDateString(file.lastModifiedTime),
+ false);
+ }
+ catch (e) { /* lastModifiedTime threw, ignore */ }
+
+ response.setHeader("Content-Type", type, false);
+ maybeAddHeaders(file, metadata, response);
+ response.setHeader("Content-Length", "" + count, false);
+
+ var fis = new FileInputStream(file, PR_RDONLY, parseInt("444", 8),
+ Ci.nsIFileInputStream.CLOSE_ON_EOF);
+
+ offset = offset || 0;
+ count = count || file.fileSize;
+ NS_ASSERT(offset === 0 || offset < file.fileSize, "bad offset");
+ NS_ASSERT(count >= 0, "bad count");
+ NS_ASSERT(offset + count <= file.fileSize, "bad total data size");
+
+ try
+ {
+ if (offset !== 0)
+ {
+ // Seek (or read, if seeking isn't supported) to the correct offset so
+ // the data sent to the client matches the requested range.
+ if (fis instanceof Ci.nsISeekableStream)
+ fis.seek(Ci.nsISeekableStream.NS_SEEK_SET, offset);
+ else
+ new ScriptableInputStream(fis).read(offset);
+ }
+ }
+ catch (e)
+ {
+ fis.close();
+ throw e;
+ }
+
+ function writeMore()
+ {
+ gThreadManager.currentThread
+ .dispatch(writeData, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+
+ var input = new BinaryInputStream(fis);
+ var output = new BinaryOutputStream(response.bodyOutputStream);
+ var writeData =
+ {
+ run: function()
+ {
+ var chunkSize = Math.min(65536, count);
+ count -= chunkSize;
+ NS_ASSERT(count >= 0, "underflow");
+
+ try
+ {
+ var data = input.readByteArray(chunkSize);
+ NS_ASSERT(data.length === chunkSize,
+ "incorrect data returned? got " + data.length +
+ ", expected " + chunkSize);
+ output.writeByteArray(data, data.length);
+ if (count === 0)
+ {
+ fis.close();
+ response.finish();
+ }
+ else
+ {
+ writeMore();
+ }
+ }
+ catch (e)
+ {
+ try
+ {
+ fis.close();
+ }
+ finally
+ {
+ response.finish();
+ }
+ throw e;
+ }
+ }
+ };
+
+ writeMore();
+
+ // Now that we know copying will start, flag the response as async.
+ response.processAsync();
+ }
+ },
+
+ /**
+* Get the value corresponding to a given key for the given path for SJS state
+* preservation across requests.
+*
+* @param path : string
+* the path from which the given state is to be retrieved
+* @param k : string
+* the key whose corresponding value is to be returned
+* @returns string
+* the corresponding value, which is initially the empty string
+*/
+ _getState: function(path, k)
+ {
+ var state = this._state;
+ if (path in state && k in state[path])
+ return state[path][k];
+ return "";
+ },
+
+ /**
+* Set the value corresponding to a given key for the given path for SJS state
+* preservation across requests.
+*
+* @param path : string
+* the path from which the given state is to be retrieved
+* @param k : string
+* the key whose corresponding value is to be set
+* @param v : string
+* the value to be set
+*/
+ _setState: function(path, k, v)
+ {
+ if (typeof v !== "string")
+ throw new Error("non-string value passed");
+ var state = this._state;
+ if (!(path in state))
+ state[path] = {};
+ state[path][k] = v;
+ },
+
+ /**
+* Get the value corresponding to a given key for SJS state preservation
+* across requests.
+*
+* @param k : string
+* the key whose corresponding value is to be returned
+* @returns string
+* the corresponding value, which is initially the empty string
+*/
+ _getSharedState: function(k)
+ {
+ var state = this._sharedState;
+ if (k in state)
+ return state[k];
+ return "";
+ },
+
+ /**
+* Set the value corresponding to a given key for SJS state preservation
+* across requests.
+*
+* @param k : string
+* the key whose corresponding value is to be set
+* @param v : string
+* the value to be set
+*/
+ _setSharedState: function(k, v)
+ {
+ if (typeof v !== "string")
+ throw new Error("non-string value passed");
+ this._sharedState[k] = v;
+ },
+
+ /**
+* Returns the object associated with the given key in the server for SJS
+* state preservation across requests.
+*
+* @param k : string
+* the key whose corresponding object is to be returned
+* @returns nsISupports
+* the corresponding object, or null if none was present
+*/
+ _getObjectState: function(k)
+ {
+ if (typeof k !== "string")
+ throw new Error("non-string key passed");
+ return this._objectState[k] || null;
+ },
+
+ /**
+* Sets the object associated with the given key in the server for SJS
+* state preservation across requests.
+*
+* @param k : string
+* the key whose corresponding object is to be set
+* @param v : nsISupports
+* the object to be associated with the given key; may be null
+*/
+ _setObjectState: function(k, v)
+ {
+ if (typeof k !== "string")
+ throw new Error("non-string key passed");
+ if (typeof v !== "object")
+ throw new Error("non-object value passed");
+ if (v && !("QueryInterface" in v))
+ {
+ throw new Error("must pass an nsISupports; use wrappedJSObject to ease " +
+ "pain when using the server from JS");
+ }
+
+ this._objectState[k] = v;
+ },
+
+ /**
+* Gets a content-type for the given file, first by checking for any custom
+* MIME-types registered with this handler for the file's extension, second by
+* asking the global MIME service for a content-type, and finally by failing
+* over to application/octet-stream.
+*
+* @param file : nsIFile
+* the nsIFile for which to get a file type
+* @returns string
+* the best content-type which can be determined for the file
+*/
+ _getTypeFromFile: function(file)
+ {
+ try
+ {
+ var name = file.leafName;
+ var dot = name.lastIndexOf(".");
+ if (dot > 0)
+ {
+ var ext = name.slice(dot + 1);
+ if (ext in this._mimeMappings)
+ return this._mimeMappings[ext];
+ }
+ return Cc["@mozilla.org/uriloader/external-helper-app-service;1"]
+ .getService(Ci.nsIMIMEService)
+ .getTypeFromFile(file);
+ }
+ catch (e)
+ {
+ return "application/octet-stream";
+ }
+ },
+
+ /**
+* Returns the nsILocalFile which corresponds to the path, as determined using
+* all registered path->directory mappings and any paths which are explicitly
+* overridden.
+*
+* @param path : string
+* the server path for which a file should be retrieved, e.g. "/foo/bar"
+* @throws HttpError
+* when the correct action is the corresponding HTTP error (i.e., because no
+* mapping was found for a directory in path, the referenced file doesn't
+* exist, etc.)
+* @returns nsILocalFile
+* the file to be sent as the response to a request for the path
+*/
+ _getFileForPath: function(path)
+ {
+ // decode and add underscores as necessary
+ try
+ {
+ path = toInternalPath(path, true);
+ }
+ catch (e)
+ {
+ throw HTTP_400; // malformed path
+ }
+
+ // next, get the directory which contains this path
+ var pathMap = this._pathDirectoryMap;
+
+ // An example progression of tmp for a path "/foo/bar/baz/" might be:
+ // "foo/bar/baz/", "foo/bar/baz", "foo/bar", "foo", ""
+ var tmp = path.substring(1);
+ while (true)
+ {
+ // do we have a match for current head of the path?
+ var file = pathMap.get(tmp);
+ if (file)
+ {
+ // XXX hack; basically disable showing mapping for /foo/bar/ when the
+ // requested path was /foo/bar, because relative links on the page
+ // will all be incorrect -- we really need the ability to easily
+ // redirect here instead
+ if (tmp == path.substring(1) &&
+ tmp.length != 0 &&
+ tmp.charAt(tmp.length - 1) != "/")
+ file = null;
+ else
+ break;
+ }
+
+ // if we've finished trying all prefixes, exit
+ if (tmp == "")
+ break;
+
+ tmp = tmp.substring(0, tmp.lastIndexOf("/"));
+ }
+
+ // no mapping applies, so 404
+ if (!file)
+ throw HTTP_404;
+
+
+ // last, get the file for the path within the determined directory
+ var parentFolder = file.parent;
+ var dirIsRoot = (parentFolder == null);
+
+ // Strategy here is to append components individually, making sure we
+ // never move above the given directory; this allows paths such as
+ // "<file>/foo/../bar" but prevents paths such as "<file>/../base-sibling";
+ // this component-wise approach also means the code works even on platforms
+ // which don't use "/" as the directory separator, such as Windows
+ var leafPath = path.substring(tmp.length + 1);
+ var comps = leafPath.split("/");
+ for (var i = 0, sz = comps.length; i < sz; i++)
+ {
+ var comp = comps[i];
+
+ if (comp == "..")
+ file = file.parent;
+ else if (comp == "." || comp == "")
+ continue;
+ else
+ file.append(comp);
+
+ if (!dirIsRoot && file.equals(parentFolder))
+ throw HTTP_403;
+ }
+
+ return file;
+ },
+
+ /**
+* Writes the error page for the given HTTP error code over the given
+* connection.
+*
+* @param errorCode : uint
+* the HTTP error code to be used
+* @param connection : Connection
+* the connection on which the error occurred
+*/
+ handleError: function(errorCode, connection)
+ {
+ var response = new Response(connection);
+
+ dumpn("*** error in request: " + errorCode);
+
+ this._handleError(errorCode, new Request(connection.port), response);
+ },
+
+ /**
+* Handles a request which generates the given error code, using the
+* user-defined error handler if one has been set, gracefully falling back to
+* the x00 status code if the code has no handler, and failing to status code
+* 500 if all else fails.
+*
+* @param errorCode : uint
+* the HTTP error which is to be returned
+* @param metadata : Request
+* metadata for the request, which will often be incomplete since this is an
+* error
+* @param response : Response
+* an uninitialized Response should be initialized when this method
+* completes with information which represents the desired error code in the
+* ideal case or a fallback code in abnormal circumstances (i.e., 500 is a
+* fallback for 505, per HTTP specs)
+*/
+ _handleError: function(errorCode, metadata, response)
+ {
+ if (!metadata)
+ throw Cr.NS_ERROR_NULL_POINTER;
+
+ var errorX00 = errorCode - (errorCode % 100);
+
+ try
+ {
+ if (!(errorCode in HTTP_ERROR_CODES))
+ dumpn("*** WARNING: requested invalid error: " + errorCode);
+
+ // RFC 2616 says that we should try to handle an error by its class if we
+ // can't otherwise handle it -- if that fails, we revert to handling it as
+ // a 500 internal server error, and if that fails we throw and shut down
+ // the server
+
+ // actually handle the error
+ try
+ {
+ if (errorCode in this._overrideErrors)
+ this._overrideErrors[errorCode](metadata, response);
+ else
+ this._defaultErrors[errorCode](metadata, response);
+ }
+ catch (e)
+ {
+ if (response.partiallySent())
+ {
+ response.abort(e);
+ return;
+ }
+
+ // don't retry the handler that threw
+ if (errorX00 == errorCode)
+ throw HTTP_500;
+
+ dumpn("*** error in handling for error code " + errorCode + ", " +
+ "falling back to " + errorX00 + "...");
+ response = new Response(response._connection);
+ if (errorX00 in this._overrideErrors)
+ this._overrideErrors[errorX00](metadata, response);
+ else if (errorX00 in this._defaultErrors)
+ this._defaultErrors[errorX00](metadata, response);
+ else
+ throw HTTP_500;
+ }
+ }
+ catch (e)
+ {
+ if (response.partiallySent())
+ {
+ response.abort();
+ return;
+ }
+
+ // we've tried everything possible for a meaningful error -- now try 500
+ dumpn("*** error in handling for error code " + errorX00 + ", falling " +
+ "back to 500...");
+
+ try
+ {
+ response = new Response(response._connection);
+ if (500 in this._overrideErrors)
+ this._overrideErrors[500](metadata, response);
+ else
+ this._defaultErrors[500](metadata, response);
+ }
+ catch (e2)
+ {
+ dumpn("*** multiple errors in default error handlers!");
+ dumpn("*** e == " + e + ", e2 == " + e2);
+ response.abort(e2);
+ return;
+ }
+ }
+
+ response.complete();
+ },
+
+ // FIELDS
+
+ /**
+* This object contains the default handlers for the various HTTP error codes.
+*/
+ _defaultErrors:
+ {
+ 400: function(metadata, response)
+ {
+ // none of the data in metadata is reliable, so hard-code everything here
+ response.setStatusLine("1.1", 400, "Bad Request");
+ response.setHeader("Content-Type", "text/plain", false);
+
+ var body = "Bad request\n";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 403: function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion, 403, "Forbidden");
+ response.setHeader("Content-Type", "text/html", false);
+
+ var body = "<html>\
+<head><title>403 Forbidden</title></head>\
+<body>\
+<h1>403 Forbidden</h1>\
+</body>\
+</html>";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 404: function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+ response.setHeader("Content-Type", "text/html", false);
+
+ var body = "<html>\
+<head><title>404 Not Found</title></head>\
+<body>\
+<h1>404 Not Found</h1>\
+<p>\
+<span style='font-family: monospace;'>" +
+ htmlEscape(metadata.path) +
+ "</span> was not found.\
+</p>\
+</body>\
+</html>";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 416: function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion,
+ 416,
+ "Requested Range Not Satisfiable");
+ response.setHeader("Content-Type", "text/html", false);
+
+ var body = "<html>\
+<head>\
+<title>416 Requested Range Not Satisfiable</title></head>\
+<body>\
+<h1>416 Requested Range Not Satisfiable</h1>\
+<p>The byte range was not valid for the\
+requested resource.\
+</p>\
+</body>\
+</html>";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 500: function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion,
+ 500,
+ "Internal Server Error");
+ response.setHeader("Content-Type", "text/html", false);
+
+ var body = "<html>\
+<head><title>500 Internal Server Error</title></head>\
+<body>\
+<h1>500 Internal Server Error</h1>\
+<p>Something's broken in this server and\
+needs to be fixed.</p>\
+</body>\
+</html>";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 501: function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion, 501, "Not Implemented");
+ response.setHeader("Content-Type", "text/html", false);
+
+ var body = "<html>\
+<head><title>501 Not Implemented</title></head>\
+<body>\
+<h1>501 Not Implemented</h1>\
+<p>This server is not (yet) Apache.</p>\
+</body>\
+</html>";
+ response.bodyOutputStream.write(body, body.length);
+ },
+ 505: function(metadata, response)
+ {
+ response.setStatusLine("1.1", 505, "HTTP Version Not Supported");
+ response.setHeader("Content-Type", "text/html", false);
+
+ var body = "<html>\
+<head><title>505 HTTP Version Not Supported</title></head>\
+<body>\
+<h1>505 HTTP Version Not Supported</h1>\
+<p>This server only supports HTTP/1.0 and HTTP/1.1\
+connections.</p>\
+</body>\
+</html>";
+ response.bodyOutputStream.write(body, body.length);
+ }
+ },
+
+ /**
+* Contains handlers for the default set of URIs contained in this server.
+*/
+ _defaultPaths:
+ {
+ "/": function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html", false);
+
+ var body = "<html>\
+<head><title>httpd.js</title></head>\
+<body>\
+<h1>httpd.js</h1>\
+<p>If you're seeing this page, httpd.js is up and\
+serving requests! Now set a base path and serve some\
+files!</p>\
+</body>\
+</html>";
+
+ response.bodyOutputStream.write(body, body.length);
+ },
+
+ "/trace": function(metadata, response)
+ {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+
+ var body = "Request-URI: " +
+ metadata.scheme + "://" + metadata.host + ":" + metadata.port +
+ metadata.path + "\n\n";
+ body += "Request (semantically equivalent, slightly reformatted):\n\n";
+ body += metadata.method + " " + metadata.path;
+
+ if (metadata.queryString)
+ body += "?" + metadata.queryString;
+
+ body += " HTTP/" + metadata.httpVersion + "\r\n";
+
+ var headEnum = metadata.headers;
+ while (headEnum.hasMoreElements())
+ {
+ var fieldName = headEnum.getNext()
+ .QueryInterface(Ci.nsISupportsString)
+ .data;
+ body += fieldName + ": " + metadata.getHeader(fieldName) + "\r\n";
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+ }
+ }
+};
+
+
+/**
+* Maps absolute paths to files on the local file system (as nsILocalFiles).
+*/
+function FileMap()
+{
+ /** Hash which will map paths to nsILocalFiles. */
+ this._map = {};
+}
+FileMap.prototype =
+{
+ // PUBLIC API
+
+ /**
+* Maps key to a clone of the nsILocalFile value if value is non-null;
+* otherwise, removes any extant mapping for key.
+*
+* @param key : string
+* string to which a clone of value is mapped
+* @param value : nsILocalFile
+* the file to map to key, or null to remove a mapping
+*/
+ put: function(key, value)
+ {
+ if (value)
+ this._map[key] = value.clone();
+ else
+ delete this._map[key];
+ },
+
+ /**
+* Returns a clone of the nsILocalFile mapped to key, or null if no such
+* mapping exists.
+*
+* @param key : string
+* key to which the returned file maps
+* @returns nsILocalFile
+* a clone of the mapped file, or null if no mapping exists
+*/
+ get: function(key)
+ {
+ var val = this._map[key];
+ return val ? val.clone() : null;
+ }
+};
+
+
+// Response CONSTANTS
+
+// token = *<any CHAR except CTLs or separators>
+// CHAR = <any US-ASCII character (0-127)>
+// CTL = <any US-ASCII control character (0-31) and DEL (127)>
+// separators = "(" | ")" | "<" | ">" | "@"
+// | "," | ";" | ":" | "\" | <">
+// | "/" | "[" | "]" | "?" | "="
+// | "{" | "}" | SP | HT
+const IS_TOKEN_ARRAY =
+ [0, 0, 0, 0, 0, 0, 0, 0, // 0
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8
+ 0, 0, 0, 0, 0, 0, 0, 0, // 16
+ 0, 0, 0, 0, 0, 0, 0, 0, // 24
+
+ 0, 1, 0, 1, 1, 1, 1, 1, // 32
+ 0, 0, 1, 1, 0, 1, 1, 0, // 40
+ 1, 1, 1, 1, 1, 1, 1, 1, // 48
+ 1, 1, 0, 0, 0, 0, 0, 0, // 56
+
+ 0, 1, 1, 1, 1, 1, 1, 1, // 64
+ 1, 1, 1, 1, 1, 1, 1, 1, // 72
+ 1, 1, 1, 1, 1, 1, 1, 1, // 80
+ 1, 1, 1, 0, 0, 0, 1, 1, // 88
+
+ 1, 1, 1, 1, 1, 1, 1, 1, // 96
+ 1, 1, 1, 1, 1, 1, 1, 1, // 104
+ 1, 1, 1, 1, 1, 1, 1, 1, // 112
+ 1, 1, 1, 0, 1, 0, 1]; // 120
+
+
+/**
+* Determines whether the given character code is a CTL.
+*
+* @param code : uint
+* the character code
+* @returns boolean
+* true if code is a CTL, false otherwise
+*/
+function isCTL(code)
+{
+ return (code >= 0 && code <= 31) || (code == 127);
+}
+
+/**
+* Represents a response to an HTTP request, encapsulating all details of that
+* response. This includes all headers, the HTTP version, status code and
+* explanation, and the entity itself.
+*
+* @param connection : Connection
+* the connection over which this response is to be written
+*/
+function Response(connection)
+{
+ /** The connection over which this response will be written. */
+ this._connection = connection;
+
+ /**
+* The HTTP version of this response; defaults to 1.1 if not set by the
+* handler.
+*/
+ this._httpVersion = nsHttpVersion.HTTP_1_1;
+
+ /**
+* The HTTP code of this response; defaults to 200.
+*/
+ this._httpCode = 200;
+
+ /**
+* The description of the HTTP code in this response; defaults to "OK".
+*/
+ this._httpDescription = "OK";
+
+ /**
+* An nsIHttpHeaders object in which the headers in this response should be
+* stored. This property is null after the status line and headers have been
+* written to the network, and it may be modified up until it is cleared,
+* except if this._finished is set first (in which case headers are written
+* asynchronously in response to a finish() call not preceded by
+* flushHeaders()).
+*/
+ this._headers = new nsHttpHeaders();
+
+ /**
+* Set to true when this response is ended (completely constructed if possible
+* and the connection closed); further actions on this will then fail.
+*/
+ this._ended = false;
+
+ /**
+* A stream used to hold data written to the body of this response.
+*/
+ this._bodyOutputStream = null;
+
+ /**
+* A stream containing all data that has been written to the body of this
+* response so far. (Async handlers make the data contained in this
+* unreliable as a way of determining content length in general, but auxiliary
+* saved information can sometimes be used to guarantee reliability.)
+*/
+ this._bodyInputStream = null;
+
+ /**
+* A stream copier which copies data to the network. It is initially null
+* until replaced with a copier for response headers; when headers have been
+* fully sent it is replaced with a copier for the response body, remaining
+* so for the duration of response processing.
+*/
+ this._asyncCopier = null;
+
+ /**
+* True if this response has been designated as being processed
+* asynchronously rather than for the duration of a single call to
+* nsIHttpRequestHandler.handle.
+*/
+ this._processAsync = false;
+
+ /**
+* True iff finish() has been called on this, signaling that no more changes
+* to this may be made.
+*/
+ this._finished = false;
+
+ /**
+* True iff powerSeized() has been called on this, signaling that this
+* response is to be handled manually by the response handler (which may then
+* send arbitrary data in response, even non-HTTP responses).
+*/
+ this._powerSeized = false;
+}
+Response.prototype =
+{
+ // PUBLIC CONSTRUCTION API
+
+ //
+ // see nsIHttpResponse.bodyOutputStream
+ //
+ get bodyOutputStream()
+ {
+ if (this._finished)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+ if (!this._bodyOutputStream)
+ {
+ var pipe = new Pipe(true, false, Response.SEGMENT_SIZE, PR_UINT32_MAX,
+ null);
+ this._bodyOutputStream = pipe.outputStream;
+ this._bodyInputStream = pipe.inputStream;
+ if (this._processAsync || this._powerSeized)
+ this._startAsyncProcessor();
+ }
+
+ return this._bodyOutputStream;
+ },
+
+ //
+ // see nsIHttpResponse.write
+ //
+ write: function(data)
+ {
+ if (this._finished)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+ var dataAsString = String(data);
+ this.bodyOutputStream.write(dataAsString, dataAsString.length);
+ },
+
+ //
+ // see nsIHttpResponse.setStatusLine
+ //
+ setStatusLine: function(httpVersion, code, description)
+ {
+ if (!this._headers || this._finished || this._powerSeized)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ this._ensureAlive();
+
+ if (!(code >= 0 && code < 1000))
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ try
+ {
+ var httpVer;
+ // avoid version construction for the most common cases
+ if (!httpVersion || httpVersion == "1.1")
+ httpVer = nsHttpVersion.HTTP_1_1;
+ else if (httpVersion == "1.0")
+ httpVer = nsHttpVersion.HTTP_1_0;
+ else
+ httpVer = new nsHttpVersion(httpVersion);
+ }
+ catch (e)
+ {
+ throw Cr.NS_ERROR_INVALID_ARG;
+ }
+
+ // Reason-Phrase = *<TEXT, excluding CR, LF>
+ // TEXT = <any OCTET except CTLs, but including LWS>
+ //
+ // XXX this ends up disallowing octets which aren't Unicode, I think -- not
+ // much to do if description is IDL'd as string
+ if (!description)
+ description = "";
+ for (var i = 0; i < description.length; i++)
+ if (isCTL(description.charCodeAt(i)) && description.charAt(i) != "\t")
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ // set the values only after validation to preserve atomicity
+ this._httpDescription = description;
+ this._httpCode = code;
+ this._httpVersion = httpVer;
+ },
+
+ //
+ // see nsIHttpResponse.setHeader
+ //
+ setHeader: function(name, value, merge)
+ {
+ if (!this._headers || this._finished || this._powerSeized)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ this._ensureAlive();
+
+ this._headers.setHeader(name, value, merge);
+ },
+
+ //
+ // see nsIHttpResponse.processAsync
+ //
+ processAsync: function()
+ {
+ if (this._finished)
+ throw Cr.NS_ERROR_UNEXPECTED;
+ if (this._powerSeized)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ if (this._processAsync)
+ return;
+ this._ensureAlive();
+
+ dumpn("*** processing connection " + this._connection.number + " async");
+ this._processAsync = true;
+
+ /*
+* Either the bodyOutputStream getter or this method is responsible for
+* starting the asynchronous processor and catching writes of data to the
+* response body of async responses as they happen, for the purpose of
+* forwarding those writes to the actual connection's output stream.
+* If bodyOutputStream is accessed first, calling this method will create
+* the processor (when it first is clear that body data is to be written
+* immediately, not buffered). If this method is called first, accessing
+* bodyOutputStream will create the processor. If only this method is
+* called, we'll write nothing, neither headers nor the nonexistent body,
+* until finish() is called. Since that delay is easily avoided by simply
+* getting bodyOutputStream or calling write(""), we don't worry about it.
+*/
+ if (this._bodyOutputStream && !this._asyncCopier)
+ this._startAsyncProcessor();
+ },
+
+ //
+ // see nsIHttpResponse.seizePower
+ //
+ seizePower: function()
+ {
+ if (this._processAsync)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ if (this._finished)
+ throw Cr.NS_ERROR_UNEXPECTED;
+ if (this._powerSeized)
+ return;
+ this._ensureAlive();
+
+ dumpn("*** forcefully seizing power over connection " +
+ this._connection.number + "...");
+
+ // Purge any already-written data without sending it. We could as easily
+ // swap out the streams entirely, but that makes it possible to acquire and
+ // unknowingly use a stale reference, so we require there only be one of
+ // each stream ever for any response to avoid this complication.
+ if (this._asyncCopier)
+ this._asyncCopier.cancel(Cr.NS_BINDING_ABORTED);
+ this._asyncCopier = null;
+ if (this._bodyOutputStream)
+ {
+ var input = new BinaryInputStream(this._bodyInputStream);
+ var avail;
+ while ((avail = input.available()) > 0)
+ input.readByteArray(avail);
+ }
+
+ this._powerSeized = true;
+ if (this._bodyOutputStream)
+ this._startAsyncProcessor();
+ },
+
+ //
+ // see nsIHttpResponse.finish
+ //
+ finish: function()
+ {
+ if (!this._processAsync && !this._powerSeized)
+ throw Cr.NS_ERROR_UNEXPECTED;
+ if (this._finished)
+ return;
+
+ dumpn("*** finishing connection " + this._connection.number);
+ this._startAsyncProcessor(); // in case bodyOutputStream was never accessed
+ if (this._bodyOutputStream)
+ this._bodyOutputStream.close();
+ this._finished = true;
+ },
+
+
+ // NSISUPPORTS
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIHttpResponse) || iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // POST-CONSTRUCTION API (not exposed externally)
+
+ /**
+* The HTTP version number of this, as a string (e.g. "1.1").
+*/
+ get httpVersion()
+ {
+ this._ensureAlive();
+ return this._httpVersion.toString();
+ },
+
+ /**
+* The HTTP status code of this response, as a string of three characters per
+* RFC 2616.
+*/
+ get httpCode()
+ {
+ this._ensureAlive();
+
+ var codeString = (this._httpCode < 10 ? "0" : "") +
+ (this._httpCode < 100 ? "0" : "") +
+ this._httpCode;
+ return codeString;
+ },
+
+ /**
+* The description of the HTTP status code of this response, or "" if none is
+* set.
+*/
+ get httpDescription()
+ {
+ this._ensureAlive();
+
+ return this._httpDescription;
+ },
+
+ /**
+* The headers in this response, as an nsHttpHeaders object.
+*/
+ get headers()
+ {
+ this._ensureAlive();
+
+ return this._headers;
+ },
+
+ //
+ // see nsHttpHeaders.getHeader
+ //
+ getHeader: function(name)
+ {
+ this._ensureAlive();
+
+ return this._headers.getHeader(name);
+ },
+
+ /**
+* Determines whether this response may be abandoned in favor of a newly
+* constructed response. A response may be abandoned only if it is not being
+* sent asynchronously and if raw control over it has not been taken from the
+* server.
+*
+* @returns boolean
+* true iff no data has been written to the network
+*/
+ partiallySent: function()
+ {
+ dumpn("*** partiallySent()");
+ return this._processAsync || this._powerSeized;
+ },
+
+ /**
+* If necessary, kicks off the remaining request processing needed to be done
+* after a request handler performs its initial work upon this response.
+*/
+ complete: function()
+ {
+ dumpn("*** complete()");
+ if (this._processAsync || this._powerSeized)
+ {
+ NS_ASSERT(this._processAsync ^ this._powerSeized,
+ "can't both send async and relinquish power");
+ return;
+ }
+
+ NS_ASSERT(!this.partiallySent(), "completing a partially-sent response?");
+
+ this._startAsyncProcessor();
+
+ // Now make sure we finish processing this request!
+ if (this._bodyOutputStream)
+ this._bodyOutputStream.close();
+ },
+
+ /**
+* Abruptly ends processing of this response, usually due to an error in an
+* incoming request but potentially due to a bad error handler. Since we
+* cannot handle the error in the usual way (giving an HTTP error page in
+* response) because data may already have been sent (or because the response
+* might be expected to have been generated asynchronously or completely from
+* scratch by the handler), we stop processing this response and abruptly
+* close the connection.
+*
+* @param e : Error
+* the exception which precipitated this abort, or null if no such exception
+* was generated
+*/
+ abort: function(e)
+ {
+ dumpn("*** abort(<" + e + ">)");
+
+ // This response will be ended by the processor if one was created.
+ var copier = this._asyncCopier;
+ if (copier)
+ {
+ // We dispatch asynchronously here so that any pending writes of data to
+ // the connection will be deterministically written. This makes it easier
+ // to specify exact behavior, and it makes observable behavior more
+ // predictable for clients. Note that the correctness of this depends on
+ // callbacks in response to _waitToReadData in WriteThroughCopier
+ // happening asynchronously with respect to the actual writing of data to
+ // bodyOutputStream, as they currently do; if they happened synchronously,
+ // an event which ran before this one could write more data to the
+ // response body before we get around to canceling the copier. We have
+ // tests for this in test_seizepower.js, however, and I can't think of a
+ // way to handle both cases without removing bodyOutputStream access and
+ // moving its effective write(data, length) method onto Response, which
+ // would be slower and require more code than this anyway.
+ gThreadManager.currentThread.dispatch({
+ run: function()
+ {
+ dumpn("*** canceling copy asynchronously...");
+ copier.cancel(Cr.NS_ERROR_UNEXPECTED);
+ }
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ else
+ {
+ this.end();
+ }
+ },
+
+ /**
+* Closes this response's network connection, marks the response as finished,
+* and notifies the server handler that the request is done being processed.
+*/
+ end: function()
+ {
+ NS_ASSERT(!this._ended, "ending this response twice?!?!");
+
+ this._connection.close();
+ if (this._bodyOutputStream)
+ this._bodyOutputStream.close();
+
+ this._finished = true;
+ this._ended = true;
+ },
+
+ // PRIVATE IMPLEMENTATION
+
+ /**
+* Sends the status line and headers of this response if they haven't been
+* sent and initiates the process of copying data written to this response's
+* body to the network.
+*/
+ _startAsyncProcessor: function()
+ {
+ dumpn("*** _startAsyncProcessor()");
+
+ // Handle cases where we're being called a second time. The former case
+ // happens when this is triggered both by complete() and by processAsync(),
+ // while the latter happens when processAsync() in conjunction with sent
+ // data causes abort() to be called.
+ if (this._asyncCopier || this._ended)
+ {
+ dumpn("*** ignoring second call to _startAsyncProcessor");
+ return;
+ }
+
+ // Send headers if they haven't been sent already and should be sent, then
+ // asynchronously continue to send the body.
+ if (this._headers && !this._powerSeized)
+ {
+ this._sendHeaders();
+ return;
+ }
+
+ this._headers = null;
+ this._sendBody();
+ },
+
+ /**
+* Signals that all modifications to the response status line and headers are
+* complete and then sends that data over the network to the client. Once
+* this method completes, a different response to the request that resulted
+* in this response cannot be sent -- the only possible action in case of
+* error is to abort the response and close the connection.
+*/
+ _sendHeaders: function()
+ {
+ dumpn("*** _sendHeaders()");
+
+ NS_ASSERT(this._headers);
+ NS_ASSERT(!this._powerSeized);
+
+ // request-line
+ var statusLine = "HTTP/" + this.httpVersion + " " +
+ this.httpCode + " " +
+ this.httpDescription + "\r\n";
+
+ // header post-processing
+
+ var headers = this._headers;
+ headers.setHeader("Connection", "close", false);
+ headers.setHeader("Server", "httpd.js", false);
+ if (!headers.hasHeader("Date"))
+ headers.setHeader("Date", toDateString(Date.now()), false);
+
+ // Any response not being processed asynchronously must have an associated
+ // Content-Length header for reasons of backwards compatibility with the
+ // initial server, which fully buffered every response before sending it.
+ // Beyond that, however, it's good to do this anyway because otherwise it's
+ // impossible to test behaviors that depend on the presence or absence of a
+ // Content-Length header.
+ if (!this._processAsync)
+ {
+ dumpn("*** non-async response, set Content-Length");
+
+ var bodyStream = this._bodyInputStream;
+ var avail = bodyStream ? bodyStream.available() : 0;
+
+ // XXX assumes stream will always report the full amount of data available
+ headers.setHeader("Content-Length", "" + avail, false);
+ }
+
+
+ // construct and send response
+ dumpn("*** header post-processing completed, sending response head...");
+
+ // request-line
+ var preambleData = [statusLine];
+
+ // headers
+ var headEnum = headers.enumerator;
+ while (headEnum.hasMoreElements())
+ {
+ var fieldName = headEnum.getNext()
+ .QueryInterface(Ci.nsISupportsString)
+ .data;
+ var values = headers.getHeaderValues(fieldName);
+ for (var i = 0, sz = values.length; i < sz; i++)
+ preambleData.push(fieldName + ": " + values[i] + "\r\n");
+ }
+
+ // end request-line/headers
+ preambleData.push("\r\n");
+
+ var preamble = preambleData.join("");
+
+ var responseHeadPipe = new Pipe(true, false, 0, PR_UINT32_MAX, null);
+ responseHeadPipe.outputStream.write(preamble, preamble.length);
+
+ var response = this;
+ var copyObserver =
+ {
+ onStartRequest: function(request, cx)
+ {
+ dumpn("*** preamble copying started");
+ },
+
+ onStopRequest: function(request, cx, statusCode)
+ {
+ dumpn("*** preamble copying complete " +
+ "[status=0x" + statusCode.toString(16) + "]");
+
+ if (!components.isSuccessCode(statusCode))
+ {
+ dumpn("!!! header copying problems: non-success statusCode, " +
+ "ending response");
+
+ response.end();
+ }
+ else
+ {
+ response._sendBody();
+ }
+ },
+
+ QueryInterface: function(aIID)
+ {
+ if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ };
+
+ var headerCopier = this._asyncCopier =
+ new WriteThroughCopier(responseHeadPipe.inputStream,
+ this._connection.output,
+ copyObserver, null);
+
+ responseHeadPipe.outputStream.close();
+
+ // Forbid setting any more headers or modifying the request line.
+ this._headers = null;
+ },
+
+ /**
+* Asynchronously writes the body of the response (or the entire response, if
+* seizePower() has been called) to the network.
+*/
+ _sendBody: function()
+ {
+ dumpn("*** _sendBody");
+
+ NS_ASSERT(!this._headers, "still have headers around but sending body?");
+
+ // If no body data was written, we're done
+ if (!this._bodyInputStream)
+ {
+ dumpn("*** empty body, response finished");
+ this.end();
+ return;
+ }
+
+ var response = this;
+ var copyObserver =
+ {
+ onStartRequest: function(request, context)
+ {
+ dumpn("*** onStartRequest");
+ },
+
+ onStopRequest: function(request, cx, statusCode)
+ {
+ dumpn("*** onStopRequest [status=0x" + statusCode.toString(16) + "]");
+
+ if (statusCode === Cr.NS_BINDING_ABORTED)
+ {
+ dumpn("*** terminating copy observer without ending the response");
+ }
+ else
+ {
+ if (!components.isSuccessCode(statusCode))
+ dumpn("*** WARNING: non-success statusCode in onStopRequest");
+
+ response.end();
+ }
+ },
+
+ QueryInterface: function(aIID)
+ {
+ if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ };
+
+ dumpn("*** starting async copier of body data...");
+ this._asyncCopier =
+ new WriteThroughCopier(this._bodyInputStream, this._connection.output,
+ copyObserver, null);
+ },
+
+ /** Ensures that this hasn't been ended. */
+ _ensureAlive: function()
+ {
+ NS_ASSERT(!this._ended, "not handling response lifetime correctly");
+ }
+};
+
+/**
+* Size of the segments in the buffer used in storing response data and writing
+* it to the socket.
+*/
+Response.SEGMENT_SIZE = 8192;
+
+/** Serves double duty in WriteThroughCopier implementation. */
+function notImplemented()
+{
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/** Returns true iff the given exception represents stream closure. */
+function streamClosed(e)
+{
+ return e === Cr.NS_BASE_STREAM_CLOSED ||
+ (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_CLOSED);
+}
+
+/** Returns true iff the given exception represents a blocked stream. */
+function wouldBlock(e)
+{
+ return e === Cr.NS_BASE_STREAM_WOULD_BLOCK ||
+ (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_WOULD_BLOCK);
+}
+
+/**
+* Copies data from source to sink as it becomes available, when that data can
+* be written to sink without blocking.
+*
+* @param source : nsIAsyncInputStream
+* the stream from which data is to be read
+* @param sink : nsIAsyncOutputStream
+* the stream to which data is to be copied
+* @param observer : nsIRequestObserver
+* an observer which will be notified when the copy starts and finishes
+* @param context : nsISupports
+* context passed to observer when notified of start/stop
+* @throws NS_ERROR_NULL_POINTER
+* if source, sink, or observer are null
+*/
+function WriteThroughCopier(source, sink, observer, context)
+{
+ if (!source || !sink || !observer)
+ throw Cr.NS_ERROR_NULL_POINTER;
+
+ /** Stream from which data is being read. */
+ this._source = source;
+
+ /** Stream to which data is being written. */
+ this._sink = sink;
+
+ /** Observer watching this copy. */
+ this._observer = observer;
+
+ /** Context for the observer watching this. */
+ this._context = context;
+
+ /**
+* True iff this is currently being canceled (cancel has been called, the
+* callback may not yet have been made).
+*/
+ this._canceled = false;
+
+ /**
+* False until all data has been read from input and written to output, at
+* which point this copy is completed and cancel() is asynchronously called.
+*/
+ this._completed = false;
+
+ /** Required by nsIRequest, meaningless. */
+ this.loadFlags = 0;
+ /** Required by nsIRequest, meaningless. */
+ this.loadGroup = null;
+ /** Required by nsIRequest, meaningless. */
+ this.name = "response-body-copy";
+
+ /** Status of this request. */
+ this.status = Cr.NS_OK;
+
+ /** Arrays of byte strings waiting to be written to output. */
+ this._pendingData = [];
+
+ // start copying
+ try
+ {
+ observer.onStartRequest(this, context);
+ this._waitToReadData();
+ this._waitForSinkClosure();
+ }
+ catch (e)
+ {
+ dumpn("!!! error starting copy: " + e +
+ ("lineNumber" in e ? ", line " + e.lineNumber : ""));
+ dumpn(e.stack);
+ this.cancel(Cr.NS_ERROR_UNEXPECTED);
+ }
+}
+WriteThroughCopier.prototype =
+{
+ /* nsISupports implementation */
+
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIInputStreamCallback) ||
+ iid.equals(Ci.nsIOutputStreamCallback) ||
+ iid.equals(Ci.nsIRequest) ||
+ iid.equals(Ci.nsISupports))
+ {
+ return this;
+ }
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // NSIINPUTSTREAMCALLBACK
+
+ /**
+* Receives a more-data-in-input notification and writes the corresponding
+* data to the output.
+*
+* @param input : nsIAsyncInputStream
+* the input stream on whose data we have been waiting
+*/
+ onInputStreamReady: function(input)
+ {
+ if (this._source === null)
+ return;
+
+ dumpn("*** onInputStreamReady");
+
+ //
+ // Ordinarily we'll read a non-zero amount of data from input, queue it up
+ // to be written and then wait for further callbacks. The complications in
+ // this method are the cases where we deviate from that behavior when errors
+ // occur or when copying is drawing to a finish.
+ //
+ // The edge cases when reading data are:
+ //
+ // Zero data is read
+ // If zero data was read, we're at the end of available data, so we can
+ // should stop reading and move on to writing out what we have (or, if
+ // we've already done that, onto notifying of completion).
+ // A stream-closed exception is thrown
+ // This is effectively a less kind version of zero data being read; the
+ // only difference is that we notify of completion with that result
+ // rather than with NS_OK.
+ // Some other exception is thrown
+ // This is the least kind result. We don't know what happened, so we
+ // act as though the stream closed except that we notify of completion
+ // with the result NS_ERROR_UNEXPECTED.
+ //
+
+ var bytesWanted = 0, bytesConsumed = -1;
+ try
+ {
+ input = new BinaryInputStream(input);
+
+ bytesWanted = Math.min(input.available(), Response.SEGMENT_SIZE);
+ dumpn("*** input wanted: " + bytesWanted);
+
+ if (bytesWanted > 0)
+ {
+ var data = input.readByteArray(bytesWanted);
+ bytesConsumed = data.length;
+ this._pendingData.push(String.fromCharCode.apply(String, data));
+ }
+
+ dumpn("*** " + bytesConsumed + " bytes read");
+
+ // Handle the zero-data edge case in the same place as all other edge
+ // cases are handled.
+ if (bytesWanted === 0)
+ throw Cr.NS_BASE_STREAM_CLOSED;
+ }
+ catch (e)
+ {
+ if (streamClosed(e))
+ {
+ dumpn("*** input stream closed");
+ e = bytesWanted === 0 ? Cr.NS_OK : Cr.NS_ERROR_UNEXPECTED;
+ }
+ else
+ {
+ dumpn("!!! unexpected error reading from input, canceling: " + e);
+ e = Cr.NS_ERROR_UNEXPECTED;
+ }
+
+ this._doneReadingSource(e);
+ return;
+ }
+
+ var pendingData = this._pendingData;
+
+ NS_ASSERT(bytesConsumed > 0);
+ NS_ASSERT(pendingData.length > 0, "no pending data somehow?");
+ NS_ASSERT(pendingData[pendingData.length - 1].length > 0,
+ "buffered zero bytes of data?");
+
+ NS_ASSERT(this._source !== null);
+
+ // Reading has gone great, and we've gotten data to write now. What if we
+ // don't have a place to write that data, because output went away just
+ // before this read? Drop everything on the floor, including new data, and
+ // cancel at this point.
+ if (this._sink === null)
+ {
+ pendingData.length = 0;
+ this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Okay, we've read the data, and we know we have a place to write it. We
+ // need to queue up the data to be written, but *only* if none is queued
+ // already -- if data's already queued, the code that actually writes the
+ // data will make sure to wait on unconsumed pending data.
+ try
+ {
+ if (pendingData.length === 1)
+ this._waitToWriteData();
+ }
+ catch (e)
+ {
+ dumpn("!!! error waiting to write data just read, swallowing and " +
+ "writing only what we already have: " + e);
+ this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Whee! We successfully read some data, and it's successfully queued up to
+ // be written. All that remains now is to wait for more data to read.
+ try
+ {
+ this._waitToReadData();
+ }
+ catch (e)
+ {
+ dumpn("!!! error waiting to read more data: " + e);
+ this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED);
+ }
+ },
+
+
+ // NSIOUTPUTSTREAMCALLBACK
+
+ /**
+* Callback when data may be written to the output stream without blocking, or
+* when the output stream has been closed.
+*
+* @param output : nsIAsyncOutputStream
+* the output stream on whose writability we've been waiting, also known as
+* this._sink
+*/
+ onOutputStreamReady: function(output)
+ {
+ if (this._sink === null)
+ return;
+
+ dumpn("*** onOutputStreamReady");
+
+ var pendingData = this._pendingData;
+ if (pendingData.length === 0)
+ {
+ // There's no pending data to write. The only way this can happen is if
+ // we're waiting on the output stream's closure, so we can respond to a
+ // copying failure as quickly as possible (rather than waiting for data to
+ // be available to read and then fail to be copied). Therefore, we must
+ // be done now -- don't bother to attempt to write anything and wrap
+ // things up.
+ dumpn("!!! output stream closed prematurely, ending copy");
+
+ this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+
+ NS_ASSERT(pendingData[0].length > 0, "queued up an empty quantum?");
+
+ //
+ // Write out the first pending quantum of data. The possible errors here
+ // are:
+ //
+ // The write might fail because we can't write that much data
+ // Okay, we've written what we can now, so re-queue what's left and
+ // finish writing it out later.
+ // The write failed because the stream was closed
+ // Discard pending data that we can no longer write, stop reading, and
+ // signal that copying finished.
+ // Some other error occurred.
+ // Same as if the stream were closed, but notify with the status
+ // NS_ERROR_UNEXPECTED so the observer knows something was wonky.
+ //
+
+ try
+ {
+ var quantum = pendingData[0];
+
+ // XXX |quantum| isn't guaranteed to be ASCII, so we're relying on
+ // undefined behavior! We're only using this because writeByteArray
+ // is unusably broken for asynchronous output streams; see bug 532834
+ // for details.
+ var bytesWritten = output.write(quantum, quantum.length);
+ if (bytesWritten === quantum.length)
+ pendingData.shift();
+ else
+ pendingData[0] = quantum.substring(bytesWritten);
+
+ dumpn("*** wrote " + bytesWritten + " bytes of data");
+ }
+ catch (e)
+ {
+ if (wouldBlock(e))
+ {
+ NS_ASSERT(pendingData.length > 0,
+ "stream-blocking exception with no data to write?");
+ NS_ASSERT(pendingData[0].length > 0,
+ "stream-blocking exception with empty quantum?");
+ this._waitToWriteData();
+ return;
+ }
+
+ if (streamClosed(e))
+ dumpn("!!! output stream prematurely closed, signaling error...");
+ else
+ dumpn("!!! unknown error: " + e + ", quantum=" + quantum);
+
+ this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // The day is ours! Quantum written, now let's see if we have more data
+ // still to write.
+ try
+ {
+ if (pendingData.length > 0)
+ {
+ this._waitToWriteData();
+ return;
+ }
+ }
+ catch (e)
+ {
+ dumpn("!!! unexpected error waiting to write pending data: " + e);
+ this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Okay, we have no more pending data to write -- but might we get more in
+ // the future?
+ if (this._source !== null)
+ {
+ /*
+* If we might, then wait for the output stream to be closed. (We wait
+* only for closure because we have no data to write -- and if we waited
+* for a specific amount of data, we would get repeatedly notified for no
+* reason if over time the output stream permitted more and more data to
+* be written to it without blocking.)
+*/
+ this._waitForSinkClosure();
+ }
+ else
+ {
+ /*
+* On the other hand, if we can't have more data because the input
+* stream's gone away, then it's time to notify of copy completion.
+* Victory!
+*/
+ this._sink = null;
+ this._cancelOrDispatchCancelCallback(Cr.NS_OK);
+ }
+ },
+
+
+ // NSIREQUEST
+
+ /** Returns true if the cancel observer hasn't been notified yet. */
+ isPending: function()
+ {
+ return !this._completed;
+ },
+
+ /** Not implemented, don't use! */
+ suspend: notImplemented,
+ /** Not implemented, don't use! */
+ resume: notImplemented,
+
+ /**
+* Cancels data reading from input, asynchronously writes out any pending
+* data, and causes the observer to be notified with the given error code when
+* all writing has finished.
+*
+* @param status : nsresult
+* the status to pass to the observer when data copying has been canceled
+*/
+ cancel: function(status)
+ {
+ dumpn("*** cancel(" + status.toString(16) + ")");
+
+ if (this._canceled)
+ {
+ dumpn("*** suppressing a late cancel");
+ return;
+ }
+
+ this._canceled = true;
+ this.status = status;
+
+ // We could be in the middle of absolutely anything at this point. Both
+ // input and output might still be around, we might have pending data to
+ // write, and in general we know nothing about the state of the world. We
+ // therefore must assume everything's in progress and take everything to its
+ // final steady state (or so far as it can go before we need to finish
+ // writing out remaining data).
+
+ this._doneReadingSource(status);
+ },
+
+
+ // PRIVATE IMPLEMENTATION
+
+ /**
+* Stop reading input if we haven't already done so, passing e as the status
+* when closing the stream, and kick off a copy-completion notice if no more
+* data remains to be written.
+*
+* @param e : nsresult
+* the status to be used when closing the input stream
+*/
+ _doneReadingSource: function(e)
+ {
+ dumpn("*** _doneReadingSource(0x" + e.toString(16) + ")");
+
+ this._finishSource(e);
+ if (this._pendingData.length === 0)
+ this._sink = null;
+ else
+ NS_ASSERT(this._sink !== null, "null output?");
+
+ // If we've written out all data read up to this point, then it's time to
+ // signal completion.
+ if (this._sink === null)
+ {
+ NS_ASSERT(this._pendingData.length === 0, "pending data still?");
+ this._cancelOrDispatchCancelCallback(e);
+ }
+ },
+
+ /**
+* Stop writing output if we haven't already done so, discard any data that
+* remained to be sent, close off input if it wasn't already closed, and kick
+* off a copy-completion notice.
+*
+* @param e : nsresult
+* the status to be used when closing input if it wasn't already closed
+*/
+ _doneWritingToSink: function(e)
+ {
+ dumpn("*** _doneWritingToSink(0x" + e.toString(16) + ")");
+
+ this._pendingData.length = 0;
+ this._sink = null;
+ this._doneReadingSource(e);
+ },
+
+ /**
+* Completes processing of this copy: either by canceling the copy if it
+* hasn't already been canceled using the provided status, or by dispatching
+* the cancel callback event (with the originally provided status, of course)
+* if it already has been canceled.
+*
+* @param status : nsresult
+* the status code to use to cancel this, if this hasn't already been
+* canceled
+*/
+ _cancelOrDispatchCancelCallback: function(status)
+ {
+ dumpn("*** _cancelOrDispatchCancelCallback(" + status + ")");
+
+ NS_ASSERT(this._source === null, "should have finished input");
+ NS_ASSERT(this._sink === null, "should have finished output");
+ NS_ASSERT(this._pendingData.length === 0, "should have no pending data");
+
+ if (!this._canceled)
+ {
+ this.cancel(status);
+ return;
+ }
+
+ var self = this;
+ var event =
+ {
+ run: function()
+ {
+ dumpn("*** onStopRequest async callback");
+
+ self._completed = true;
+ try
+ {
+ self._observer.onStopRequest(self, self._context, self.status);
+ }
+ catch (e)
+ {
+ NS_ASSERT(false,
+ "how are we throwing an exception here? we control " +
+ "all the callers! " + e);
+ }
+ }
+ };
+
+ gThreadManager.currentThread.dispatch(event, Ci.nsIThread.DISPATCH_NORMAL);
+ },
+
+ /**
+* Kicks off another wait for more data to be available from the input stream.
+*/
+ _waitToReadData: function()
+ {
+ dumpn("*** _waitToReadData");
+ this._source.asyncWait(this, 0, Response.SEGMENT_SIZE,
+ gThreadManager.mainThread);
+ },
+
+ /**
+* Kicks off another wait until data can be written to the output stream.
+*/
+ _waitToWriteData: function()
+ {
+ dumpn("*** _waitToWriteData");
+
+ var pendingData = this._pendingData;
+ NS_ASSERT(pendingData.length > 0, "no pending data to write?");
+ NS_ASSERT(pendingData[0].length > 0, "buffered an empty write?");
+
+ this._sink.asyncWait(this, 0, pendingData[0].length,
+ gThreadManager.mainThread);
+ },
+
+ /**
+* Kicks off a wait for the sink to which data is being copied to be closed.
+* We wait for stream closure when we don't have any data to be copied, rather
+* than waiting to write a specific amount of data. We can't wait to write
+* data because the sink might be infinitely writable, and if no data appears
+* in the source for a long time we might have to spin quite a bit waiting to
+* write, waiting to write again, &c. Waiting on stream closure instead means
+* we'll get just one notification if the sink dies. Note that when data
+* starts arriving from the sink we'll resume waiting for data to be written,
+* dropping this closure-only callback entirely.
+*/
+ _waitForSinkClosure: function()
+ {
+ dumpn("*** _waitForSinkClosure");
+
+ this._sink.asyncWait(this, Ci.nsIAsyncOutputStream.WAIT_CLOSURE_ONLY, 0,
+ gThreadManager.mainThread);
+ },
+
+ /**
+* Closes input with the given status, if it hasn't already been closed;
+* otherwise a no-op.
+*
+* @param status : nsresult
+* status code use to close the source stream if necessary
+*/
+ _finishSource: function(status)
+ {
+ dumpn("*** _finishSource(" + status.toString(16) + ")");
+
+ if (this._source !== null)
+ {
+ this._source.closeWithStatus(status);
+ this._source = null;
+ }
+ }
+};
+
+
+/**
+* A container for utility functions used with HTTP headers.
+*/
+const headerUtils =
+{
+ /**
+* Normalizes fieldName (by converting it to lowercase) and ensures it is a
+* valid header field name (although not necessarily one specified in RFC
+* 2616).
+*
+* @throws NS_ERROR_INVALID_ARG
+* if fieldName does not match the field-name production in RFC 2616
+* @returns string
+* fieldName converted to lowercase if it is a valid header, for characters
+* where case conversion is possible
+*/
+ normalizeFieldName: function(fieldName)
+ {
+ if (fieldName == "")
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ for (var i = 0, sz = fieldName.length; i < sz; i++)
+ {
+ if (!IS_TOKEN_ARRAY[fieldName.charCodeAt(i)])
+ {
+ dumpn(fieldName + " is not a valid header field name!");
+ throw Cr.NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ return fieldName.toLowerCase();
+ },
+
+ /**
+* Ensures that fieldValue is a valid header field value (although not
+* necessarily as specified in RFC 2616 if the corresponding field name is
+* part of the HTTP protocol), normalizes the value if it is, and
+* returns the normalized value.
+*
+* @param fieldValue : string
+* a value to be normalized as an HTTP header field value
+* @throws NS_ERROR_INVALID_ARG
+* if fieldValue does not match the field-value production in RFC 2616
+* @returns string
+* fieldValue as a normalized HTTP header field value
+*/
+ normalizeFieldValue: function(fieldValue)
+ {
+ // field-value = *( field-content | LWS )
+ // field-content = <the OCTETs making up the field-value
+ // and consisting of either *TEXT or combinations
+ // of token, separators, and quoted-string>
+ // TEXT = <any OCTET except CTLs,
+ // but including LWS>
+ // LWS = [CRLF] 1*( SP | HT )
+ //
+ // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
+ // qdtext = <any TEXT except <">>
+ // quoted-pair = "\" CHAR
+ // CHAR = <any US-ASCII character (octets 0 - 127)>
+
+ // Any LWS that occurs between field-content MAY be replaced with a single
+ // SP before interpreting the field value or forwarding the message
+ // downstream (section 4.2); we replace 1*LWS with a single SP
+ var val = fieldValue.replace(/(?:(?:\r\n)?[ \t]+)+/g, " ");
+
+ // remove leading/trailing LWS (which has been converted to SP)
+ val = val.replace(/^ +/, "").replace(/ +$/, "");
+
+ // that should have taken care of all CTLs, so val should contain no CTLs
+ for (var i = 0, len = val.length; i < len; i++)
+ if (isCTL(val.charCodeAt(i)))
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ // XXX disallows quoted-pair where CHAR is a CTL -- will not invalidly
+ // normalize, however, so this can be construed as a tightening of the
+ // spec and not entirely as a bug
+ return val;
+ }
+};
+
+
+
+/**
+* Converts the given string into a string which is safe for use in an HTML
+* context.
+*
+* @param str : string
+* the string to make HTML-safe
+* @returns string
+* an HTML-safe version of str
+*/
+function htmlEscape(str)
+{
+ // this is naive, but it'll work
+ var s = "";
+ for (var i = 0; i < str.length; i++)
+ s += "&#" + str.charCodeAt(i) + ";";
+ return s;
+}
+
+
+/**
+* Constructs an object representing an HTTP version (see section 3.1).
+*
+* @param versionString
+* a string of the form "#.#", where # is an non-negative decimal integer with
+* or without leading zeros
+* @throws
+* if versionString does not specify a valid HTTP version number
+*/
+function nsHttpVersion(versionString)
+{
+ var matches = /^(\d+)\.(\d+)$/.exec(versionString);
+ if (!matches)
+ throw "Not a valid HTTP version!";
+
+ /** The major version number of this, as a number. */
+ this.major = parseInt(matches[1], 10);
+
+ /** The minor version number of this, as a number. */
+ this.minor = parseInt(matches[2], 10);
+
+ if (isNaN(this.major) || isNaN(this.minor) ||
+ this.major < 0 || this.minor < 0)
+ throw "Not a valid HTTP version!";
+}
+nsHttpVersion.prototype =
+{
+ /**
+* Returns the standard string representation of the HTTP version represented
+* by this (e.g., "1.1").
+*/
+ toString: function ()
+ {
+ return this.major + "." + this.minor;
+ },
+
+ /**
+* Returns true if this represents the same HTTP version as otherVersion,
+* false otherwise.
+*
+* @param otherVersion : nsHttpVersion
+* the version to compare against this
+*/
+ equals: function (otherVersion)
+ {
+ return this.major == otherVersion.major &&
+ this.minor == otherVersion.minor;
+ },
+
+ /** True if this >= otherVersion, false otherwise. */
+ atLeast: function(otherVersion)
+ {
+ return this.major > otherVersion.major ||
+ (this.major == otherVersion.major &&
+ this.minor >= otherVersion.minor);
+ }
+};
+
+nsHttpVersion.HTTP_1_0 = new nsHttpVersion("1.0");
+nsHttpVersion.HTTP_1_1 = new nsHttpVersion("1.1");
+
+
+/**
+* An object which stores HTTP headers for a request or response.
+*
+* Note that since headers are case-insensitive, this object converts headers to
+* lowercase before storing them. This allows the getHeader and hasHeader
+* methods to work correctly for any case of a header, but it means that the
+* values returned by .enumerator may not be equal case-sensitively to the
+* values passed to setHeader when adding headers to this.
+*/
+function nsHttpHeaders()
+{
+ /**
+* A hash of headers, with header field names as the keys and header field
+* values as the values. Header field names are case-insensitive, but upon
+* insertion here they are converted to lowercase. Header field values are
+* normalized upon insertion to contain no leading or trailing whitespace.
+*
+* Note also that per RFC 2616, section 4.2, two headers with the same name in
+* a message may be treated as one header with the same field name and a field
+* value consisting of the separate field values joined together with a "," in
+* their original order. This hash stores multiple headers with the same name
+* in this manner.
+*/
+ this._headers = {};
+}
+nsHttpHeaders.prototype =
+{
+ /**
+* Sets the header represented by name and value in this.
+*
+* @param name : string
+* the header name
+* @param value : string
+* the header value
+* @throws NS_ERROR_INVALID_ARG
+* if name or value is not a valid header component
+*/
+ setHeader: function(fieldName, fieldValue, merge)
+ {
+ var name = headerUtils.normalizeFieldName(fieldName);
+ var value = headerUtils.normalizeFieldValue(fieldValue);
+
+ // The following three headers are stored as arrays because their real-world
+ // syntax prevents joining individual headers into a single header using
+ // ",". See also <http://hg.mozilla.org/mozilla-central/diff/9b2a99adc05e/netwerk/protocol/http/src/nsHttpHeaderArray.cpp#l77>
+ if (merge && name in this._headers)
+ {
+ if (name === "www-authenticate" ||
+ name === "proxy-authenticate" ||
+ name === "set-cookie")
+ {
+ this._headers[name].push(value);
+ }
+ else
+ {
+ this._headers[name][0] += "," + value;
+ NS_ASSERT(this._headers[name].length === 1,
+ "how'd a non-special header have multiple values?")
+ }
+ }
+ else
+ {
+ this._headers[name] = [value];
+ }
+ },
+
+ /**
+* Returns the value for the header specified by this.
+*
+* @throws NS_ERROR_INVALID_ARG
+* if fieldName does not constitute a valid header field name
+* @throws NS_ERROR_NOT_AVAILABLE
+* if the given header does not exist in this
+* @returns string
+* the field value for the given header, possibly with non-semantic changes
+* (i.e., leading/trailing whitespace stripped, whitespace runs replaced
+* with spaces, etc.) at the option of the implementation; multiple
+* instances of the header will be combined with a comma, except for
+* the three headers noted in the description of getHeaderValues
+*/
+ getHeader: function(fieldName)
+ {
+ return this.getHeaderValues(fieldName).join("\n");
+ },
+
+ /**
+* Returns the value for the header specified by fieldName as an array.
+*
+* @throws NS_ERROR_INVALID_ARG
+* if fieldName does not constitute a valid header field name
+* @throws NS_ERROR_NOT_AVAILABLE
+* if the given header does not exist in this
+* @returns [string]
+* an array of all the header values in this for the given
+* header name. Header values will generally be collapsed
+* into a single header by joining all header values together
+* with commas, but certain headers (Proxy-Authenticate,
+* WWW-Authenticate, and Set-Cookie) violate the HTTP spec
+* and cannot be collapsed in this manner. For these headers
+* only, the returned array may contain multiple elements if
+* that header has been added more than once.
+*/
+ getHeaderValues: function(fieldName)
+ {
+ var name = headerUtils.normalizeFieldName(fieldName);
+
+ if (name in this._headers)
+ return this._headers[name];
+ else
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ },
+
+ /**
+* Returns true if a header with the given field name exists in this, false
+* otherwise.
+*
+* @param fieldName : string
+* the field name whose existence is to be determined in this
+* @throws NS_ERROR_INVALID_ARG
+* if fieldName does not constitute a valid header field name
+* @returns boolean
+* true if the header's present, false otherwise
+*/
+ hasHeader: function(fieldName)
+ {
+ var name = headerUtils.normalizeFieldName(fieldName);
+ return (name in this._headers);
+ },
+
+ /**
+* Returns a new enumerator over the field names of the headers in this, as
+* nsISupportsStrings. The names returned will be in lowercase, regardless of
+* how they were input using setHeader (header names are case-insensitive per
+* RFC 2616).
+*/
+ get enumerator()
+ {
+ var headers = [];
+ for (var i in this._headers)
+ {
+ var supports = new SupportsString();
+ supports.data = i;
+ headers.push(supports);
+ }
+
+ return new nsSimpleEnumerator(headers);
+ }
+};
+
+
+/**
+* Constructs an nsISimpleEnumerator for the given array of items.
+*
+* @param items : Array
+* the items, which must all implement nsISupports
+*/
+function nsSimpleEnumerator(items)
+{
+ this._items = items;
+ this._nextIndex = 0;
+}
+nsSimpleEnumerator.prototype =
+{
+ hasMoreElements: function()
+ {
+ return this._nextIndex < this._items.length;
+ },
+ getNext: function()
+ {
+ if (!this.hasMoreElements())
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+ return this._items[this._nextIndex++];
+ },
+ QueryInterface: function(aIID)
+ {
+ if (Ci.nsISimpleEnumerator.equals(aIID) ||
+ Ci.nsISupports.equals(aIID))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+
+/**
+* A representation of the data in an HTTP request.
+*
+* @param port : uint
+* the port on which the server receiving this request runs
+*/
+function Request(port)
+{
+ /** Method of this request, e.g. GET or POST. */
+ this._method = "";
+
+ /** Path of the requested resource; empty paths are converted to '/'. */
+ this._path = "";
+
+ /** Query string, if any, associated with this request (not including '?'). */
+ this._queryString = "";
+
+ /** Scheme of requested resource, usually http, always lowercase. */
+ this._scheme = "http";
+
+ /** Hostname on which the requested resource resides. */
+ this._host = undefined;
+
+ /** Port number over which the request was received. */
+ this._port = port;
+
+ var bodyPipe = new Pipe(false, false, 0, PR_UINT32_MAX, null);
+
+ /** Stream from which data in this request's body may be read. */
+ this._bodyInputStream = bodyPipe.inputStream;
+
+ /** Stream to which data in this request's body is written. */
+ this._bodyOutputStream = bodyPipe.outputStream;
+
+ /**
+* The headers in this request.
+*/
+ this._headers = new nsHttpHeaders();
+
+ /**
+* For the addition of ad-hoc properties and new functionality without having
+* to change nsIHttpRequest every time; currently lazily created, as its only
+* use is in directory listings.
+*/
+ this._bag = null;
+}
+Request.prototype =
+{
+ // SERVER METADATA
+
+ //
+ // see nsIHttpRequest.scheme
+ //
+ get scheme()
+ {
+ return this._scheme;
+ },
+
+ //
+ // see nsIHttpRequest.host
+ //
+ get host()
+ {
+ return this._host;
+ },
+
+ //
+ // see nsIHttpRequest.port
+ //
+ get port()
+ {
+ return this._port;
+ },
+
+ // REQUEST LINE
+
+ //
+ // see nsIHttpRequest.method
+ //
+ get method()
+ {
+ return this._method;
+ },
+
+ //
+ // see nsIHttpRequest.httpVersion
+ //
+ get httpVersion()
+ {
+ return this._httpVersion.toString();
+ },
+
+ //
+ // see nsIHttpRequest.path
+ //
+ get path()
+ {
+ return this._path;
+ },
+
+ //
+ // see nsIHttpRequest.queryString
+ //
+ get queryString()
+ {
+ return this._queryString;
+ },
+
+ // HEADERS
+
+ //
+ // see nsIHttpRequest.getHeader
+ //
+ getHeader: function(name)
+ {
+ return this._headers.getHeader(name);
+ },
+
+ //
+ // see nsIHttpRequest.hasHeader
+ //
+ hasHeader: function(name)
+ {
+ return this._headers.hasHeader(name);
+ },
+
+ //
+ // see nsIHttpRequest.headers
+ //
+ get headers()
+ {
+ return this._headers.enumerator;
+ },
+
+ //
+ // see nsIPropertyBag.enumerator
+ //
+ get enumerator()
+ {
+ this._ensurePropertyBag();
+ return this._bag.enumerator;
+ },
+
+ //
+ // see nsIHttpRequest.headers
+ //
+ get bodyInputStream()
+ {
+ return this._bodyInputStream;
+ },
+
+ //
+ // see nsIPropertyBag.getProperty
+ //
+ getProperty: function(name)
+ {
+ this._ensurePropertyBag();
+ return this._bag.getProperty(name);
+ },
+
+
+ // NSISUPPORTS
+
+ //
+ // see nsISupports.QueryInterface
+ //
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIHttpRequest) || iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // PRIVATE IMPLEMENTATION
+
+ /** Ensures a property bag has been created for ad-hoc behaviors. */
+ _ensurePropertyBag: function()
+ {
+ if (!this._bag)
+ this._bag = new WritablePropertyBag();
+ }
+};
+
+
+// XPCOM trappings
+if ("XPCOMUtils" in this && // Firefox 3.6 doesn't load XPCOMUtils in this scope for some reason...
+ "generateNSGetFactory" in XPCOMUtils) {
+ var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsHttpServer]);
+}
+
+
+
+/**
+* Creates a new HTTP server listening for loopback traffic on the given port,
+* starts it, and runs the server until the server processes a shutdown request,
+* spinning an event loop so that events posted by the server's socket are
+* processed.
+*
+* This method is primarily intended for use in running this script from within
+* xpcshell and running a functional HTTP server without having to deal with
+* non-essential details.
+*
+* Note that running multiple servers using variants of this method probably
+* doesn't work, simply due to how the internal event loop is spun and stopped.
+*
+* @note
+* This method only works with Mozilla 1.9 (i.e., Firefox 3 or trunk code);
+* you should use this server as a component in Mozilla 1.8.
+* @param port
+* the port on which the server will run, or -1 if there exists no preference
+* for a specific port; note that attempting to use some values for this
+* parameter (particularly those below 1024) may cause this method to throw or
+* may result in the server being prematurely shut down
+* @param basePath
+* a local directory from which requests will be served (i.e., if this is
+* "/home/jwalden/" then a request to /index.html will load
+* /home/jwalden/index.html); if this is omitted, only the default URLs in
+* this server implementation will be functional
+*/
+function server(port, basePath)
+{
+ if (basePath)
+ {
+ var lp = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile);
+ lp.initWithPath(basePath);
+ }
+
+ // if you're running this, you probably want to see debugging info
+ DEBUG = true;
+
+ var srv = new nsHttpServer();
+ if (lp)
+ srv.registerDirectory("/", lp);
+ srv.registerContentType("sjs", SJS_TYPE);
+ srv.identity.setPrimary("http", "localhost", port);
+ srv.start(port);
+
+ var thread = gThreadManager.currentThread;
+ while (!srv.isStopped())
+ thread.processNextEvent(true);
+
+ // get rid of any pending requests
+ while (thread.hasPendingEvents())
+ thread.processNextEvent(true);
+
+ DEBUG = false;
+}
+
+function startServerAsync(port, basePath)
+{
+ if (basePath)
+ {
+ var lp = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile);
+ lp.initWithPath(basePath);
+ }
+
+ var srv = new nsHttpServer();
+ if (lp)
+ srv.registerDirectory("/", lp);
+ srv.registerContentType("sjs", "sjs");
+ srv.identity.setPrimary("http", "localhost", port);
+ srv.start(port);
+ return srv;
+}
+
+exports.nsHttpServer = nsHttpServer;
+exports.ScriptableInputStream = ScriptableInputStream;
+exports.server = server;
+exports.startServerAsync = startServerAsync;
diff --git a/tools/addon-sdk-1.12/lib/sdk/test/loader.js b/tools/addon-sdk-1.12/lib/sdk/test/loader.js
new file mode 100644
index 0000000..282cf5e
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/test/loader.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";
+
+const { Loader, resolveURI, Require,
+ unload, override, descriptor } = require('../loader/cuddlefish');
+
+exports.Loader = function(module, globals, packaging) {
+ let options = packaging || require("@loader/options");
+ options = override(options, {
+ globals: override(require('../system/globals'), globals || {})
+ });
+
+ let loader = Loader(options);
+ return Object.create(loader, descriptor({
+ require: Require(loader, module),
+ sandbox: function(id) {
+ let requirement = loader.resolve(id, module.id);
+ let uri = resolveURI(requirement, loader.mapping);
+ return loader.sandboxes[uri];
+ },
+ unload: function(reason) {
+ unload(loader, reason);
+ }
+ }));
+};
diff --git a/tools/addon-sdk-1.12/lib/sdk/test/runner.js b/tools/addon-sdk-1.12/lib/sdk/test/runner.js
new file mode 100644
index 0000000..4db9aca
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/test/runner.js
@@ -0,0 +1,150 @@
+/* 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 obsvc = require("../deprecated/observer-service");
+var { exit, stdout } = require("../system");
+var cfxArgs = require("@test/options");
+var { Cc, Ci} = require("chrome");
+
+function runTests(findAndRunTests) {
+ var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]
+ .getService(Ci.nsIWindowWatcher);
+
+ let ns = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
+ let msg = 'Running tests...';
+ let markup = '<?xml version="1.0"?><window xmlns="' + ns +
+ '" windowtype="test:runner"><label>' + msg + '</label></window>';
+ let url = "data:application/vnd.mozilla.xul+xml;charset=utf-8," + escape(markup);
+
+
+ var window = ww.openWindow(null, url, "harness", "centerscreen", null);
+
+ var harness = require("./harness");
+
+ function onDone(tests) {
+ stdout.write("\n");
+ var total = tests.passed + tests.failed;
+ stdout.write(tests.passed + " of " + total + " tests passed.\n");
+
+ window.close();
+ if (tests.failed == 0) {
+ if (tests.passed === 0)
+ stdout.write("No tests were run\n");
+ exit(0);
+ } else {
+ printFailedTests(tests, cfxArgs.verbose, stdout.write);
+ exit(1);
+ }
+ };
+
+ // We have to wait for this window to be fully loaded *and* focused
+ // in order to avoid it to mess with our various window/focus tests.
+ // We are first waiting for our window to be fully loaded before ensuring
+ // that it will take the focus, and then we wait for it to be focused.
+ window.addEventListener("load", function onload() {
+ window.removeEventListener("load", onload, true);
+
+ window.addEventListener("focus", function onfocus() {
+ window.removeEventListener("focus", onfocus, true);
+ // Finally, we have to run test on next cycle, otherwise XPCOM components
+ // are not correctly updated.
+ // For ex: nsIFocusManager.getFocusedElementForWindow may throw
+ // NS_ERROR_ILLEGAL_VALUE exception.
+ require("../timers").setTimeout(function () {
+ harness.runTests({
+ findAndRunTests: findAndRunTests,
+ iterations: cfxArgs.iterations || 1,
+ filter: cfxArgs.filter,
+ profileMemory: cfxArgs.profileMemory,
+ stopOnError: cfxArgs.stopOnError,
+ verbose: cfxArgs.verbose,
+ print: stdout.write,
+ onDone: onDone
+ });
+ }, 0);
+ }, true);
+ window.focus();
+ }, true);
+}
+
+function printFailedTests(tests, verbose, print) {
+ if (!verbose)
+ return;
+
+ let iterationNumber = 0;
+ let singleIteration = tests.testRuns.length == 1;
+ let padding = singleIteration ? "" : " ";
+
+ print("\nThe following tests failed:\n");
+
+ for each (let testRun in tests.testRuns) {
+ iterationNumber++;
+
+ if (!singleIteration)
+ print(" Iteration " + iterationNumber + ":\n");
+
+ for each (let test in testRun) {
+ if (test.failed > 0) {
+ print(padding + " " + test.name + ": " + test.errors +"\n");
+ }
+ }
+ print("\n");
+ }
+}
+
+function main() {
+ var testsStarted = false;
+
+ if (!testsStarted) {
+ testsStarted = true;
+ runTests(function findAndRunTests(loader, nextIteration) {
+ loader.require("../deprecated/unit-test").findAndRunTests({
+ testOutOfProcess: false,
+ testInProcess: true,
+ stopOnError: cfxArgs.stopOnError,
+ filter: cfxArgs.filter,
+ onDone: nextIteration
+ });
+ });
+ }
+};
+
+if (require.main === module)
+ main();
+
+exports.runTestsFromModule = function runTestsFromModule(module) {
+ let id = module.id;
+ // Make a copy of exports as it may already be frozen by module loader
+ let exports = {};
+ Object.keys(module.exports).forEach(function(key) {
+ exports[key] = module.exports[key];
+ });
+
+ runTests(function findAndRunTests(loader, nextIteration) {
+ // Consider that all these tests are CommonJS ones
+ loader.require('../test').run(exports);
+
+ // Reproduce what is done in unit-test-finder.findTests()
+ let tests = [];
+ for each (let name in Object.keys(exports).sort()) {
+ tests.push({
+ setup: exports.setup,
+ teardown: exports.teardown,
+ testFunction: exports[name],
+ name: id + "." + name
+ });
+ }
+
+ // Reproduce what is done by unit-test.findAndRunTests()
+ var { TestRunner } = loader.require("../deprecated/unit-test");
+ var runner = new TestRunner();
+ runner.startMany({
+ tests: tests,
+ stopOnError: cfxArgs.stopOnError,
+ onDone: nextIteration
+ });
+ });
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/test/tmp-file.js b/tools/addon-sdk-1.12/lib/sdk/test/tmp-file.js
new file mode 100644
index 0000000..634d2f1
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/test/tmp-file.js
@@ -0,0 +1,73 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Cc, Ci } = require("chrome");
+
+const system = require("sdk/system");
+const file = require("sdk/io/file");
+const unload = require("sdk/system/unload");
+
+// Retrieve the path to the OS temporary directory:
+const tmpDir = require("sdk/system").pathFor("TmpD");
+
+// List of all tmp file created
+let files = [];
+
+// Remove all tmp files on addon disabling
+unload.when(function () {
+ files.forEach(function (path){
+ // Catch exception in order to avoid leaking following files
+ try {
+ if (file.exists(path))
+ file.remove(path);
+ }
+ catch(e) {
+ console.exception(e);
+ }
+ });
+});
+
+// Utility function that synchronously reads local resource from the given
+// `uri` and returns content string. Read in binary mode.
+function readBinaryURI(uri) {
+ let ioservice = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ let channel = ioservice.newChannel(uri, "UTF-8", null);
+ let stream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ stream.setInputStream(channel.open());
+
+ let data = "";
+ while (true) {
+ let available = stream.available();
+ if (available <= 0)
+ break;
+ data += stream.readBytes(available);
+ }
+ stream.close();
+
+ return data;
+}
+
+// Create a temporary file from a given string and returns its path
+exports.createFromString = function createFromString(data, tmpName) {
+ let filename = (tmpName ? tmpName : "tmp-file") + "-" + (new Date().getTime());
+ let path = file.join(tmpDir, filename);
+
+ let tmpFile = file.open(path, "wb");
+ tmpFile.write(data);
+ tmpFile.close();
+
+ // Register tmp file path
+ files.push(path);
+
+ return path;
+}
+
+// Create a temporary file from a given URL and returns its path
+exports.createFromURL = function createFromURL(url, tmpName) {
+ let data = readBinaryURI(url);
+ return exports.createFromString(data, tmpName);
+}
+
diff --git a/tools/addon-sdk-1.12/lib/sdk/timers.js b/tools/addon-sdk-1.12/lib/sdk/timers.js
new file mode 100644
index 0000000..bc72717
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/timers.js
@@ -0,0 +1,53 @@
+/* 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 { CC, Ci } = require('chrome');
+const { when: unload } = require('./system/unload');
+
+const { TYPE_ONE_SHOT, TYPE_REPEATING_SLACK } = Ci.nsITimer;
+const Timer = CC('@mozilla.org/timer;1', 'nsITimer');
+const timers = Object.create(null);
+
+// Last timer id.
+let lastID = 0;
+
+// Sets typer either by timeout or by interval
+// depending on a given type.
+function setTimer(type, callback, delay) {
+ let id = ++ lastID;
+ let timer = timers[id] = Timer();
+ let args = Array.slice(arguments, 3);
+ timer.initWithCallback({
+ notify: function notify() {
+ try {
+ if (type === TYPE_ONE_SHOT)
+ delete timers[id];
+ callback.apply(null, args);
+ }
+ catch(error) {
+ console.exception(error);
+ }
+ }
+ }, delay || 0, type);
+ return id;
+}
+
+function unsetTimer(id) {
+ let timer = timers[id];
+ delete timers[id];
+ if (timer)
+ timer.cancel();
+}
+
+exports.setTimeout = setTimer.bind(null, TYPE_ONE_SHOT);
+exports.setInterval = setTimer.bind(null, TYPE_REPEATING_SLACK);
+exports.clearTimeout = unsetTimer.bind(null);
+exports.clearInterval = unsetTimer.bind(null);
+
+unload(function() { Object.keys(timers).forEach(unsetTimer) });
diff --git a/tools/addon-sdk-1.12/lib/sdk/url.js b/tools/addon-sdk-1.12/lib/sdk/url.js
new file mode 100644
index 0000000..3581398
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/url.js
@@ -0,0 +1,236 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Cc, Ci, Cr } = require("chrome");
+
+const { Class } = require("./core/heritage");
+const base64 = require("./base64");
+
+var ios = Cc['@mozilla.org/network/io-service;1']
+ .getService(Ci.nsIIOService);
+
+var resProt = ios.getProtocolHandler("resource")
+ .QueryInterface(Ci.nsIResProtocolHandler);
+
+function newURI(uriStr, base) {
+ try {
+ let baseURI = base ? ios.newURI(base, null, null) : null;
+ return ios.newURI(uriStr, null, baseURI);
+ }
+ catch (e if e.result == Cr.NS_ERROR_MALFORMED_URI) {
+ throw new Error("malformed URI: " + uriStr);
+ }
+ catch (e if (e.result == Cr.NS_ERROR_FAILURE ||
+ e.result == Cr.NS_ERROR_ILLEGAL_VALUE)) {
+ throw new Error("invalid URI: " + uriStr);
+ }
+}
+
+function resolveResourceURI(uri) {
+ var resolved;
+ try {
+ resolved = resProt.resolveURI(uri);
+ } catch (e if e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
+ throw new Error("resource does not exist: " + uri.spec);
+ };
+ return resolved;
+}
+
+let fromFilename = exports.fromFilename = function fromFilename(path) {
+ var file = Cc['@mozilla.org/file/local;1']
+ .createInstance(Ci.nsILocalFile);
+ file.initWithPath(path);
+ return ios.newFileURI(file).spec;
+};
+
+let toFilename = exports.toFilename = function toFilename(url) {
+ var uri = newURI(url);
+ if (uri.scheme == "resource")
+ uri = newURI(resolveResourceURI(uri));
+ if (uri.scheme == "chrome") {
+ var channel = ios.newChannelFromURI(uri);
+ try {
+ channel = channel.QueryInterface(Ci.nsIFileChannel);
+ return channel.file.path;
+ } catch (e if e.result == Cr.NS_NOINTERFACE) {
+ throw new Error("chrome url isn't on filesystem: " + url);
+ }
+ }
+ if (uri.scheme == "file") {
+ var file = uri.QueryInterface(Ci.nsIFileURL).file;
+ return file.path;
+ }
+ throw new Error("cannot map to filename: " + url);
+};
+
+function URL(url, base) {
+ if (!(this instanceof URL)) {
+ return new URL(url, base);
+ }
+
+ var uri = newURI(url, base);
+
+ var userPass = null;
+ try {
+ userPass = uri.userPass ? uri.userPass : null;
+ } catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
+
+ var host = null;
+ try {
+ host = uri.host;
+ } catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
+
+ var port = null;
+ try {
+ port = uri.port == -1 ? null : uri.port;
+ } catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
+
+ this.__defineGetter__("scheme", function() uri.scheme);
+ this.__defineGetter__("userPass", function() userPass);
+ this.__defineGetter__("host", function() host);
+ this.__defineGetter__("port", function() port);
+ this.__defineGetter__("path", function() uri.path);
+
+ Object.defineProperties(this, {
+ toString: {
+ value: function URL_toString() new String(uri.spec).toString(),
+ enumerable: false
+ },
+ valueOf: {
+ value: function() new String(uri.spec).valueOf(),
+ enumerable: false
+ },
+ toSource: {
+ value: function() new String(uri.spec).toSource(),
+ enumerable: false
+ }
+ });
+
+ return this;
+};
+
+URL.prototype = Object.create(String.prototype);
+exports.URL = URL;
+
+/**
+ * Parse and serialize a Data URL.
+ *
+ * See: http://tools.ietf.org/html/rfc2397
+ *
+ * Note: Could be extended in the future to decode / encode automatically binary
+ * data.
+ */
+const DataURL = Class({
+
+ get base64 () {
+ return "base64" in this.parameters;
+ },
+
+ set base64 (value) {
+ if (value)
+ this.parameters["base64"] = "";
+ else
+ delete this.parameters["base64"];
+ },
+ /**
+ * Initialize the Data URL object. If a uri is given, it will be parsed.
+ *
+ * @param {String} [uri] The uri to parse
+ *
+ * @throws {URIError} if the Data URL is malformed
+ */
+ initialize: function(uri) {
+ // Due to bug 751834 it is not possible document and define these
+ // properties in the prototype.
+
+ /**
+ * An hashmap that contains the parameters of the Data URL. By default is
+ * empty, that accordingly to RFC is equivalent to {"charset" : "US-ASCII"}
+ */
+ this.parameters = {};
+
+ /**
+ * The MIME type of the data. By default is empty, that accordingly to RFC
+ * is equivalent to "text/plain"
+ */
+ this.mimeType = "";
+
+ /**
+ * The string that represent the data in the Data URL
+ */
+ this.data = "";
+
+ if (typeof uri === "undefined")
+ return;
+
+ uri = String(uri);
+
+ let matches = uri.match(/^data:([^,]*),(.*)$/i);
+
+ if (!matches)
+ throw new URIError("Malformed Data URL: " + uri);
+
+ let mediaType = matches[1].trim();
+
+ this.data = decodeURIComponent(matches[2].trim());
+
+ if (!mediaType)
+ return;
+
+ let parametersList = mediaType.split(";");
+
+ this.mimeType = parametersList.shift().trim();
+
+ for (let parameter, i = 0; parameter = parametersList[i++];) {
+ let pairs = parameter.split("=");
+ let name = pairs[0].trim();
+ let value = pairs.length > 1 ? decodeURIComponent(pairs[1].trim()) : "";
+
+ this.parameters[name] = value;
+ }
+
+ if (this.base64)
+ this.data = base64.decode(this.data);
+
+ },
+
+ /**
+ * Returns the object as a valid Data URL string
+ *
+ * @returns {String} The Data URL
+ */
+ toString : function() {
+ let parametersList = [];
+
+ for (let name in this.parameters) {
+ let encodedParameter = encodeURIComponent(name);
+ let value = this.parameters[name];
+
+ if (value)
+ encodedParameter += "=" + encodeURIComponent(value);
+
+ parametersList.push(encodedParameter);
+ }
+
+ // If there is at least a parameter, add an empty string in order
+ // to start with a `;` on join call.
+ if (parametersList.length > 0)
+ parametersList.unshift("");
+
+ let data = this.base64 ? base64.encode(this.data) : this.data;
+
+ return "data:" +
+ this.mimeType +
+ parametersList.join(";") + "," +
+ encodeURIComponent(data);
+ }
+});
+
+exports.DataURL = DataURL;
diff --git a/tools/addon-sdk-1.12/lib/sdk/util/array.js b/tools/addon-sdk-1.12/lib/sdk/util/array.js
new file mode 100644
index 0000000..764eeca
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/util/array.js
@@ -0,0 +1,89 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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"
+};
+
+/**
+ * Returns `true` if given `array` contain given `element` or `false`
+ * otherwise.
+ * @param {Array} array
+ * Target array.
+ * @param {Object|String|Number|Boolean} element
+ * Element being looked up.
+ * @returns {Boolean}
+ */
+var has = exports.has = function has(array, element) {
+ // shorter and faster equivalent of `array.indexOf(element) >= 0`
+ return !!~array.indexOf(element);
+};
+var hasAny = exports.hasAny = function hasAny(array, elements) {
+ if (arguments.length < 2)
+ return false;
+ if (!Array.isArray(elements))
+ elements = [ elements ];
+ return array.some(function (element) {
+ return has(elements, element);
+ });
+};
+
+/**
+ * Adds given `element` to the given `array` if it does not contain it yet.
+ * `true` is returned if element was added otherwise `false` is returned.
+ * @param {Array} array
+ * Target array.
+ * @param {Object|String|Number|Boolean} element
+ * Element to be added.
+ * @returns {Boolean}
+ */
+var add = exports.add = function add(array, element) {
+ var result;
+ if ((result = !has(array, element)))
+ array.push(element);
+
+ return result;
+};
+
+/**
+ * Removes first occurrence of the given `element` from the given `array`. If
+ * `array` does not contain given `element` `false` is returned otherwise
+ * `true` is returned.
+ * @param {Array} array
+ * Target array.
+ * @param {Object|String|Number|Boolean} element
+ * Element to be removed.
+ * @returns {Boolean}
+ */
+exports.remove = function remove(array, element) {
+ var result;
+ if ((result = has(array, element)))
+ array.splice(array.indexOf(element), 1);
+
+ return result;
+};
+
+/**
+ * Produces a duplicate-free version of the given `array`.
+ * @param {Array} array
+ * Source array.
+ * @returns {Array}
+ */
+exports.unique = function unique(array) {
+ var value = [];
+ return array.forEach(function(element) {
+ add(value, element);
+ });
+ return value;
+};
+
+exports.flatten = function flatten(array){
+ var flat = [];
+ for (var i = 0, l = array.length; i < l; i++) {
+ flat = flat.concat(Array.isArray(array[i]) ? flatten(array[i]) : array[i]);
+ }
+ return flat;
+};
diff --git a/tools/addon-sdk-1.12/lib/sdk/util/collection.js b/tools/addon-sdk-1.12/lib/sdk/util/collection.js
new file mode 100644
index 0000000..54e7313
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/util/collection.js
@@ -0,0 +1,112 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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"
+};
+
+exports.Collection = Collection;
+
+/**
+ * Adds a collection property to the given object. Setting the property to a
+ * scalar value empties the collection and adds the value. Setting it to an
+ * array empties the collection and adds all the items in the array.
+ *
+ * @param obj
+ * The property will be defined on this object.
+ * @param propName
+ * The name of the property.
+ * @param array
+ * If given, this will be used as the collection's backing array.
+ */
+exports.addCollectionProperty = function addCollProperty(obj, propName, array) {
+ array = array || [];
+ let publicIface = new Collection(array);
+
+ obj.__defineSetter__(propName, function (itemOrItems) {
+ array.splice(0, array.length);
+ publicIface.add(itemOrItems);
+ });
+
+ obj.__defineGetter__(propName, function () {
+ return publicIface;
+ });
+};
+
+/**
+ * A collection is ordered, like an array, but its items are unique, like a set.
+ *
+ * @param array
+ * The collection is backed by an array. If this is given, it will be
+ * used as the backing array. This way the caller can fully control the
+ * collection. Otherwise a new empty array will be used, and no one but
+ * the collection will have access to it.
+ */
+function Collection(array) {
+ array = array || [];
+
+ /**
+ * Provides iteration over the collection. Items are yielded in the order
+ * they were added.
+ */
+ this.__iterator__ = function Collection___iterator__() {
+ let items = array.slice();
+ for (let i = 0; i < items.length; i++)
+ yield items[i];
+ };
+
+ /**
+ * The number of items in the collection.
+ */
+ this.__defineGetter__("length", function Collection_get_length() {
+ return array.length;
+ });
+
+ /**
+ * Adds a single item or an array of items to the collection. Any items
+ * already contained in the collection are ignored.
+ *
+ * @param itemOrItems
+ * An item or array of items.
+ * @return The collection.
+ */
+ this.add = function Collection_add(itemOrItems) {
+ let items = toArray(itemOrItems);
+ for (let i = 0; i < items.length; i++) {
+ let item = items[i];
+ if (array.indexOf(item) < 0)
+ array.push(item);
+ }
+ return this;
+ };
+
+ /**
+ * Removes a single item or an array of items from the collection. Any items
+ * not contained in the collection are ignored.
+ *
+ * @param itemOrItems
+ * An item or array of items.
+ * @return The collection.
+ */
+ this.remove = function Collection_remove(itemOrItems) {
+ let items = toArray(itemOrItems);
+ for (let i = 0; i < items.length; i++) {
+ let idx = array.indexOf(items[i]);
+ if (idx >= 0)
+ array.splice(idx, 1);
+ }
+ return this;
+ };
+};
+
+function toArray(itemOrItems) {
+ let isArr = itemOrItems &&
+ itemOrItems.constructor &&
+ itemOrItems.constructor.name === "Array";
+ return isArr ? itemOrItems : [itemOrItems];
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/util/deprecate.js b/tools/addon-sdk-1.12/lib/sdk/util/deprecate.js
new file mode 100644
index 0000000..b38606a
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/util/deprecate.js
@@ -0,0 +1,27 @@
+/* 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 traceback = require("../console/traceback");
+
+function deprecateUsage(msg) {
+ // Print caller stacktrace in order to help figuring out which code
+ // does use deprecated thing
+ let stack = traceback.get().slice(0, -2);
+ console.error("DEPRECATED: " + msg + "\n" + traceback.format(stack));
+}
+exports.deprecateUsage = deprecateUsage;
+
+function deprecateFunction(fun, msg) {
+ return function deprecated() {
+ deprecateUsage(msg);
+ return fun.apply(this, arguments);
+ };
+}
+exports.deprecateFunction = deprecateFunction;
diff --git a/tools/addon-sdk-1.12/lib/sdk/util/list.js b/tools/addon-sdk-1.12/lib/sdk/util/list.js
new file mode 100644
index 0000000..0a143d9
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/util/list.js
@@ -0,0 +1,78 @@
+/* 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 { Class } = require('../core/heritage');
+const listNS = require('../core/namespace').ns();
+
+const List = Class({
+ /**
+ * List constructor can take any number of element to populate itself.
+ * @params {Object|String|Number} element
+ * @example
+ * List(1,2,3).length == 3 // true
+ */
+ initialize: function List() {
+ listNS(this).keyValueMap = [];
+
+ for (let i = 0, ii = arguments.length; i < ii; i++)
+ addListItem(this, arguments[i]);
+ },
+ /**
+ * Number of elements in this list.
+ * @type {Number}
+ */
+ get length() listNS(this).keyValueMap.length,
+ /**
+ * Returns a string representing this list.
+ * @returns {String}
+ */
+ toString: function toString() 'List(' + listNS(this).keyValueMap + ')',
+ /**
+ * Custom iterator providing `List`s enumeration behavior.
+ * We cant reuse `_iterator` that is defined by `Iterable` since it provides
+ * iteration in an arbitrary order.
+ * @see https://developer.mozilla.org/en/JavaScript/Reference/Statements/for...in
+ * @param {Boolean} onKeys
+ */
+ __iterator__: function __iterator__(onKeys, onKeyValue) {
+ let array = listNS(this).keyValueMap.slice(0),
+ i = -1;
+ for each(let element in array)
+ yield onKeyValue ? [++i, element] : onKeys ? ++i : element;
+ }
+});
+exports.List = List;
+
+function addListItem(that, value) {
+ let list = listNS(that).keyValueMap,
+ index = list.indexOf(value);
+
+ if (-1 === index) {
+ try {
+ that[that.length] = value;
+ }
+ catch (e) {}
+ list.push(value);
+ }
+}
+exports.addListItem = addListItem;
+
+function removeListItem(that, element) {
+ let list = listNS(that).keyValueMap,
+ index = list.indexOf(element);
+
+ if (0 <= index) {
+ list.splice(index, 1);
+ try {
+ for (let length = list.length; index < length; index++)
+ that[index] = list[index];
+ that[list.length] = undefined;
+ }
+ catch(e){}
+ }
+}
+exports.removeListItem = removeListItem;
+
+exports.listNS = listNS;
diff --git a/tools/addon-sdk-1.12/lib/sdk/util/object.js b/tools/addon-sdk-1.12/lib/sdk/util/object.js
new file mode 100644
index 0000000..4b1a2d4
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/util/object.js
@@ -0,0 +1,57 @@
+/* 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"
+};
+
+/**
+ * Merges all the properties of all arguments into first argument. If two or
+ * more argument objects have own properties with the same name, the property
+ * is overridden, with precedence from right to left, implying, that properties
+ * of the object on the left are overridden by a same named property of the
+ * object on the right.
+ *
+ * Any argument given with "falsy" value - commonly `null` and `undefined` in
+ * case of objects - are skipped.
+ *
+ * @examples
+ * var a = { bar: 0, a: 'a' }
+ * var b = merge(a, { foo: 'foo', bar: 1 }, { foo: 'bar', name: 'b' });
+ * b === a // true
+ * b.a // 'a'
+ * b.foo // 'bar'
+ * b.bar // 1
+ * b.name // 'b'
+ */
+function merge(source) {
+ let descriptor = {};
+ // `Boolean` converts the first parameter to a boolean value. Any object is
+ // converted to `true` where `null` and `undefined` becames `false`. Therefore
+ // the `filter` method will keep only objects that are defined and not null.
+ Array.slice(arguments, 1).filter(Boolean).forEach(function onEach(properties) {
+ Object.getOwnPropertyNames(properties).forEach(function(name) {
+ descriptor[name] = Object.getOwnPropertyDescriptor(properties, name);
+ });
+ });
+ return Object.defineProperties(source, descriptor);
+}
+exports.merge = merge;
+
+/**
+ * Returns an object that inherits from the first argument and contains all the
+ * properties from all following arguments.
+ * `extend(source1, source2, source3)` is equivalent of
+ * `merge(Object.create(source1), source2, source3)`.
+ */
+function extend(source) {
+ let rest = Array.slice(arguments, 1);
+ rest.unshift(Object.create(source));
+ return merge.apply(null, rest);
+}
+exports.extend = extend;
+
+
diff --git a/tools/addon-sdk-1.12/lib/sdk/util/registry.js b/tools/addon-sdk-1.12/lib/sdk/util/registry.js
new file mode 100644
index 0000000..7d385f0
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/util/registry.js
@@ -0,0 +1,61 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 each (let instance in _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;
+
diff --git a/tools/addon-sdk-1.12/lib/sdk/util/uuid.js b/tools/addon-sdk-1.12/lib/sdk/util/uuid.js
new file mode 100644
index 0000000..0ebf981
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/util/uuid.js
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Cc, Ci, components: { ID: parseUUID } } = require('chrome');
+const { generateUUID } = Cc['@mozilla.org/uuid-generator;1'].
+ getService(Ci.nsIUUIDGenerator);
+
+// Returns `uuid`. If `id` is passed then it's parsed to `uuid` and returned
+// if not then new one is generated.
+exports.uuid = function uuid(id) id ? parseUUID(id) : generateUUID()
diff --git a/tools/addon-sdk-1.12/lib/sdk/widget.js b/tools/addon-sdk-1.12/lib/sdk/widget.js
new file mode 100644
index 0000000..f1236aa
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/widget.js
@@ -0,0 +1,938 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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": "stable"
+};
+
+// Widget content types
+const CONTENT_TYPE_URI = 1;
+const CONTENT_TYPE_HTML = 2;
+const CONTENT_TYPE_IMAGE = 3;
+
+const ERR_CONTENT = "No content or contentURL property found. Widgets must "
+ + "have one or the other.",
+ ERR_LABEL = "The widget must have a non-empty label property.",
+ ERR_ID = "You have to specify a unique value for the id property of " +
+ "your widget in order for the application to remember its " +
+ "position.",
+ ERR_DESTROYED = "The widget has been destroyed and can no longer be used.";
+
+// Supported events, mapping from DOM event names to our event names
+const EVENTS = {
+ "click": "click",
+ "mouseover": "mouseover",
+ "mouseout": "mouseout",
+};
+
+if (!require("./system/xul-app").is("Firefox")) {
+ throw new Error([
+ "The widget module currently supports only Firefox. In the future ",
+ "it will support other applications. Please see ",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=560716 for more information."
+ ].join(""));
+}
+
+const { validateOptions } = require("./deprecated/api-utils");
+const panels = require("./panel");
+const { EventEmitter, EventEmitterTrait } = require("./deprecated/events");
+const { Trait } = require("./deprecated/traits");
+const LightTrait = require('./deprecated/light-traits').Trait;
+const { Loader, Symbiont } = require("./content/content");
+const { Cortex } = require('./deprecated/cortex');
+const windowsAPI = require("./windows");
+const { WindowTracker } = require("./deprecated/window-utils");
+const { isBrowser } = require("./window/utils");
+const { setTimeout } = require("./timers");
+const unload = require("./system/unload");
+const { uuid } = require("./util/uuid");
+
+// Data types definition
+const valid = {
+ number: { is: ["null", "undefined", "number"] },
+ string: { is: ["null", "undefined", "string"] },
+ id: {
+ is: ["string"],
+ ok: function (v) v.length > 0,
+ msg: ERR_ID,
+ readonly: true
+ },
+ label: {
+ is: ["string"],
+ ok: function (v) v.length > 0,
+ msg: ERR_LABEL
+ },
+ panel: {
+ is: ["null", "undefined", "object"],
+ ok: function(v) !v || v instanceof panels.Panel
+ },
+ width: {
+ is: ["null", "undefined", "number"],
+ map: function (v) {
+ if (null === v || undefined === v) v = 16;
+ return v;
+ },
+ defaultValue: 16
+ },
+ allow: {
+ is: ["null", "undefined", "object"],
+ map: function (v) {
+ if (!v) v = { script: true };
+ return v;
+ },
+ get defaultValue() ({ script: true })
+ },
+};
+
+// Widgets attributes definition
+let widgetAttributes = {
+ label: valid.label,
+ id: valid.id,
+ tooltip: valid.string,
+ width: valid.width,
+ content: valid.string,
+ panel: valid.panel,
+ allow: valid.allow
+};
+
+// Import data definitions from loader, but don't compose with it as Model
+// functions allow us to recreate easily all Loader code.
+let loaderAttributes = require("./content/loader").validationAttributes;
+for (let i in loaderAttributes)
+ widgetAttributes[i] = loaderAttributes[i];
+
+widgetAttributes.contentURL.optional = true;
+
+// Widgets public events list, that are automatically binded in options object
+const WIDGET_EVENTS = [
+ "click",
+ "mouseover",
+ "mouseout",
+ "error",
+ "message",
+ "attach"
+];
+
+// `Model` utility functions that help creating these various Widgets objects
+let model = {
+
+ // Validate one attribute using api-utils.js:validateOptions function
+ _validate: function _validate(name, suspect, validation) {
+ let $1 = {};
+ $1[name] = suspect;
+ let $2 = {};
+ $2[name] = validation;
+ return validateOptions($1, $2)[name];
+ },
+
+ /**
+ * This method has two purposes:
+ * 1/ Validate and define, on a given object, a set of attribute
+ * 2/ Emit a "change" event on this object when an attribute is changed
+ *
+ * @params {Object} object
+ * Object on which we can bind attributes on and watch for their changes.
+ * This object must have an EventEmitter interface, or, at least `_emit`
+ * method
+ * @params {Object} attrs
+ * Dictionary of attributes definition following api-utils:validateOptions
+ * scheme
+ * @params {Object} values
+ * Dictionary of attributes default values
+ */
+ setAttributes: function setAttributes(object, attrs, values) {
+ let properties = {};
+ for (let name in attrs) {
+ let value = values[name];
+ let req = attrs[name];
+
+ // Retrieve default value from typedef if the value is not defined
+ if ((typeof value == "undefined" || value == null) && req.defaultValue)
+ value = req.defaultValue;
+
+ // Check for valid value if value is defined or mandatory
+ if (!req.optional || typeof value != "undefined")
+ value = model._validate(name, value, req);
+
+ // In any case, define this property on `object`
+ let property = null;
+ if (req.readonly) {
+ property = {
+ value: value,
+ writable: false,
+ enumerable: true,
+ configurable: false
+ };
+ }
+ else {
+ property = model._createWritableProperty(name, value);
+ }
+
+ properties[name] = property;
+ }
+ Object.defineProperties(object, properties);
+ },
+
+ // Generate ES5 property definition for a given attribute
+ _createWritableProperty: function _createWritableProperty(name, value) {
+ return {
+ get: function () {
+ return value;
+ },
+ set: function (newValue) {
+ value = newValue;
+ // The main goal of all this Model stuff is here:
+ // We want to forward all changes to some listeners
+ this._emit("change", name, value);
+ },
+ enumerable: true,
+ configurable: false
+ };
+ },
+
+ /**
+ * Automagically register listeners in options dictionary
+ * by detecting listener attributes with name starting with `on`
+ *
+ * @params {Object} object
+ * Target object that need to follow EventEmitter interface, or, at least,
+ * having `on` method.
+ * @params {Array} events
+ * List of events name to automatically bind.
+ * @params {Object} listeners
+ * Dictionary of event listener functions to register.
+ */
+ setEvents: function setEvents(object, events, listeners) {
+ for (let i = 0, l = events.length; i < l; i++) {
+ let name = events[i];
+ let onName = "on" + name[0].toUpperCase() + name.substr(1);
+ if (!listeners[onName])
+ continue;
+ object.on(name, listeners[onName].bind(object));
+ }
+ }
+
+};
+
+
+/**
+ * Main Widget class: entry point of the widget API
+ *
+ * Allow to control all widget across all existing windows with a single object.
+ * Widget.getView allow to retrieve a WidgetView instance to control a widget
+ * specific to one window.
+ */
+const WidgetTrait = LightTrait.compose(EventEmitterTrait, LightTrait({
+
+ _initWidget: function _initWidget(options) {
+ model.setAttributes(this, widgetAttributes, options);
+
+ browserManager.validate(this);
+
+ // We must have at least content or contentURL defined
+ if (!(this.content || this.contentURL))
+ throw new Error(ERR_CONTENT);
+
+ this._views = [];
+
+ // Set tooltip to label value if we don't have tooltip defined
+ if (!this.tooltip)
+ this.tooltip = this.label;
+
+ model.setEvents(this, WIDGET_EVENTS, options);
+
+ this.on('change', this._onChange.bind(this));
+
+ let self = this;
+ this._port = EventEmitterTrait.create({
+ emit: function () {
+ let args = arguments;
+ self._views.forEach(function(v) v.port.emit.apply(v.port, args));
+ }
+ });
+ // expose wrapped port, that exposes only public properties.
+ this._port._public = Cortex(this._port);
+
+ // Register this widget to browser manager in order to create new widget on
+ // all new windows
+ browserManager.addItem(this);
+ },
+
+ _onChange: function _onChange(name, value) {
+ // Set tooltip to label value if we don't have tooltip defined
+ if (name == 'tooltip' && !value) {
+ // we need to change tooltip again in order to change the value of the
+ // attribute itself
+ this.tooltip = this.label;
+ return;
+ }
+
+ // Forward attributes changes to WidgetViews
+ if (['width', 'tooltip', 'content', 'contentURL'].indexOf(name) != -1) {
+ this._views.forEach(function(v) v[name] = value);
+ }
+ },
+
+ _onEvent: function _onEvent(type, eventData) {
+ this._emit(type, eventData);
+ },
+
+ _createView: function _createView() {
+ // Create a new WidgetView instance
+ let view = WidgetView(this);
+
+ // Keep a reference to it
+ this._views.push(view);
+
+ return view;
+ },
+
+ // a WidgetView instance is destroyed
+ _onViewDestroyed: function _onViewDestroyed(view) {
+ let idx = this._views.indexOf(view);
+ this._views.splice(idx, 1);
+ },
+
+ /**
+ * Called on browser window closed, to destroy related WidgetViews
+ * @params {ChromeWindow} window
+ * Window that has been closed
+ */
+ _onWindowClosed: function _onWindowClosed(window) {
+ for each (let view in this._views) {
+ if (view._isInChromeWindow(window)) {
+ view.destroy();
+ break;
+ }
+ }
+ },
+
+ /**
+ * Get the WidgetView instance related to a BrowserWindow instance
+ * @params {BrowserWindow} window
+ * BrowserWindow reference from "windows" module
+ */
+ getView: function getView(window) {
+ for each (let view in this._views) {
+ if (view._isInWindow(window)) {
+ return view._public;
+ }
+ }
+ return null;
+ },
+
+ get port() this._port._public,
+ set port(v) {}, // Work around Cortex failure with getter without setter
+ // See bug 653464
+ _port: null,
+
+ postMessage: function postMessage(message) {
+ this._views.forEach(function(v) v.postMessage(message));
+ },
+
+ destroy: function destroy() {
+ if (this.panel)
+ this.panel.destroy();
+
+ // Dispatch destroy calls to views
+ // we need to go backward as we remove items from this array in
+ // _onViewDestroyed
+ for (let i = this._views.length - 1; i >= 0; i--)
+ this._views[i].destroy();
+
+ // Unregister widget to stop creating it over new windows
+ // and allow creation of new widget with same id
+ browserManager.removeItem(this);
+ }
+
+}));
+
+// Widget constructor
+const Widget = function Widget(options) {
+ let w = WidgetTrait.create(Widget.prototype);
+ w._initWidget(options);
+
+ // Return a Cortex of widget in order to hide private attributes like _onEvent
+ let _public = Cortex(w);
+ unload.ensure(_public, "destroy");
+ return _public;
+}
+exports.Widget = Widget;
+
+
+
+/**
+ * WidgetView is an instance of a widget for a specific window.
+ *
+ * This is an external API that can be retrieved by calling Widget.getView or
+ * by watching `attach` event on Widget.
+ */
+const WidgetViewTrait = LightTrait.compose(EventEmitterTrait, LightTrait({
+
+ // Reference to the matching WidgetChrome
+ // set right after constructor call
+ _chrome: null,
+
+ // Public interface of the WidgetView, passed in `attach` event or in
+ // Widget.getView
+ _public: null,
+
+ _initWidgetView: function WidgetView__initWidgetView(baseWidget) {
+ this._baseWidget = baseWidget;
+
+ model.setAttributes(this, widgetAttributes, baseWidget);
+
+ this.on('change', this._onChange.bind(this));
+
+ let self = this;
+ this._port = EventEmitterTrait.create({
+ emit: function () {
+ if (!self._chrome)
+ throw new Error(ERR_DESTROYED);
+ self._chrome.update(self._baseWidget, "emit", arguments);
+ }
+ });
+ // expose wrapped port, that exposes only public properties.
+ this._port._public = Cortex(this._port);
+
+ this._public = Cortex(this);
+ },
+
+ // Called by WidgetChrome, when the related Worker is applied to the document,
+ // so that we can start sending events to it
+ _onWorkerReady: function () {
+ // Emit an `attach` event with a WidgetView instance without private attrs
+ this._baseWidget._emit("attach", this._public);
+ },
+
+ _onChange: function WidgetView__onChange(name, value) {
+ if (name == 'tooltip' && !value) {
+ this.tooltip = this.label;
+ return;
+ }
+
+ // Forward attributes changes to WidgetChrome instance
+ if (['width', 'tooltip', 'content', 'contentURL'].indexOf(name) != -1) {
+ this._chrome.update(this._baseWidget, name, value);
+ }
+ },
+
+ _onEvent: function WidgetView__onEvent(type, eventData, domNode) {
+ // Dispatch event in view
+ this._emit(type, eventData);
+
+ // And forward it to the main Widget object
+ if ("click" == type || type.indexOf("mouse") == 0)
+ this._baseWidget._onEvent(type, this._public);
+ else
+ this._baseWidget._onEvent(type, eventData);
+
+ // Special case for click events: if the widget doesn't have a click
+ // handler, but it does have a panel, display the panel.
+ if ("click" == type && !this._listeners("click").length && this.panel)
+ this.panel.show(domNode);
+ },
+
+ _isInWindow: function WidgetView__isInWindow(window) {
+ return windowsAPI.BrowserWindow({
+ window: this._chrome.window
+ }) == window;
+ },
+
+ _isInChromeWindow: function WidgetView__isInChromeWindow(window) {
+ return this._chrome.window == window;
+ },
+
+ _onPortEvent: function WidgetView__onPortEvent(args) {
+ let port = this._port;
+ port._emit.apply(port, args);
+ let basePort = this._baseWidget._port;
+ basePort._emit.apply(basePort, args);
+ },
+
+ get port() this._port._public,
+ set port(v) {}, // Work around Cortex failure with getter without setter
+ // See bug 653464
+ _port: null,
+
+ postMessage: function WidgetView_postMessage(message) {
+ if (!this._chrome)
+ throw new Error(ERR_DESTROYED);
+ this._chrome.update(this._baseWidget, "postMessage", message);
+ },
+
+ destroy: function WidgetView_destroy() {
+ this._chrome.destroy();
+ delete this._chrome;
+ this._baseWidget._onViewDestroyed(this);
+ this._emit("detach");
+ }
+
+}));
+
+const WidgetView = function WidgetView(baseWidget) {
+ let w = WidgetViewTrait.create(WidgetView.prototype);
+ w._initWidgetView(baseWidget);
+ return w;
+}
+
+
+
+/**
+ * Keeps track of all browser windows.
+ * Exposes methods for adding/removing widgets
+ * across all open windows (and future ones).
+ * Create a new instance of BrowserWindow per window.
+ */
+let browserManager = {
+ items: [],
+ windows: [],
+
+ // Registers the manager to listen for window openings and closings. Note
+ // that calling this method can cause onTrack to be called immediately if
+ // there are open windows.
+ init: function () {
+ let windowTracker = new WindowTracker(this);
+ unload.ensure(windowTracker);
+ },
+
+ // Registers a window with the manager. This is a WindowTracker callback.
+ onTrack: function browserManager_onTrack(window) {
+ if (isBrowser(window)) {
+ let win = new BrowserWindow(window);
+ win.addItems(this.items);
+ this.windows.push(win);
+ }
+ },
+
+ // Unregisters a window from the manager. It's told to undo all
+ // modifications. This is a WindowTracker callback. Note that when
+ // WindowTracker is unloaded, it calls onUntrack for every currently opened
+ // window. The browserManager therefore doesn't need to specially handle
+ // unload itself, since unloading the browserManager means untracking all
+ // currently opened windows.
+ onUntrack: function browserManager_onUntrack(window) {
+ if (isBrowser(window)) {
+ this.items.forEach(function(i) i._onWindowClosed(window));
+ for (let i = 0; i < this.windows.length; i++) {
+ if (this.windows[i].window == window) {
+ this.windows.splice(i, 1)[0];
+ return;
+ }
+ }
+
+ }
+ },
+
+ // Used to validate widget by browserManager before adding it,
+ // in order to check input very early in widget constructor
+ validate : function (item) {
+ let idx = this.items.indexOf(item);
+ if (idx > -1)
+ throw new Error("The widget " + item + " has already been added.");
+ if (item.id) {
+ let sameId = this.items.filter(function(i) i.id == item.id);
+ if (sameId.length > 0)
+ throw new Error("This widget ID is already used: " + item.id);
+ } else {
+ item.id = this.items.length;
+ }
+ },
+
+ // Registers an item with the manager. It's added to all currently registered
+ // windows, and when new windows are registered it will be added to them, too.
+ addItem: function browserManager_addItem(item) {
+ this.items.push(item);
+ this.windows.forEach(function (w) w.addItems([item]));
+ },
+
+ // Unregisters an item from the manager. It's removed from all windows that
+ // are currently registered.
+ removeItem: function browserManager_removeItem(item) {
+ let idx = this.items.indexOf(item);
+ if (idx > -1)
+ this.items.splice(idx, 1);
+ }
+};
+
+
+
+/**
+ * Keeps track of a single browser window.
+ *
+ * This is where the core of how a widget's content is added to a window lives.
+ */
+function BrowserWindow(window) {
+ this.window = window;
+ this.doc = window.document;
+}
+
+BrowserWindow.prototype = {
+
+ // Adds an array of items to the window.
+ addItems: function BW_addItems(items) {
+ items.forEach(this._addItemToWindow, this);
+ },
+
+ _addItemToWindow: function BW__addItemToWindow(baseWidget) {
+ // Create a WidgetView instance
+ let widget = baseWidget._createView();
+
+ // Create a WidgetChrome instance
+ let item = new WidgetChrome({
+ widget: widget,
+ doc: this.doc,
+ window: this.window
+ });
+
+ widget._chrome = item;
+
+ this._insertNodeInToolbar(item.node);
+
+ // We need to insert Widget DOM Node before finishing widget view creation
+ // (because fill creates an iframe and tries to access its docShell)
+ item.fill();
+ },
+
+ _insertNodeInToolbar: function BW__insertNodeInToolbar(node) {
+ // Add to the customization palette
+ let toolbox = this.doc.getElementById("navigator-toolbox");
+ let palette = toolbox.palette;
+ palette.appendChild(node);
+
+ // Search for widget toolbar by reading toolbar's currentset attribute
+ let container = null;
+ let toolbars = this.doc.getElementsByTagName("toolbar");
+ let id = node.getAttribute("id");
+ for (let i = 0, l = toolbars.length; i < l; i++) {
+ let toolbar = toolbars[i];
+ if (toolbar.getAttribute("currentset").indexOf(id) == -1)
+ continue;
+ container = toolbar;
+ }
+
+ // if widget isn't in any toolbar, add it to the addon-bar
+ // TODO: we may want some "first-launch" module to do this only on very
+ // first execution
+ if (!container) {
+ container = this.doc.getElementById("addon-bar");
+ // TODO: find a way to make the following code work when we use "cfx run":
+ // http://mxr.mozilla.org/mozilla-central/source/browser/base/content/browser.js#8586
+ // until then, force display of addon bar directly from sdk code
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=627484
+ if (container.collapsed)
+ this.window.toggleAddonBar();
+ }
+
+ // Now retrieve a reference to the next toolbar item
+ // by reading currentset attribute on the toolbar
+ let nextNode = null;
+ let currentSet = container.getAttribute("currentset");
+ let ids = (currentSet == "__empty") ? [] : currentSet.split(",");
+ let idx = ids.indexOf(id);
+ if (idx != -1) {
+ for (let i = idx; i < ids.length; i++) {
+ nextNode = this.doc.getElementById(ids[i]);
+ if (nextNode)
+ break;
+ }
+ }
+
+ // Finally insert our widget in the right toolbar and in the right position
+ container.insertItem(id, nextNode, null, false);
+
+ // Update DOM in order to save position: which toolbar, and which position
+ // in this toolbar. But only do this the first time we add it to the toolbar
+ // Otherwise, this code will collide with other instance of Widget module
+ // during Firefox startup. See bug 685929.
+ if (ids.indexOf(id) == -1) {
+ container.setAttribute("currentset", container.currentSet);
+ // Save DOM attribute in order to save position on new window opened
+ this.window.document.persist(container.id, "currentset");
+ }
+ }
+}
+
+
+/**
+ * Final Widget class that handles chrome DOM Node:
+ * - create initial DOM nodes
+ * - receive instruction from WidgetView through update method and update DOM
+ * - watch for DOM events and forward them to WidgetView
+ */
+function WidgetChrome(options) {
+ this.window = options.window;
+ this._doc = options.doc;
+ this._widget = options.widget;
+ this._symbiont = null; // set later
+ this.node = null; // set later
+
+ this._createNode();
+}
+
+// Update a property of a widget.
+WidgetChrome.prototype.update = function WC_update(updatedItem, property, value) {
+ switch(property) {
+ case "contentURL":
+ case "content":
+ this.setContent();
+ break;
+ case "width":
+ this.node.style.minWidth = value + "px";
+ this.node.querySelector("iframe").style.width = value + "px";
+ break;
+ case "tooltip":
+ this.node.setAttribute("tooltiptext", value);
+ break;
+ case "postMessage":
+ this._symbiont.postMessage(value);
+ break;
+ case "emit":
+ let port = this._symbiont.port;
+ port.emit.apply(port, value);
+ break;
+ }
+}
+
+// Add a widget to this window.
+WidgetChrome.prototype._createNode = function WC__createNode() {
+ // XUL element container for widget
+ let node = this._doc.createElement("toolbaritem");
+ let guid = String(uuid());
+
+ // Temporary work around require("self") failing on unit-test execution ...
+ let jetpackID = "testID";
+ try {
+ jetpackID = require("./self").id;
+ } catch(e) {}
+
+ // Compute an unique and stable widget id with jetpack id and widget.id
+ let id = "widget:" + jetpackID + "-" + this._widget.id;
+ node.setAttribute("id", id);
+ node.setAttribute("label", this._widget.label);
+ node.setAttribute("tooltiptext", this._widget.tooltip);
+ node.setAttribute("align", "center");
+ // Bug 626326: Prevent customize toolbar context menu to appear
+ node.setAttribute("context", "");
+
+ // TODO move into a stylesheet, configurable by consumers.
+ // Either widget.style, exposing the style object, or a URL
+ // (eg, can load local stylesheet file).
+ node.setAttribute("style", [
+ "overflow: hidden; margin: 1px 2px 1px 2px; padding: 0px;",
+ "min-height: 16px;",
+ ].join(""));
+
+ node.style.minWidth = this._widget.width + "px";
+
+ this.node = node;
+}
+
+// Initial population of a widget's content.
+WidgetChrome.prototype.fill = function WC_fill() {
+ // Create element
+ var iframe = this._doc.createElement("iframe");
+ iframe.setAttribute("type", "content");
+ iframe.setAttribute("transparent", "transparent");
+ iframe.style.overflow = "hidden";
+ iframe.style.height = "16px";
+ iframe.style.maxHeight = "16px";
+ iframe.style.width = this._widget.width + "px";
+ iframe.setAttribute("flex", "1");
+ iframe.style.border = "none";
+ iframe.style.padding = "0px";
+
+ // Do this early, because things like contentWindow are null
+ // until the node is attached to a document.
+ this.node.appendChild(iframe);
+
+ // add event handlers
+ this.addEventHandlers();
+
+ // set content
+ this.setContent();
+}
+
+// Get widget content type.
+WidgetChrome.prototype.getContentType = function WC_getContentType() {
+ if (this._widget.content)
+ return CONTENT_TYPE_HTML;
+ return (this._widget.contentURL && /\.(jpg|gif|png|ico)$/.test(this._widget.contentURL))
+ ? CONTENT_TYPE_IMAGE : CONTENT_TYPE_URI;
+}
+
+// Set widget content.
+WidgetChrome.prototype.setContent = function WC_setContent() {
+ let type = this.getContentType();
+ let contentURL = null;
+
+ switch (type) {
+ case CONTENT_TYPE_HTML:
+ contentURL = "data:text/html;charset=utf-8," + encodeURIComponent(this._widget.content);
+ break;
+ case CONTENT_TYPE_URI:
+ contentURL = this._widget.contentURL;
+ break;
+ case CONTENT_TYPE_IMAGE:
+ let imageURL = this._widget.contentURL;
+ contentURL = "data:text/html;charset=utf-8,<html><body><img src='" +
+ encodeURI(imageURL) + "'></body></html>";
+ break;
+ default:
+ throw new Error("The widget's type cannot be determined.");
+ }
+
+ let iframe = this.node.firstElementChild;
+
+ let self = this;
+ // Cleanup previously created symbiont (in case we are update content)
+ if (this._symbiont)
+ this._symbiont.destroy();
+
+ this._symbiont = Trait.compose(Symbiont.resolve({
+ _onContentScriptEvent: "_onContentScriptEvent-not-used",
+ _onInit: "_initSymbiont"
+ }), {
+ // Overload `Symbiont._onInit` in order to know when the related worker
+ // is ready.
+ _onInit: function () {
+ this._initSymbiont();
+ self._widget._onWorkerReady();
+ },
+ _onContentScriptEvent: function () {
+ // Redirect events to WidgetView
+ self._widget._onPortEvent(arguments);
+ }
+ })({
+ frame: iframe,
+ contentURL: contentURL,
+ contentScriptFile: this._widget.contentScriptFile,
+ contentScript: this._widget.contentScript,
+ contentScriptWhen: this._widget.contentScriptWhen,
+ contentScriptOptions: this._widget.contentScriptOptions,
+ allow: this._widget.allow,
+ onMessage: function(message) {
+ setTimeout(function() {
+ self._widget._onEvent("message", message);
+ }, 0);
+ }
+ });
+}
+
+// Detect if document consists of a single image.
+WidgetChrome._isImageDoc = function WC__isImageDoc(doc) {
+ return doc.body.childNodes.length == 1 &&
+ doc.body.firstElementChild &&
+ doc.body.firstElementChild.tagName == "IMG";
+}
+
+// Set up all supported events for a widget.
+WidgetChrome.prototype.addEventHandlers = function WC_addEventHandlers() {
+ let contentType = this.getContentType();
+
+ let self = this;
+ let listener = function(e) {
+ // Ignore event firings that target the iframe.
+ if (e.target == self.node.firstElementChild)
+ return;
+
+ // The widget only supports left-click for now,
+ // so ignore right-clicks.
+ if (e.type == "click" && e.button == 2)
+ return;
+
+ // Proxy event to the widget
+ setTimeout(function() {
+ self._widget._onEvent(EVENTS[e.type], null, self.node);
+ }, 0);
+ };
+
+ this.eventListeners = {};
+ let iframe = this.node.firstElementChild;
+ for (let type in EVENTS) {
+ iframe.addEventListener(type, listener, true, true);
+
+ // Store listeners for later removal
+ this.eventListeners[type] = listener;
+ }
+
+ // On document load, make modifications required for nice default
+ // presentation.
+ function loadListener(e) {
+ let containerStyle = self.window.getComputedStyle(self.node.parentNode);
+ // Ignore event firings that target the iframe
+ if (e.target == iframe)
+ return;
+ // Ignore about:blank loads
+ if (e.type == "load" && e.target.location == "about:blank")
+ return;
+
+ // We may have had an unload event before that cleaned up the symbiont
+ if (!self._symbiont)
+ self.setContent();
+
+ let doc = e.target;
+ if (contentType == CONTENT_TYPE_IMAGE || WidgetChrome._isImageDoc(doc)) {
+ // Force image content to size.
+ // Add-on authors must size their images correctly.
+ doc.body.firstElementChild.style.width = self._widget.width + "px";
+ doc.body.firstElementChild.style.height = "16px";
+ }
+
+ // Extend the add-on bar's default text styles to the widget.
+ doc.body.style.color = containerStyle.color;
+ doc.body.style.fontFamily = containerStyle.fontFamily;
+ doc.body.style.fontSize = containerStyle.fontSize;
+ doc.body.style.fontWeight = containerStyle.fontWeight;
+ doc.body.style.textShadow = containerStyle.textShadow;
+ // Allow all content to fill the box by default.
+ doc.body.style.margin = "0";
+ }
+ iframe.addEventListener("load", loadListener, true);
+ this.eventListeners["load"] = loadListener;
+
+ // Register a listener to unload symbiont if the toolbaritem is moved
+ // on user toolbars customization
+ function unloadListener(e) {
+ if (e.target.location == "about:blank")
+ return;
+ self._symbiont.destroy();
+ self._symbiont = null;
+ // This may fail but not always, it depends on how the node is
+ // moved or removed
+ try {
+ self.setContent();
+ } catch(e) {}
+
+ }
+
+ iframe.addEventListener("unload", unloadListener, true);
+ this.eventListeners["unload"] = unloadListener;
+}
+
+// Remove and unregister the widget from everything
+WidgetChrome.prototype.destroy = function WC_destroy(removedItems) {
+ // remove event listeners
+ for (let type in this.eventListeners) {
+ let listener = this.eventListeners[type];
+ this.node.firstElementChild.removeEventListener(type, listener, true);
+ }
+ // remove dom node
+ this.node.parentNode.removeChild(this.node);
+ // cleanup symbiont
+ this._symbiont.destroy();
+ // cleanup itself
+ this.eventListeners = null;
+ this._widget = null;
+ this._symbiont = null;
+}
+
+// Init the browserManager only after setting prototypes and such above, because
+// it will cause browserManager.onTrack to be called immediately if there are
+// open windows.
+browserManager.init();
diff --git a/tools/addon-sdk-1.12/lib/sdk/window/browser.js b/tools/addon-sdk-1.12/lib/sdk/window/browser.js
new file mode 100644
index 0000000..2b63a83
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/window/browser.js
@@ -0,0 +1,41 @@
+/* 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 { Class } = require('../core/heritage');
+const { windowNS } = require('./namespace');
+const { on, off, once } = require('../event/core');
+const { method } = require('../lang/functional');
+const { getWindowTitle } = require('./utils');
+const unload = require('../system/unload');
+const { getMode } = require('../private-browsing/utils');
+const { EventTarget } = require('../event/target');
+
+const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec, consider using require("tabs") instead';
+
+const BrowserWindow = Class({
+ initialize: function initialize(options) {
+ EventTarget.prototype.initialize.call(this, options);
+ windowNS(this).window = options.window;
+ },
+ activate: function activate() {
+ // TODO
+ return null;
+ },
+ close: function() {
+ throw new Error(ERR_FENNEC_MSG);
+ return null;
+ },
+ get title() getWindowTitle(windowNS(this).window),
+ // NOTE: Fennec only has one window, which is assumed below
+ // TODO: remove assumption below
+ // NOTE: tabs requires windows
+ get tabs() require('../tabs'),
+ get activeTab() require('../tabs').activeTab,
+ on: method(on),
+ removeListener: method(off),
+ once: method(once),
+ get isPrivateBrowsing() getMode(windowNS(this).window),
+});
+exports.BrowserWindow = BrowserWindow;
diff --git a/tools/addon-sdk-1.12/lib/sdk/window/namespace.js b/tools/addon-sdk-1.12/lib/sdk/window/namespace.js
new file mode 100644
index 0000000..b486f88
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/window/namespace.js
@@ -0,0 +1,6 @@
+/* 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.windowNS = require('../core/namespace').ns();
diff --git a/tools/addon-sdk-1.12/lib/sdk/window/utils.js b/tools/addon-sdk-1.12/lib/sdk/window/utils.js
new file mode 100644
index 0000000..0bca8b1
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/window/utils.js
@@ -0,0 +1,205 @@
+/* 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 array = require('../util/array');
+
+const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1'].
+ getService(Ci.nsIWindowWatcher);
+const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
+ getService(Ci.nsIAppShellService);
+const observers = require('../deprecated/observer-service');
+const WM = Cc['@mozilla.org/appshell/window-mediator;1'].
+ getService(Ci.nsIWindowMediator);
+
+const BROWSER = 'navigator:browser',
+ URI_BROWSER = 'chrome://browser/content/browser.xul',
+ NAME = '_blank',
+ FEATURES = 'chrome,all,dialog=no';
+
+function getMostRecentBrowserWindow() {
+ return WM.getMostRecentWindow(BROWSER);
+}
+exports.getMostRecentBrowserWindow = getMostRecentBrowserWindow;
+
+/**
+ * Returns the ID of the window's current inner window.
+ */
+function getInnerId(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
+};
+exports.getInnerId = getInnerId;
+
+/**
+ * Returns the ID of the window's outer window.
+ */
+function getOuterId(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
+};
+exports.getOuterId = getOuterId;
+
+/**
+ * Returns `nsIXULWindow` for the given `nsIDOMWindow`.
+ */
+function getXULWindow(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebNavigation).
+ QueryInterface(Ci.nsIDocShellTreeItem).
+ treeOwner.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIXULWindow);
+};
+exports.getXULWindow = getXULWindow;
+
+/**
+ * Returns `nsIBaseWindow` for the given `nsIDOMWindow`.
+ */
+function getBaseWindow(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebNavigation).
+ QueryInterface(Ci.nsIDocShell).
+ QueryInterface(Ci.nsIDocShellTreeItem).
+ treeOwner.
+ QueryInterface(Ci.nsIBaseWindow);
+}
+exports.getBaseWindow = getBaseWindow;
+
+function getWindowDocShell(window) window.gBrowser.docShell;
+exports.getWindowDocShell = getWindowDocShell;
+
+function getWindowLoadingContext(window) {
+ return getWindowDocShell(window).
+ QueryInterface(Ci.nsILoadContext);
+}
+exports.getWindowLoadingContext = getWindowLoadingContext;
+
+/**
+ * Removes given window from the application's window registry. Unless
+ * `options.close` is `false` window is automatically closed on application
+ * quit.
+ * @params {nsIDOMWindow} window
+ * @params {Boolean} options.close
+ */
+function backgroundify(window, options) {
+ let base = getBaseWindow(window);
+ base.visibility = false;
+ base.enabled = false;
+ appShellService.unregisterTopLevelWindow(getXULWindow(window));
+ if (!options || options.close !== false)
+ observers.add('quit-application-granted', window.close.bind(window));
+
+ return window;
+}
+exports.backgroundify = backgroundify;
+
+/**
+ * Takes hash of options and serializes it to a features string that
+ * can be used passed to `window.open`. For more details on features string see:
+ * https://developer.mozilla.org/en/DOM/window.open#Position_and_size_features
+ */
+function serializeFeatures(options) {
+ return Object.keys(options).reduce(function(result, name) {
+ let value = options[name];
+ return result + ',' + name + '=' +
+ (value === true ? 'yes' : value === false ? 'no' : value);
+ }, '').substr(1);
+}
+
+/**
+ * Opens a top level window and returns it's `nsIDOMWindow` representation.
+ * @params {String} uri
+ * URI of the document to be loaded into window.
+ * @params {nsIDOMWindow} options.parent
+ * Used as parent for the created window.
+ * @params {String} options.name
+ * Optional name that is assigned to the window.
+ * @params {Object} options.features
+ * Map of key, values like: `{ width: 10, height: 15, chrome: true }`.
+ */
+function open(uri, options) {
+ options = options || {};
+ return windowWatcher.
+ openWindow(options.parent || null,
+ uri,
+ options.name || null,
+ serializeFeatures(options.features || {}),
+ options.args || null);
+}
+exports.open = open;
+
+/**
+ * Opens a top level window and returns it's `nsIDOMWindow` representation.
+ * Same as `open` but with more features
+ * @param {Object} options
+ *
+ */
+function openDialog(options) {
+ options = options || {};
+
+ let browser = WM.getMostRecentWindow(BROWSER);
+ return browser.openDialog.apply(
+ browser,
+ array.flatten([
+ options.url || URI_BROWSER,
+ options.name || '_blank',
+ options.features || 'chrome,all,dialog=no',
+ options.args || null
+ ])
+ );
+}
+exports.openDialog = openDialog;
+
+/**
+ * Returns an array of all currently opened windows.
+ * Note that these windows may still be loading.
+ */
+function windows() {
+ let list = [];
+ let winEnum = windowWatcher.getWindowEnumerator();
+ while (winEnum.hasMoreElements()) {
+ let window = winEnum.getNext().QueryInterface(Ci.nsIDOMWindow);
+ list.push(window);
+ }
+ return list;
+}
+exports.windows = windows;
+
+/**
+ * Check if the given window is completely loaded.
+ * i.e. if its "load" event has already been fired and all possible DOM content
+ * is done loading (the whole DOM document, images content, ...)
+ * @params {nsIDOMWindow} window
+ */
+function isDocumentLoaded(window) {
+ return window.document.readyState == "complete";
+}
+exports.isDocumentLoaded = isDocumentLoaded;
+
+function isBrowser(window) {
+ return window.document.documentElement.getAttribute("windowtype") === BROWSER;
+};
+exports.isBrowser = isBrowser;
+
+function getWindowTitle(window) {
+ return window && window.document ? window.document.title : null;
+}
+exports.getWindowTitle = getWindowTitle;
+
+function isXULBrowser(window) {
+ return !!(isBrowser(window) && window.XULBrowserWindow);
+}
+exports.isXULBrowser = isXULBrowser;
+
+function getFrames(window) {
+ return Array.slice(window.frames).reduce(function(frames, frame) {
+ return frames.concat(frame, getFrames(frame))
+ }, [])
+}
+exports.getFrames = getFrames;
diff --git a/tools/addon-sdk-1.12/lib/sdk/windows.js b/tools/addon-sdk-1.12/lib/sdk/windows.js
new file mode 100644
index 0000000..15399c9
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/windows.js
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+module.metadata = {
+ 'stability': 'stable'
+};
+
+if (require('./system/xul-app').is('Firefox')) {
+ module.exports = require('./windows/firefox');
+}
+else if (require('./system/xul-app').is('Fennec')) {
+ module.exports = require('./windows/fennec');
+}
+else {
+ throw new Error([
+ 'The windows module currently supports only Firefox & Fennec. In the future',
+ ' we would like it to support other applications, however.'
+ ].join(''));
+}
diff --git a/tools/addon-sdk-1.12/lib/sdk/windows/dom.js b/tools/addon-sdk-1.12/lib/sdk/windows/dom.js
new file mode 100644
index 0000000..ac2b86b
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/windows/dom.js
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+const { Trait } = require('../deprecated/traits');
+const { getWindowTitle } = require('../window/utils');
+const { getMode } = require('../private-browsing/utils');
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const WindowDom = Trait.compose({
+ _window: Trait.required,
+ get title() {
+ return getWindowTitle(this._window);
+ },
+ close: function close() {
+ let window = this._window;
+ if (window) window.close();
+ return this._public;
+ },
+ activate: function activate() {
+ let window = this._window;
+ if (window) window.focus();
+ return this._public;
+ },
+ get isPrivateBrowsing() {
+ return getMode(this._window);
+ }
+});
+exports.WindowDom = WindowDom;
diff --git a/tools/addon-sdk-1.12/lib/sdk/windows/fennec.js b/tools/addon-sdk-1.12/lib/sdk/windows/fennec.js
new file mode 100644
index 0000000..a497ea9
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/windows/fennec.js
@@ -0,0 +1,84 @@
+/* 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 { Class } = require('../core/heritage');
+const { BrowserWindow } = require('../window/browser');
+const windowUtils = require('../deprecated/window-utils');
+const { WindowTracker } = windowUtils;
+const { isBrowser } = require('../window/utils');
+const { windowNS } = require('../window/namespace');
+const { on, off, once, emit } = require('../event/core');
+const { method } = require('../lang/functional');
+const { EventTarget } = require('../event/target');
+const { List, addListItem } = require('../util/list');
+
+const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec, consider using require("tabs") instead';
+
+// NOTE: On Fennec there is only one window.
+
+let BrowserWindows = Class({
+ implements: [ List ],
+ extends: EventTarget,
+ initialize: function() {
+ List.prototype.initialize.apply(this);
+ },
+ get activeWindow() {
+ let window = windowUtils.activeBrowserWindow;
+ return window ? getBrowserWindow({window: window}) : null;
+ },
+ open: function open(options) {
+ throw new Error(ERR_FENNEC_MSG);
+ return null;
+ }
+});
+const browserWindows = exports.browserWindows = BrowserWindows();
+
+
+/**
+ * Gets a `BrowserWindow` for the given `chromeWindow` if previously
+ * registered, `null` otherwise.
+ */
+function getRegisteredWindow(chromeWindow) {
+ for each (let window in browserWindows) {
+ if (chromeWindow === windowNS(window).window)
+ return window;
+ }
+
+ return null;
+}
+
+/**
+ * Gets a `BrowserWindow` for the provided window options obj
+ * @params {Object} options
+ * Options that are passed to the the `BrowserWindowTrait`
+ * @returns {BrowserWindow}
+ */
+function getBrowserWindow(options) {
+ let window = null;
+
+ // if we have a BrowserWindow already then use it
+ if ('window' in options)
+ window = getRegisteredWindow(options.window);
+ if (window)
+ return window;
+
+ // we don't have a BrowserWindow yet, so create one
+ var window = BrowserWindow(options);
+ addListItem(browserWindows, window);
+ return window;
+}
+
+WindowTracker({
+ onTrack: function onTrack(chromeWindow) {
+ if (!isBrowser(chromeWindow)) return;
+ let window = getBrowserWindow({ window: chromeWindow });
+ emit(browserWindows, 'open', window);
+ },
+ onUntrack: function onUntrack(chromeWindow) {
+ if (!isBrowser(chromeWindow)) return;
+ let window = getBrowserWindow({ window: chromeWindow });
+ emit(browserWindows, 'close', window);
+ }
+});
diff --git a/tools/addon-sdk-1.12/lib/sdk/windows/firefox.js b/tools/addon-sdk-1.12/lib/sdk/windows/firefox.js
new file mode 100644
index 0000000..9a55a08
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/windows/firefox.js
@@ -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';
+
+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 } = 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'),
+ { isWindowPBEnabled } = require('../private-browsing/utils');
+
+/**
+ * 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,
+ /**
+ * 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);
+
+ // make sure we don't have unhandled errors
+ this.on('error', console.exception.bind(console));
+
+ if ('onOpen' in options)
+ this.on('open', options.onOpen);
+ if ('onClose' in options)
+ this.on('close', options.onClose);
+ if ('onActivate' in options)
+ this.on('activate', options.onActivate);
+ if ('onDeactivate' in options)
+ this.on('deactivate', options.onDeactivate);
+ if ('window' in options)
+ this._window = options.window;
+
+ if ('tabs' in options) {
+ this._tabOptions = Array.isArray(options.tabs) ?
+ options.tabs.map(Options) :
+ [ Options(options.tabs) ];
+ }
+ else if ('url' in options) {
+ this._tabOptions = [ Options(options.url) ];
+ }
+
+ this._load();
+ return this;
+ },
+ destroy: function () this._onUnload(),
+ _tabOptions: [],
+ _onLoad: function() {
+ try {
+ this._initWindowTabTracker();
+ }
+ catch(e) {
+ this._emit('error', e);
+ }
+
+ this._emitOnObject(browserWindows, 'open', this._public);
+ },
+ _onUnload: function() {
+ if (!this._window)
+ return;
+ this._destroyWindowTabTracker();
+ this._emitOnObject(browserWindows, 'close', this._public);
+ this._window = null;
+ // Removing reference from the windows array.
+ windows.splice(windows.indexOf(this), 1);
+ this._removeAllListeners();
+ },
+ close: function close(callback) {
+ // maybe we should deprecate this with message ?
+ if (callback) this.on('close', callback);
+ return this._close();
+ }
+ })
+);
+
+/**
+ * Gets a `BrowserWindowTrait` for the given `chromeWindow` if previously
+ * registered, `null` otherwise.
+ */
+function getRegisteredWindow(chromeWindow) {
+ for each (let window in windows) {
+ if (chromeWindow === window._window)
+ return window;
+ }
+
+ return null;
+}
+
+/**
+ * Wrapper for `BrowserWindowTrait`. Creates new instance if wrapper for
+ * window doesn't exists yet. If wrapper already exists then returns it
+ * instead.
+ * @params {Object} options
+ * Options that are passed to the the `BrowserWindowTrait`
+ * @returns {BrowserWindow}
+ * @see BrowserWindowTrait
+ */
+function BrowserWindow(options) {
+ let window = null;
+
+ if ("window" in options)
+ window = getRegisteredWindow(options.window);
+
+ return (window || BrowserWindowTrait(options))._public;
+}
+// to have proper `instanceof` behavior will go away when #596248 is fixed.
+BrowserWindow.prototype = BrowserWindowTrait.prototype;
+exports.BrowserWindow = BrowserWindow;
+
+const windows = [];
+
+const browser = ns();
+
+function onWindowActivation (chromeWindow, event) {
+ if (!isBrowser(chromeWindow)) return; // Ignore if it's not a browser window.
+
+ let window = getRegisteredWindow(chromeWindow);
+
+ if (window)
+ window._emit(event.type, window._public);
+ else
+ window = BrowserWindowTrait({ window: chromeWindow });
+
+ browser(browserWindows).internals._emit(event.type, window._public);
+}
+
+windowObserver.on("activate", onWindowActivation);
+windowObserver.on("deactivate", onWindowActivation);
+
+/**
+ * `BrowserWindows` trait is composed out of `List` trait and it represents
+ * "live" list of currently open browser windows. Instance mutates itself
+ * whenever new browser window gets opened / closed.
+ */
+// Very stupid to resolve all `toStrings` but this will be fixed by #596248
+const browserWindows = Trait.resolve({ toString: null }).compose(
+ List.resolve({ constructor: '_initList' }),
+ EventEmitter.resolve({ toString: null }),
+ WindowTrackerTrait.resolve({ constructor: '_initTracker', toString: null }),
+ Trait.compose({
+ _emit: Trait.required,
+ _add: Trait.required,
+ _remove: Trait.required,
+
+ // public API
+
+ /**
+ * Constructor creates instance of `Windows` that represents live list of open
+ * windows.
+ */
+ constructor: function BrowserWindows() {
+ browser(this._public).internals = this;
+
+ this._trackedWindows = [];
+ this._initList();
+ this._initTracker();
+ unload.ensure(this, "_destructor");
+ },
+ _destructor: function _destructor() {
+ this._removeAllListeners('open');
+ this._removeAllListeners('close');
+ this._removeAllListeners('activate');
+ this._removeAllListeners('deactivate');
+ this._clear();
+
+ delete browser(this._public).internals;
+ },
+ /**
+ * This property represents currently active window.
+ * Property is non-enumerable, in order to preserve array like enumeration.
+ * @type {Window|null}
+ */
+ get activeWindow() {
+ let window = windowUtils.activeBrowserWindow;
+ return window ? BrowserWindow({window: window}) : null;
+ },
+ open: function open(options) {
+ if (typeof options === "string")
+ // `tabs` option is under review and may be removed.
+ options = { tabs: [Options(options)] };
+ return BrowserWindow(options);
+ },
+
+ /**
+ * Internal listener which is called whenever new window gets open.
+ * Creates wrapper and adds to this list.
+ * @param {nsIWindow} chromeWindow
+ */
+ _onTrack: function _onTrack(chromeWindow) {
+ if (!isBrowser(chromeWindow)) return;
+ let window = BrowserWindow({ window: chromeWindow });
+ this._add(window);
+ this._emit('open', window);
+ },
+ /**
+ * Internal listener which is called whenever window gets closed.
+ * Cleans up references and removes wrapper from this list.
+ * @param {nsIWindow} window
+ */
+ _onUntrack: function _onUntrack(chromeWindow) {
+ if (!isBrowser(chromeWindow)) return;
+ let window = BrowserWindow({ window: chromeWindow });
+ this._remove(window);
+ this._emit('close', window);
+
+ // Bug 724404: do not leak this module and linked windows:
+ // We have to do it on untrack and not only when `_onUnload` is called
+ // when windows are closed, otherwise, we will leak on addon disabling.
+ window.destroy();
+ }
+ }).resolve({ toString: null })
+)();
+
+exports.browserWindows = browserWindows;
diff --git a/tools/addon-sdk-1.12/lib/sdk/windows/loader.js b/tools/addon-sdk-1.12/lib/sdk/windows/loader.js
new file mode 100644
index 0000000..be3d84c
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/windows/loader.js
@@ -0,0 +1,120 @@
+/* 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({
+ 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) {
+ _window.removeEventListener(ON_UNLOAD, this.__unloadListener, false);
+ _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)
+ }
+ }
+ }
+ },
+ __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;
+
diff --git a/tools/addon-sdk-1.12/lib/sdk/windows/observer.js b/tools/addon-sdk-1.12/lib/sdk/windows/observer.js
new file mode 100644
index 0000000..1097bf3
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/windows/observer.js
@@ -0,0 +1,52 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 { EventEmitterTrait: EventEmitter } = require("../deprecated/events");
+const { WindowTracker, windowIterator } = require("../deprecated/window-utils");
+const { DOMEventAssembler } = require("../deprecated/events/assembler");
+const { Trait } = require("../deprecated/light-traits");
+
+// 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,
+ /**
+ * 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);
+ }
+});
+
+// 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;
diff --git a/tools/addon-sdk-1.12/lib/sdk/windows/tabs-fennec.js b/tools/addon-sdk-1.12/lib/sdk/windows/tabs-fennec.js
new file mode 100644
index 0000000..ddf4aeb
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/windows/tabs-fennec.js
@@ -0,0 +1,190 @@
+/* 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 { Class } = require('../core/heritage');
+const { Tab } = require('../tabs/tab');
+const { browserWindows } = require('./fennec');
+const { windowNS } = require('../window/namespace');
+const { tabsNS, tabNS } = require('../tabs/namespace');
+const { openTab, getTabs, getSelectedTab } = require('../tabs/utils');
+const { Options } = require('../tabs/common');
+const { on, once, off, emit } = require('../event/core');
+const { method } = require('../lang/functional');
+const { EVENTS } = require('../tabs/events');
+const { EventTarget } = require('../event/target');
+const { when: unload } = require('../system/unload');
+const { windowIterator } = require('../deprecated/window-utils');
+const { List, addListItem, removeListItem } = require('../util/list');
+
+const mainWindow = windowNS(browserWindows.activeWindow).window;
+
+const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec';
+
+const Tabs = Class({
+ implements: [ List ],
+ extends: EventTarget,
+ initialize: function initialize(options) {
+ let tabsInternals = tabsNS(this);
+ let window = tabsNS(this).window = options.window || mainWindow;
+
+ EventTarget.prototype.initialize.call(this, options);
+ List.prototype.initialize.apply(this, getTabs(window).map(Tab));
+
+ // TabOpen event
+ window.BrowserApp.deck.addEventListener(EVENTS.open.dom, onTabOpen, false);
+
+ // TabSelect
+ window.BrowserApp.deck.addEventListener(EVENTS.activate.dom, onTabSelect, false);
+
+ // TabClose
+ window.BrowserApp.deck.addEventListener(EVENTS.close.dom, onTabClose, false);
+ },
+ get activeTab() {
+ return getTabForRawTab(getSelectedTab(tabsNS(this).window));
+ },
+ open: function(options) {
+ options = Options(options);
+ let activeWin = browserWindows.activeWindow;
+
+ if (options.isPinned) {
+ console.error(ERR_FENNEC_MSG); // TODO
+ }
+
+ let rawTab = openTab(windowNS(activeWin).window, options.url, {
+ inBackground: options.inBackground
+ });
+
+ // by now the tab has been created
+ let tab = getTabForRawTab(rawTab);
+
+ if (options.onClose)
+ tab.on('close', options.onClose);
+
+ if (options.onOpen) {
+ // NOTE: on Fennec this will be true
+ if (tabNS(tab).opened)
+ options.onOpen(tab);
+
+ tab.on('open', options.onOpen);
+ }
+
+ if (options.onReady)
+ tab.on('ready', options.onReady);
+
+ if (options.onActivate)
+ tab.on('activate', options.onActivate);
+
+ return tab;
+ }
+});
+let gTabs = exports.tabs = Tabs(mainWindow);
+
+function tabsUnloader(event, window) {
+ window = window || (event && event.target);
+ if (!(window && window.BrowserApp))
+ return;
+ window.BrowserApp.deck.removeEventListener(EVENTS.open.dom, onTabOpen, false);
+ window.BrowserApp.deck.removeEventListener(EVENTS.activate.dom, onTabSelect, false);
+ window.BrowserApp.deck.removeEventListener(EVENTS.close.dom, onTabClose, false);
+}
+
+// unload handler
+unload(function() {
+ for (let window in windowIterator()) {
+ tabsUnloader(null, window);
+ }
+});
+
+function addTab(tab) {
+ addListItem(gTabs, tab);
+ return tab;
+}
+
+function removeTab(tab) {
+ removeListItem(gTabs, tab);
+ return tab;
+}
+
+function getTabForBrowser(browser) {
+ return getTabForRawTab(getRawTabForBrowser(browser));
+}
+
+function getRawTabForBrowser(browser) {
+ let tabs = mainWindow.BrowserApp.tabs;
+ for (let i = 0; i < tabs.length; i++) {
+ let tab = tabs[i];
+ if (tab.browser === browser)
+ return tab
+ }
+ return null;
+}
+
+// TabOpen
+function onTabOpen(event) {
+ let browser = event.target;
+
+ let tab = getTabForBrowser(browser);
+ if (tab === null) {
+ let rawTab = getRawTabForBrowser(browser);
+
+ // create a Tab instance for this new tab
+ tab = addTab(Tab(rawTab));
+ }
+
+ tabNS(tab).opened = true;
+
+ // TabReady
+ let onReady = tabNS(tab).onReady = onTabReady.bind(tab);
+ browser.addEventListener(EVENTS.ready.dom, onReady, false);
+
+ emit(tab, 'open', tab);
+ emit(gTabs, 'open', tab);
+};
+
+function onTabReady() {
+ emit(this, 'ready', this);
+ emit(gTabs, 'ready', this);
+}
+
+// TabSelect
+function onTabSelect(event) {
+ // Set value whenever new tab becomes active.
+ let tab = getTabForBrowser(event.target);
+ emit(tab, 'activate', tab);
+ emit(gTabs, 'activate', tab);
+
+ for each (let t in gTabs) {
+ if (t === tab) continue;
+ emit(t, 'deactivate', t);
+ emit(gTabs, 'deactivate', t);
+ }
+};
+
+// TabClose
+function onTabClose(event) {
+ let tab = getTabForBrowser(event.target);
+ removeTab(tab);
+
+ emit(gTabs, 'close', tab);
+ emit(tab, 'close', tab);
+};
+
+function getTabForRawTab(rawTab) {
+ for each (let tab in gTabs) {
+ if (tabNS(tab).tab === rawTab)
+ return tab;
+ }
+ return null;
+}
+
+unload(function() {
+ for each (let tab in gTabs) {
+ let tabInternals = tabNS(tab);
+ tabInternals.tab.browser.removeEventListener(EVENTS.ready.dom, tabInternals.onReady, false);
+ tabInternals.onReady = null;
+ tabInternals.tab = null;
+ tabInternals.window = null;
+ }
+});
diff --git a/tools/addon-sdk-1.12/lib/sdk/windows/tabs-firefox.js b/tools/addon-sdk-1.12/lib/sdk/windows/tabs-firefox.js
new file mode 100644
index 0000000..f41e752
--- /dev/null
+++ b/tools/addon-sdk-1.12/lib/sdk/windows/tabs-firefox.js
@@ -0,0 +1,188 @@
+/* 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 { Trait } = require("../deprecated/traits");
+const { List } = require("../deprecated/list");
+const { Tab } = require("../tabs/tab");
+const { EventEmitter } = require("../deprecated/events");
+const { EVENTS } = require("../tabs/events");
+const { getOwnerWindow, getActiveTab, getTabs,
+ openTab, activateTab } = require("../tabs/utils");
+const { Options } = require("../tabs/common");
+const { observer: tabsObserver } = require("../tabs/observer");
+
+const TAB_BROWSER = "tabbrowser";
+
+/**
+ * This is a trait that is used in composition of window wrapper. Trait tracks
+ * tab related events of the wrapped window in order to keep track of open
+ * tabs and maintain their wrappers. Every new tab gets wrapped and jetpack
+ * type event is emitted.
+ */
+const WindowTabTracker = Trait.compose({
+ /**
+ * Chrome window whose tabs are tracked.
+ */
+ _window: Trait.required,
+ /**
+ * Function used to emit events.
+ */
+ _emit: EventEmitter.required,
+ _tabOptions: Trait.required,
+ /**
+ * Function to add event listeners.
+ */
+ on: EventEmitter.required,
+ removeListener: EventEmitter.required,
+ /**
+ * Initializes tab tracker for a browser window.
+ */
+ _initWindowTabTracker: function _initWindowTabTracker() {
+ // Ugly hack that we have to remove at some point (see Bug 658059). At this
+ // point it is necessary to invoke lazy `tabs` getter on the windows object
+ // which creates a `TabList` instance.
+ this.tabs;
+
+ // Binding all methods used as event listeners to the instance.
+ this._onTabReady = this._emitEvent.bind(this, "ready");
+ this._onTabOpen = this._onTabEvent.bind(this, "open");
+ this._onTabClose = this._onTabEvent.bind(this, "close");
+ this._onTabActivate = this._onTabEvent.bind(this, "activate");
+ this._onTabDeactivate = this._onTabEvent.bind(this, "deactivate");
+ this._onTabPinned = this._onTabEvent.bind(this, "pinned");
+ this._onTabUnpinned = this._onTabEvent.bind(this, "unpinned");
+
+ for each (let tab in getTabs(this._window)) {
+ // We emulate "open" events for all open tabs since gecko does not emits
+ // them on the tabs that new windows are open with. Also this is
+ // necessary to synchronize tabs lists with an actual state.
+ this._onTabOpen(tab);
+ }
+
+ // We also emulate "activate" event so that it's picked up by a tab list.
+ this._onTabActivate(getActiveTab(this._window));
+
+ // Setting up event listeners
+ tabsObserver.on("open", this._onTabOpen);
+ tabsObserver.on("close", this._onTabClose);
+ tabsObserver.on("activate", this._onTabActivate);
+ tabsObserver.on("deactivate", this._onTabDeactivate);
+ tabsObserver.on("pinned", this._onTabPinned);
+ tabsObserver.on("unpinned", this._onTabUnpinned);
+ },
+ _destroyWindowTabTracker: function _destroyWindowTabTracker() {
+ // We emulate close events on all tabs, since gecko does not emits such
+ // events by itself.
+ for each (let tab in this.tabs)
+ this._emitEvent("close", tab);
+
+ this._tabs._clear();
+
+ tabsObserver.removeListener("open", this._onTabOpen);
+ tabsObserver.removeListener("close", this._onTabClose);
+ tabsObserver.removeListener("activate", this._onTabActivate);
+ tabsObserver.removeListener("deactivate", this._onTabDeactivate);
+ },
+ _onTabEvent: function _onTabEvent(type, tab) {
+ if (this._window === getOwnerWindow(tab)) {
+ let options = this._tabOptions.shift() || {};
+ options.tab = tab;
+ options.window = this._public;
+
+ // creating tab wrapper and adding listener to "ready" events.
+ let wrappedTab = Tab(options);
+
+ // Setting up an event listener for ready events.
+ if (type === "open")
+ wrappedTab.on("ready", this._onTabReady);
+
+ this._emitEvent(type, wrappedTab);
+ }
+ },
+ _emitEvent: function _emitEvent(type, tab) {
+ // Notifies combined tab list that tab was added / removed.
+ tabs._emit(type, tab);
+ // Notifies contained tab list that window was added / removed.
+ this._tabs._emit(type, tab);
+ }
+});
+exports.WindowTabTracker = WindowTabTracker;
+
+/**
+ * This trait is used to create live representation of open tab lists. Each
+ * window wrapper's tab list is represented by an object created from this
+ * trait. It is also used to represent list of all the open windows. Trait is
+ * composed out of `EventEmitter` in order to emit 'TabOpen', 'TabClose' events.
+ * **Please note** that objects created by this trait can't be exposed outside
+ * instead you should expose it's `_public` property, see comments in
+ * constructor for details.
+ */
+const TabList = List.resolve({ constructor: "_init" }).compose(
+ // This is ugly, but necessary. Will be removed by #596248
+ EventEmitter.resolve({ toString: null }),
+ Trait.compose({
+ on: Trait.required,
+ _emit: Trait.required,
+ constructor: function TabList(options) {
+ this._window = options.window;
+ // Add new items to the list
+ this.on(EVENTS.open.name, this._add.bind(this));
+
+ // Remove closed items from the list
+ this.on(EVENTS.close.name, this._remove.bind(this));
+
+ // Set value whenever new tab becomes active.
+ this.on("activate", function onTabActivate(tab) {
+ this._activeTab = tab;
+ }.bind(this));
+
+ // Initialize list.
+ this._init();
+ // This list is not going to emit any events, object holding this list
+ // will do it instead, to make that possible we return a private API.
+ return this;
+ },
+ get activeTab() this._activeTab,
+ _activeTab: null,
+
+ open: function open(options) {
+ options = Options(options);
+ this._window._tabOptions.push(options);
+ let tab = openTab(this._window._window, options.url);
+ if (!options.inBackground)
+ activateTab(tab);
+ }
+ // This is ugly, but necessary. Will be removed by #596248
+ }).resolve({ toString: null })
+);
+
+/**
+ * Combined list of all open tabs on all the windows.
+ * type {TabList}
+ */
+var tabs = TabList({ window: null });
+exports.tabs = tabs._public;
+
+/**
+ * Trait is a part of composition that represents window wrapper. This trait is
+ * composed out of `WindowTabTracker` that allows it to keep track of open tabs
+ * on the window being wrapped.
+ */
+const WindowTabs = Trait.compose(
+ WindowTabTracker,
+ Trait.compose({
+ _window: Trait.required,
+ /**
+ * List of tabs
+ */
+ get tabs() (this._tabs || (this._tabs = TabList({ window: this })))._public,
+ _tabs: null,
+ })
+);
+exports.WindowTabs = WindowTabs;