diff options
Diffstat (limited to 'tools/addon-sdk-1.5/packages/api-utils/lib/cuddlefish.js')
-rw-r--r-- | tools/addon-sdk-1.5/packages/api-utils/lib/cuddlefish.js | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/tools/addon-sdk-1.5/packages/api-utils/lib/cuddlefish.js b/tools/addon-sdk-1.5/packages/api-utils/lib/cuddlefish.js new file mode 100644 index 0000000..b501786 --- /dev/null +++ b/tools/addon-sdk-1.5/packages/api-utils/lib/cuddlefish.js @@ -0,0 +1,307 @@ +/* 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: {} }, + + // Workaround loader instances Object.freeze, + // we need to set attributes lazily, like `loader.data.channel`. + data: { 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); |