aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.5/packages/api-utils/lib/cuddlefish.js
diff options
context:
space:
mode:
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.js307
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);