/* 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/. */ var EXPORTED_SYMBOLS = [ 'Loader' ]; !function(exports) { "use strict"; const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu, results: Cr, manager: Cm } = Components; const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')(); const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1']. getService(Ci.mozIJSSubScriptLoader); const Sandbox = { new: function ({ principal, prototype, name, existingSandbox }) { let options = { sandboxPrototype: prototype || Sandbox.prototype, wantXrays: Sandbox.wantXrays }; if (name) options.sandboxName = name; if (existingSandbox) options.sameGroupAs = existingSandbox.sandbox; let sandbox = Object.create(Sandbox, { sandbox: { value: Cu.Sandbox(principal || Sandbox.principal, options) } }); // There are few properties (dump, Iterator) that by default appear in // sandboxes shadowing properties provided by a prototype. To workaround // this we override all such properties by copying them directly to the // sandbox. Object.keys(prototype).forEach(function onEach(key) { if (sandbox.sandbox[key] !== prototype[key]) sandbox.sandbox[key] = prototype[key]; }); return sandbox; }, evaluate: function evaluate(source, uri, lineNumber) { return Cu.evalInSandbox( source, this.sandbox, this.version, uri, lineNumber || this.lineNumber ); }, load: function load(uri) { scriptLoader.loadSubScript(uri, this.sandbox, 'UTF-8'); }, merge: function merge(properties) { Object.getOwnPropertyNames(properties).forEach(function(name) { Object.defineProperty(this.sandbox, name, Object.getOwnPropertyDescriptor(properties, name)); }, this); }, principal: systemPrincipal, version: '1.8', lineNumber: 1, wantXrays: false, prototype: {} }; // the Module object made available to CommonJS modules when they are // evaluated, along with 'exports' and 'uri' const Module = { new: function(id, path, uri) { let module = Object.create(this); module.id = id; module.path = path; module.uri = uri; module.exports = {}; return module; }, // TODO: I'd like to remove this, it's not used adds complexity and does // not has much adoption in commonjs either. setExports: function setExports(exports) { this.exports = exports; } }; const Loader = { new: function (options) { let loader = Object.create(Loader, { // Manifest generated by a linker, containing map of module url's mapped // to it's requirements, comes from harness-options.json manifest: { value: options.manifest || {} }, // Following property may be passed in (usually for mocking purposes) in // order to override default modules cache. modules: { value: options.modules || Object.create(Loader.modules) }, globals: { value: options.globals || {} }, uriPrefix: { value: options.uriPrefix }, sandboxes: { value: {} } }); loader.require = this.require.bind(loader, options.loader); // some 'magic' modules, that have no corresponding .js file loader.modules['@packaging'] = Object.freeze({ id: '@packaging', exports: JSON.parse(JSON.stringify(options)) }); loader.modules['@loader'] = Object.freeze({ exports: Object.freeze({ Loader: Loader }), id: '@loader' }); // This special module defines globals which will be added to every // module this loader creates let globals = loader.require('api-utils/globals!'); Object.getOwnPropertyNames(globals).forEach(function(name) { Object.defineProperty(loader.globals, name, Object.getOwnPropertyDescriptor(globals, name)); }); // Freeze globals so that modules won't have a chance to mutate scope of // other modules. Object.freeze(globals); // Override global `dump` so that it behaves same as in any other module ( // currently we override dump to write to a file instead of `stdout` so that // python can read it on windows). dump = globals.dump; return Object.freeze(loader); }, modules: { 'chrome': Object.freeze({ exports: Object.freeze({ Cc: Cc, CC: CC, Ci: Ci, Cu: Cu, Cr: Cr, Cm: Cm, components: Components, messageManager: 'addMessageListener' in exports ? exports : null }), id: 'chrome' }), 'self': function self(loader, requirer) { return loader.require('api-utils/self!').create(requirer.path); }, }, // populate a Module by evaluating the CommonJS module code in the sandbox load: function load(module) { let require = Loader.require.bind(this, module.path); require.main = this.main; // Get an existing module sandbox, if any, so we can reuse its compartment // when creating the new one to reduce memory consumption. let existingSandbox = [this.sandboxes[p] for (p in this.sandboxes)][0]; // XXX Always set "principal" to work around bug 705795, which generates // 'reference to undefined property "principal"' warnings when the argument // is deconstructed in the "new" function's parameter list. // FIXME: stop setting "principal" once bug 705795 is fixed. let sandbox = this.sandboxes[module.path] = Sandbox.new({ principal: null, prototype: this.globals, name: module.uri, existingSandbox: existingSandbox }); sandbox.merge({ require: require, module: module, exports: module.exports }); sandbox.load(module.uri); // Workaround for bug 674195. Freezing objects from other sandboxes fail, // so we create descendant and freeze it instead. if (typeof(module.exports) === 'object') { module.exports = Object.prototype.isPrototypeOf(module.exports) ? Object.freeze(module.exports) : Object.freeze(Object.create(module.exports)); } }, // this require() is the main entry point for regular CommonJS modules. The // bind() in load (above) causes those modules to get a very restricted // form of this require(): one which can only ever reference this one // loader, and which always uses their URI as a "base" (so they're limited // to their own manifest entries, and can't import anything off the // manifest). require: function require(base, id) { let module, manifest = this.manifest[base], requirer = this.modules[base]; if (!id) throw Error("you must provide a module name when calling require() from " + (requirer && requirer.id), base, id); // If we have a manifest for requirer, then all it's requirements have been // registered by linker and we should have a `path` to the required module. // Even pseudo-modules like 'chrome', 'self', '@packaging', and '@loader' // have pseudo-paths: exactly those same names. // details see: Bug-697422. let requirement = manifest && manifest.requirements[id]; if (!requirement) throw Error("Module: " + (requirer && requirer.id) + ' located at ' + base + " has no authority to load: " + id); let path = requirement.path; if (path in this.modules) { module = this.modules[path]; } else { let uri = this.uriPrefix + path; module = this.modules[path] = Module.new(id, path, uri); this.load(module); Object.freeze(module); } // "magic" modules which have contents that depend upon who imports them // (like "self") are expressed in the Loader's pre-populated 'modules' // table as callable functions, which are given the reference to this // Loader and a copy of the importer's URI // // TODO: Find a better way to implement `self`. // Maybe something like require('self!path/to/data') if (typeof(module) === 'function') module = module(this, requirer); return module.exports; }, // process.process() will eventually cause a call to main() to be evaluated // in the addon's context. This function loads and executes the addon's // entry point module. main: function main(id, path) { try { let uri = this.uriPrefix + path; let module = this.modules[path] = Module.new(id, path, uri); this.load(module); // this is where the addon's main.js finally runs let program = Object.freeze(module).exports; if (typeof(program.onUnload) === 'function') this.require('api-utils/unload').when(program.onUnload); if (program.main) { let { exit, staticArgs } = this.require('api-utils/system'); let { loadReason } = this.require('@packaging'); program.main({ loadReason: loadReason, staticArgs: staticArgs }, { print: function($) dump($ + '\n'), quit: exit }); } } catch (error) { Cu.reportError(error); if (this.globals.console) this.globals.console.exception(error); throw error; } }, // This is the main entry-point: bootstrap.js calls this when the add-on is // installed. The order of calls is a bit confusing, but here's what // happens (in temporal order): // * process.spawn creates a new XUL 'browser' element which will house the // main addon code. When e10s is active, this uses a real separate OS // process. When e10s is disabled, this element lives in the one original // process. Either way, its API is the same. // * Grab the channel named "require!" and attach a handler which will load // modules (in the chrome process) when requested to by the addon // process. This handler uses Loader.require to import the module, then // calls the module's .initialize() function to connect a new channel. // The remote caller winds up with a channel reference, which they can // use to send messages to the newly loaded module. This is for e10s. // * After the channel handler is attached, process.process() (invoked by // process.spawn()) will use loadScript() to evaluate code in the // 'browser' element (which is where the main addon code starts running), // to do the following: // * create a Loader, initialized with the same manifest and // harness-options.json that we've got // * invoke it's main() method, with the name and path of the addon's // entry module (which comes from linker via harness-options.js, and is // usually main.js). That executes main(), above. // * main() loads the addon's main.js, which executes all top-level // forms. If the module defines an "exports.main=" function, we invoke // that too. This is where the addon finally gets to run. spawn: function spawn(id, path) { let loader = this; let process = this.require('api-utils/process'); process.spawn(id, path)(function(addon) { // Listen to `require!` channel's input messages from the add-on process // and load modules being required. addon.channel('require!').input(function({ requirer: { path }, id }) { try { Loader.require.call(loader, path, id).initialize(addon.channel(id)); } catch (error) { this.globals.console.exception(error); } }); }); }, unload: function unload(reason, callback) { this.require('api-utils/unload').send(reason, callback); } }; exports.Loader = Loader; }(this);