/* 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/. */ ;(function(id, factory) { // Module boilerplate :( if (typeof(define) === 'function') { // RequireJS define(factory); } else if (typeof(require) === 'function') { // CommonJS factory.call(this, require, exports, module); } else if (~String(this).indexOf('BackstagePass')) { // JSM factory(function require(uri) { var imports = {}; this['Components'].utils.import(uri, imports); return imports; }, this, { uri: __URI__, id: id }); this.EXPORTED_SYMBOLS = Object.keys(this); } else { // Browser or alike var globals = this factory(function require(id) { return globals[id]; }, (globals[id] = {}), { uri: document.location.href + '#' + id, id: id }); } }).call(this, 'loader', function(require, exports, module) { '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 { loadSubScript } = Cc['@mozilla.org/moz/jssubscript-loader;1']. getService(Ci.mozIJSSubScriptLoader); const { notifyObservers } = Cc['@mozilla.org/observer-service;1']. getService(Ci.nsIObserverService); // Define some shortcuts. const bind = Function.call.bind(Function.bind); const getOwnPropertyNames = Object.getOwnPropertyNames; const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; const define = Object.defineProperties; const prototypeOf = Object.getPrototypeOf; const create = Object.create; const keys = Object.keys; // Workaround for bug 674195. Freezing objects from other compartments fail, // so we use `Object.freeze` from the same component instead. function freeze(object) { if (prototypeOf(object) === null) { Object.freeze(object); } else { prototypeOf(prototypeOf(object.isPrototypeOf)). constructor. // `Object` from the owner compartment. freeze(object); } return object; } // Returns map of given `object`-s own property descriptors. const descriptor = iced(function descriptor(object) { let value = {}; getOwnPropertyNames(object).forEach(function(name) { value[name] = getOwnPropertyDescriptor(object, name) }); return value; }); exports.descriptor = descriptor; // Freeze important built-ins so they can't be used by untrusted code as a // message passing channel. freeze(Object); freeze(Object.prototype); freeze(Function); freeze(Function.prototype); freeze(Array); freeze(Array.prototype); freeze(String); freeze(String.prototype); // This function takes `f` function sets it's `prototype` to undefined and // freezes it. We need to do this kind of deep freeze with all the exposed // functions so that untrusted code won't be able to use them a message // passing channel. function iced(f) { f.prototype = undefined; return freeze(f); } // Defines own properties of given `properties` object on the given // target object overriding any existing property with a conflicting name. // Returns `target` object. Note we only export this function because it's // useful during loader bootstrap when other util modules can't be used & // thats only case where this export should be used. const override = iced(function override(target, source) { let properties = descriptor(target) let extension = descriptor(source || {}) getOwnPropertyNames(extension).forEach(function(name) { properties[name] = extension[name]; }); return define({}, properties); }); exports.override = override; // Function takes set of options and returns a JS sandbox. Function may be // passed set of options: // - `name`: A string value which identifies the sandbox in about:memory. Will // throw exception if omitted. // - `principal`: String URI or `nsIPrincipal` for the sandbox. Defaults to // system principal. // - `prototype`: Ancestor for the sandbox that will be created. Defaults to // `{}`. // - `wantXrays`: A Boolean value indicating whether code outside the sandbox // wants X-ray vision with respect to objects inside the sandbox. Defaults // to `true`. // - `sandbox`: A sandbox to share JS compartment with. If omitted new // compartment will be created. // For more details see: // https://developer.mozilla.org/en/Components.utils.Sandbox const Sandbox = iced(function Sandbox(options) { // Normalize options and rename to match `Cu.Sandbox` expectations. options = { // Do not expose `Components` if you really need them (bad idea!) you // still can expose via prototype. wantComponents: false, sandboxName: options.name, principal: 'principal' in options ? options.principal : systemPrincipal, wantXrays: 'wantXrays' in options ? options.wantXrays : true, sandboxPrototype: 'prototype' in options ? options.prototype : {}, sameGroupAs: 'sandbox' in options ? options.sandbox : null }; // Make `options.sameGroupAs` only if `sandbox` property is passed, // otherwise `Cu.Sandbox` will throw. if (!options.sameGroupAs) delete options.sameGroupAs; let sandbox = Cu.Sandbox(options.principal, options); // Each sandbox at creation gets set of own properties that will be shadowing // ones from it's prototype. We override delete such `sandbox` properties // to avoid shadowing. delete sandbox.Iterator; delete sandbox.Components; delete sandbox.importFunction; delete sandbox.debug; return sandbox; }); exports.Sandbox = Sandbox; // Evaluates code from the given `uri` into given `sandbox`. If // `options.source` is passed, then that code is evaluated instead. // Optionally following options may be given: // - `options.encoding`: Source encoding, defaults to 'UTF-8'. // - `options.line`: Line number to start count from for stack traces. // Defaults to 1. // - `options.version`: Version of JS used, defaults to '1.8'. const evaluate = iced(function evaluate(sandbox, uri, options) { let { source, line, version, encoding } = override({ encoding: 'UTF-8', line: 1, version: '1.8', source: null }, options); return source ? Cu.evalInSandbox(source, sandbox, version, uri, line) : loadSubScript(uri, sandbox, encoding); }); exports.evaluate = evaluate; // Populates `exports` of the given CommonJS `module` object, in the context // of the given `loader` by evaluating code associated with it. const load = iced(function load(loader, module) { let { sandboxes, globals } = loader; let require = Require(loader, module); let sandbox = sandboxes[module.uri] = Sandbox({ name: module.uri, // Get an existing module sandbox, if any, so we can reuse its compartment // when creating the new one to reduce memory consumption. sandbox: sandboxes[keys(sandboxes).shift()], // We expose set of properties defined by `CommonJS` specification via // prototype of the sandbox. Also globals are deeper in the prototype // chain so that each module has access to them as well. prototype: create(globals, descriptor({ require: require, module: module, exports: module.exports })), wantXrays: false }); evaluate(sandbox, module.uri); if (module.exports && typeof(module.exports) === 'object') freeze(module.exports); return module; }); exports.load = load; // Utility function to check if id is relative. function isRelative(id) { return id[0] === '.'; } // Utility function to normalize module `uri`s so they have `.js` extension. function normalize(uri) { return uri.substr(-3) === '.js' ? uri : uri + '.js'; } // Utility function to join paths. In common case `base` is a // `requirer.uri` but in some cases it may be `baseURI`. In order to // avoid complexity we require `baseURI` with a trailing `/`. const resolve = iced(function resolve(id, base) { let paths = id.split('/'); let result = base.split('/'); result.pop(); while (paths.length) { let path = paths.shift(); if (path === '..') result.pop(); else if (path !== '.') result.push(path); } return result.join('/'); }); exports.resolve = resolve; const resolveURI = iced(function resolveURI(id, mapping) { let count = mapping.length, index = 0; while (index < count) { let [ path, uri ] = mapping[index ++]; if (id.indexOf(path) === 0) return normalize(id.replace(path, uri)); } }); exports.resolveURI = resolveURI; // Creates version of `require` that will be exposed to the given `module` // in the context of the given `loader`. Each module gets own limited copy // of `require` that is allowed to load only a modules that are associated // with it during link time. const Require = iced(function Require(loader, requirer) { let { modules, mapping, resolve } = loader; function require(id) { if (!id) // Throw if `id` is not passed. throw Error('you must provide a module name when calling require() from ' + requirer.id, requirer.uri); // Resolve `id` to its requirer if it's relative. let requirement = requirer ? resolve(id, requirer.id) : id; // Resolves `uri` of module using loaders resolve function. let uri = resolveURI(requirement, mapping); if (!uri) // Throw if `uri` can not be resolved. throw Error('Module: Can not resolve "' + id + '" module required by ' + requirer.id + ' located at ' + requirer.uri, requirer.uri); let module = null; // If module is already cached by loader then just use it. if (uri in modules) { module = modules[uri]; } // Otherwise load and cache it. We also freeze module to prevent it from // further changes at runtime. else { module = modules[uri] = Module(requirement, uri); freeze(load(loader, module)); } return module.exports; } // Make `require.main === module` evaluate to true in main module scope. require.main = loader.main === requirer ? requirer : undefined; return iced(require); }); exports.Require = Require; const main = iced(function main(loader, id) { let module = Module(id, resolveURI(id, loader.mapping)); loader.main = module; return load(loader, module).exports; }); exports.main = main; // Makes module object that is made available to CommonJS modules when they // are evaluated, along with `exports` and `require`. const Module = iced(function Module(id, uri) { return create(null, { id: { enumerable: true, value: id }, exports: { enumerable: true, writable: true, value: create(null) }, uri: { value: uri } }); }); exports.Module = Module; // Takes `loader`, and unload `reason` string and notifies all observers that // they should cleanup after them-self. const unload = iced(function unload(loader, reason) { // subject is a unique object created per loader instance. // This allows any code to cleanup on loader unload regardless of how // it was loaded. To handle unload for specific loader subject may be // asserted against loader.destructor or require('@loader/unload') // Note: We don not destroy loader's module cache or sandboxes map as // some modules may do cleanup in subsequent turns of event loop. Destroying // cache may cause module identity problems in such cases. let subject = { wrappedJSObject: loader.destructor }; notifyObservers(subject, 'sdk:loader:destroy', reason); }); exports.unload = unload; // Function makes new loader that can be used to load CommonJS modules // described by a given `options.manifest`. Loader takes following options: // - `globals`: Optional map of globals, that all module scopes will inherit // from. Map is also exposed under `globals` property of the returned loader // so it can be extended further later. Defaults to `{}`. // - `modules` Optional map of built-in module exports mapped by module id. // These modules will incorporated into module cache. Each module will be // frozen. // - `resolve` Optional module `id` resolution function. If given it will be // used to resolve module URIs, by calling it with require term, requirer // module object (that has `uri` property) and `baseURI` of the loader. // If `resolve` does not returns `uri` string exception will be thrown by // an associated `require` call. const Loader = iced(function Loader(options) { let { modules, globals, resolve, paths } = override({ paths: {}, modules: {}, globals: {}, resolve: exports.resolve }, options); // We create an identity object that will be dispatched on an unload // event as subject. This way unload listeners will be able to assert // which loader is unloaded. Please note that we intentionally don't // use `loader` as subject to prevent a loader access leakage through // observer notifications. let destructor = freeze(create(null)); // Make mapping array that is sorted from longest path to shortest path // to allow overlays. let mapping = keys(paths). sort(function(a, b) { return b.length - a.length }). map(function(path) { return [ path, paths[path] ] }); // Define pseudo modules. modules = override({ '@loader/unload': destructor, '@loader/options': options, 'chrome': { Cc: Cc, Ci: Ci, Cu: Cu, Cr: Cr, Cm: Cm, CC: bind(CC, Components), components: Components } }, modules); modules = keys(modules).reduce(function(result, id) { // We resolve `uri` from `id` since modules are cached by `uri`. let uri = resolveURI(id, mapping); let module = Module(id, uri); module.exports = freeze(modules[id]); result[uri] = freeze(module); return result; }, {}); // Loader object is just a representation of a environment // state. We freeze it and mark make it's properties non-enumerable // as they are pure implementation detail that no one should rely upon. return freeze(create(null, { destructor: { enumerable: false, value: destructor }, globals: { enumerable: false, value: globals }, mapping: { enumerable: false, value: mapping }, // Map of module objects indexed by module URIs. modules: { enumerable: false, value: modules }, // Map of module sandboxes indexed by module URIs. sandboxes: { enumerable: false, value: {} }, resolve: { enumerable: false, value: resolve }, // Main (entry point) module, it can be set only once, since loader // instance can have only one main module. main: new function() { let main; return { enumerable: false, get: function() { return main; }, // Only set main if it has not being set yet! set: function(module) { main = main || module; } } } })); }); exports.Loader = Loader; });