summaryrefslogtreecommitdiffstats
path: root/toolkit/jetpack/sdk/addon/bootstrap.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/jetpack/sdk/addon/bootstrap.js')
-rw-r--r--toolkit/jetpack/sdk/addon/bootstrap.js182
1 files changed, 182 insertions, 0 deletions
diff --git a/toolkit/jetpack/sdk/addon/bootstrap.js b/toolkit/jetpack/sdk/addon/bootstrap.js
new file mode 100644
index 000000000..0397d91e5
--- /dev/null
+++ b/toolkit/jetpack/sdk/addon/bootstrap.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";
+
+const { Cu } = require("chrome");
+const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
+const { Task: { spawn } } = require("resource://gre/modules/Task.jsm");
+const { readURI } = require("sdk/net/url");
+const { mount, unmount } = require("sdk/uri/resource");
+const { setTimeout } = require("sdk/timers");
+const { Loader, Require, Module, main, unload } = require("toolkit/loader");
+const prefs = require("sdk/preferences/service");
+
+// load below now, so that it can be used by sdk/addon/runner
+// see bug https://bugzilla.mozilla.org/show_bug.cgi?id=1042239
+const Startup = Cu.import("resource://gre/modules/sdk/system/Startup.js", {});
+
+const REASON = [ "unknown", "startup", "shutdown", "enable", "disable",
+ "install", "uninstall", "upgrade", "downgrade" ];
+
+const UUID_PATTERN = /^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/;
+// Takes add-on ID and normalizes it to a domain name so that add-on
+// can be mapped to resource://domain/
+const readDomain = id =>
+ // If only `@` character is the first one, than just substract it,
+ // otherwise fallback to legacy normalization code path. Note: `.`
+ // is valid character for resource substitutaiton & we intend to
+ // make add-on URIs intuitive, so it's best to just stick to an
+ // add-on author typed input.
+ id.lastIndexOf("@") === 0 ? id.substr(1).toLowerCase() :
+ id.toLowerCase().
+ replace(/@/g, "-at-").
+ replace(/\./g, "-dot-").
+ replace(UUID_PATTERN, "$1");
+
+const readPaths = id => {
+ const base = `extensions.modules.${id}.path.`;
+ const domain = readDomain(id);
+ return prefs.keys(base).reduce((paths, key) => {
+ const value = prefs.get(key);
+ const name = key.replace(base, "");
+ const path = name.split(".").join("/");
+ const prefix = path.length ? `${path}/` : path;
+ const uri = value.endsWith("/") ? value : `${value}/`;
+ const root = `extensions.modules.${domain}.commonjs.path.${name}`;
+
+ mount(root, uri);
+
+ paths[prefix] = `resource://${root}/`;
+ return paths;
+ }, {});
+};
+
+const Bootstrap = function(mountURI) {
+ this.mountURI = mountURI;
+ this.install = this.install.bind(this);
+ this.uninstall = this.uninstall.bind(this);
+ this.startup = this.startup.bind(this);
+ this.shutdown = this.shutdown.bind(this);
+};
+Bootstrap.prototype = {
+ constructor: Bootstrap,
+ mount(domain, rootURI) {
+ mount(domain, rootURI);
+ this.domain = domain;
+ },
+ unmount() {
+ if (this.domain) {
+ unmount(this.domain);
+ this.domain = null;
+ }
+ },
+ install(addon, reason) {
+ return new Promise(resolve => resolve());
+ },
+ uninstall(addon, reason) {
+ return new Promise(resolve => {
+ const {id} = addon;
+
+ prefs.reset(`extensions.${id}.sdk.domain`);
+ prefs.reset(`extensions.${id}.sdk.version`);
+ prefs.reset(`extensions.${id}.sdk.rootURI`);
+ prefs.reset(`extensions.${id}.sdk.baseURI`);
+ prefs.reset(`extensions.${id}.sdk.load.reason`);
+
+ resolve();
+ });
+ },
+ startup(addon, reasonCode) {
+ const { id, version, resourceURI: { spec: addonURI } } = addon;
+ const rootURI = this.mountURI || addonURI;
+ const reason = REASON[reasonCode];
+ const self = this;
+
+ return spawn(function*() {
+ const metadata = JSON.parse(yield readURI(`${rootURI}package.json`));
+ const domain = readDomain(id);
+ const baseURI = `resource://${domain}/`;
+
+ this.mount(domain, rootURI);
+
+ prefs.set(`extensions.${id}.sdk.domain`, domain);
+ prefs.set(`extensions.${id}.sdk.version`, version);
+ prefs.set(`extensions.${id}.sdk.rootURI`, rootURI);
+ prefs.set(`extensions.${id}.sdk.baseURI`, baseURI);
+ prefs.set(`extensions.${id}.sdk.load.reason`, reason);
+
+ const command = prefs.get(`extensions.${id}.sdk.load.command`);
+
+ const loader = Loader({
+ id,
+ isNative: true,
+ checkCompatibility: true,
+ prefixURI: baseURI,
+ rootURI: baseURI,
+ name: metadata.name,
+ paths: Object.assign({
+ "": "resource://gre/modules/commonjs/",
+ "devtools/": "resource://devtools/",
+ "./": baseURI
+ }, readPaths(id)),
+ manifest: metadata,
+ metadata: metadata,
+ modules: {
+ "@test/options": {},
+ },
+ noQuit: prefs.get(`extensions.${id}.sdk.test.no-quit`, false)
+ });
+ self.loader = loader;
+
+ const module = Module("package.json", `${baseURI}package.json`);
+ const require = Require(loader, module);
+ const main = command === "test" ? "sdk/test/runner" : null;
+ const prefsURI = `${baseURI}defaults/preferences/prefs.js`;
+
+ // Init the 'sdk/webextension' module from the bootstrap addon parameter.
+ require("sdk/webextension").initFromBootstrapAddonParam(addon);
+
+ const { startup } = require("sdk/addon/runner");
+ startup(reason, {loader, main, prefsURI});
+ }.bind(this)).catch(error => {
+ console.error(`Failed to start ${id} addon`, error);
+ throw error;
+ });
+ },
+ shutdown(addon, code) {
+ this.unmount();
+ return this.unload(REASON[code]);
+ },
+ unload(reason) {
+ return new Promise(resolve => {
+ const { loader } = this;
+ if (loader) {
+ this.loader = null;
+ unload(loader, reason);
+
+ setTimeout(() => {
+ for (let uri of Object.keys(loader.sandboxes)) {
+ let sandbox = loader.sandboxes[uri];
+ if (Cu.getClassName(sandbox, true) == "Sandbox")
+ Cu.nukeSandbox(sandbox);
+ delete loader.sandboxes[uri];
+ delete loader.modules[uri];
+ }
+
+ try {
+ Cu.nukeSandbox(loader.sharedGlobalSandbox);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+
+ resolve();
+ }, 1000);
+ }
+ else {
+ resolve();
+ }
+ });
+ }
+};
+exports.Bootstrap = Bootstrap;