diff options
Diffstat (limited to 'toolkit/jetpack/sdk/addon/bootstrap.js')
-rw-r--r-- | toolkit/jetpack/sdk/addon/bootstrap.js | 182 |
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; |