addon-sdk/source/lib/toolkit/loader.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 ;(function(id, factory) { // Module boilerplate :(
     6   if (typeof(define) === 'function') { // RequireJS
     7     define(factory);
     8   } else if (typeof(require) === 'function') { // CommonJS
     9     factory.call(this, require, exports, module);
    10   } else if (~String(this).indexOf('BackstagePass')) { // JSM
    11     this[factory.name] = {};
    12     factory(function require(uri) {
    13       var imports = {};
    14       this['Components'].utils.import(uri, imports);
    15       return imports;
    16     }, this[factory.name], { uri: __URI__, id: id });
    17     this.EXPORTED_SYMBOLS = [factory.name];
    18   } else if (~String(this).indexOf('Sandbox')) { // Sandbox
    19     factory(function require(uri) {}, this, { uri: __URI__, id: id });
    20   } else {  // Browser or alike
    21     var globals = this
    22     factory(function require(id) {
    23       return globals[id];
    24     }, (globals[id] = {}), { uri: document.location.href + '#' + id, id: id });
    25   }
    26 }).call(this, 'loader', function Loader(require, exports, module) {
    28 'use strict';
    30 module.metadata = {
    31   "stability": "unstable"
    32 };
    34 const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu,
    35         results: Cr, manager: Cm } = Components;
    36 const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
    37 const { loadSubScript } = Cc['@mozilla.org/moz/jssubscript-loader;1'].
    38                      getService(Ci.mozIJSSubScriptLoader);
    39 const { notifyObservers } = Cc['@mozilla.org/observer-service;1'].
    40                         getService(Ci.nsIObserverService);
    41 const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
    42 const { Reflect } = Cu.import("resource://gre/modules/reflect.jsm", {});
    43 const { console } = Cu.import("resource://gre/modules/devtools/Console.jsm");
    44 const { join: pathJoin, normalize, dirname } = Cu.import("resource://gre/modules/osfile/ospath_unix.jsm");
    46 // Define some shortcuts.
    47 const bind = Function.call.bind(Function.bind);
    48 const getOwnPropertyNames = Object.getOwnPropertyNames;
    49 const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
    50 const define = Object.defineProperties;
    51 const prototypeOf = Object.getPrototypeOf;
    52 const create = Object.create;
    53 const keys = Object.keys;
    55 const NODE_MODULES = ["assert", "buffer_ieee754", "buffer", "child_process", "cluster", "console", "constants", "crypto", "_debugger", "dgram", "dns", "domain", "events", "freelist", "fs", "http", "https", "_linklist", "module", "net", "os", "path", "punycode", "querystring", "readline", "repl", "stream", "string_decoder", "sys", "timers", "tls", "tty", "url", "util", "vm", "zlib"];
    57 const COMPONENT_ERROR = '`Components` is not available in this context.\n' +
    58   'Functionality provided by Components may be available in an SDK\n' +
    59   'module: https://jetpack.mozillalabs.com/sdk/latest/docs/ \n\n' +
    60   'However, if you still need to import Components, you may use the\n' +
    61   '`chrome` module\'s properties for shortcuts to Component properties:\n\n' +
    62   'Shortcuts: \n' +
    63   '    Cc = Components' + '.classes \n' +
    64   '    Ci = Components' + '.interfaces \n' +
    65   '    Cu = Components' + '.utils \n' +
    66   '    CC = Components' + '.Constructor \n' +
    67   'Example: \n' +
    68   '    let { Cc, Ci } = require(\'chrome\');\n';
    70 // Workaround for bug 674195. Freezing objects from other compartments fail,
    71 // so we use `Object.freeze` from the same component instead.
    72 function freeze(object) {
    73   if (prototypeOf(object) === null) {
    74       Object.freeze(object);
    75   }
    76   else {
    77     prototypeOf(prototypeOf(object.isPrototypeOf)).
    78       constructor. // `Object` from the owner compartment.
    79       freeze(object);
    80   }
    81   return object;
    82 }
    84 // Returns map of given `object`-s own property descriptors.
    85 const descriptor = iced(function descriptor(object) {
    86   let value = {};
    87   getOwnPropertyNames(object).forEach(function(name) {
    88     value[name] = getOwnPropertyDescriptor(object, name)
    89   });
    90   return value;
    91 });
    92 exports.descriptor = descriptor;
    94 // Freeze important built-ins so they can't be used by untrusted code as a
    95 // message passing channel.
    96 freeze(Object);
    97 freeze(Object.prototype);
    98 freeze(Function);
    99 freeze(Function.prototype);
   100 freeze(Array);
   101 freeze(Array.prototype);
   102 freeze(String);
   103 freeze(String.prototype);
   105 // This function takes `f` function sets it's `prototype` to undefined and
   106 // freezes it. We need to do this kind of deep freeze with all the exposed
   107 // functions so that untrusted code won't be able to use them a message
   108 // passing channel.
   109 function iced(f) {
   110   if (!Object.isFrozen(f)) {
   111     f.prototype = undefined;
   112   }
   113   return freeze(f);
   114 }
   116 // Defines own properties of given `properties` object on the given
   117 // target object overriding any existing property with a conflicting name.
   118 // Returns `target` object. Note we only export this function because it's
   119 // useful during loader bootstrap when other util modules can't be used &
   120 // thats only case where this export should be used.
   121 const override = iced(function override(target, source) {
   122   let properties = descriptor(target)
   123   let extension = descriptor(source || {})
   124   getOwnPropertyNames(extension).forEach(function(name) {
   125     properties[name] = extension[name];
   126   });
   127   return define({}, properties);
   128 });
   129 exports.override = override;
   131 function sourceURI(uri) { return String(uri).split(" -> ").pop(); }
   132 exports.sourceURI = iced(sourceURI);
   134 function isntLoaderFrame(frame) { return frame.fileName !== module.uri }
   136 function parseURI(uri) { return String(uri).split(" -> ").pop(); }
   137 exports.parseURI = parseURI;
   139 function parseStack(stack) {
   140   let lines = String(stack).split("\n");
   141   return lines.reduce(function(frames, line) {
   142     if (line) {
   143       let atIndex = line.indexOf("@");
   144       let columnIndex = line.lastIndexOf(":");
   145       let lineIndex = line.lastIndexOf(":", columnIndex - 1);
   146       let fileName = parseURI(line.slice(atIndex + 1, lineIndex));
   147       let lineNumber = parseInt(line.slice(lineIndex + 1, columnIndex));
   148       let columnNumber = parseInt(line.slice(columnIndex + 1));
   149       let name = line.slice(0, atIndex).split("(").shift();
   150       frames.unshift({
   151         fileName: fileName,
   152         name: name,
   153         lineNumber: lineNumber,
   154         columnNumber: columnNumber
   155       });
   156     }
   157     return frames;
   158   }, []);
   159 }
   160 exports.parseStack = parseStack;
   162 function serializeStack(frames) {
   163   return frames.reduce(function(stack, frame) {
   164     return frame.name + "@" +
   165            frame.fileName + ":" +
   166            frame.lineNumber + ":" +
   167            frame.columnNumber + "\n" +
   168            stack;
   169   }, "");
   170 }
   171 exports.serializeStack = serializeStack;
   173 function readURI(uri) {
   174   let stream = NetUtil.newChannel(uri, 'UTF-8', null).open();
   175   let count = stream.available();
   176   let data = NetUtil.readInputStreamToString(stream, count, {
   177     charset: 'UTF-8'
   178   });
   180   stream.close();
   182   return data;
   183 }
   185 // Combines all arguments into a resolved, normalized path
   186 function join (...paths) {
   187   let resolved = normalize(pathJoin(...paths))
   188   // OS.File `normalize` strips out the second slash in
   189   // `resource://` or `chrome://`, and third slash in
   190   // `file:///`, so we work around this
   191   resolved = resolved.replace(/^resource\:\/([^\/])/, 'resource://$1');
   192   resolved = resolved.replace(/^file\:\/([^\/])/, 'file:///$1');
   193   resolved = resolved.replace(/^chrome\:\/([^\/])/, 'chrome://$1');
   194   return resolved;
   195 }
   196 exports.join = join;
   198 // Function takes set of options and returns a JS sandbox. Function may be
   199 // passed set of options:
   200 //  - `name`: A string value which identifies the sandbox in about:memory. Will
   201 //    throw exception if omitted.
   202 // - `principal`: String URI or `nsIPrincipal` for the sandbox. Defaults to
   203 //    system principal.
   204 // - `prototype`: Ancestor for the sandbox that will be created. Defaults to
   205 //    `{}`.
   206 // - `wantXrays`: A Boolean value indicating whether code outside the sandbox
   207 //    wants X-ray vision with respect to objects inside the sandbox. Defaults
   208 //    to `true`.
   209 // - `sandbox`: A sandbox to share JS compartment with. If omitted new
   210 //    compartment will be created.
   211 // - `metadata`: A metadata object associated with the sandbox. It should
   212 //    be JSON-serializable.
   213 // For more details see:
   214 // https://developer.mozilla.org/en/Components.utils.Sandbox
   215 const Sandbox = iced(function Sandbox(options) {
   216   // Normalize options and rename to match `Cu.Sandbox` expectations.
   217   options = {
   218     // Do not expose `Components` if you really need them (bad idea!) you
   219     // still can expose via prototype.
   220     wantComponents: false,
   221     sandboxName: options.name,
   222     principal: 'principal' in options ? options.principal : systemPrincipal,
   223     wantXrays: 'wantXrays' in options ? options.wantXrays : true,
   224     wantGlobalProperties: 'wantGlobalProperties' in options ?
   225                           options.wantGlobalProperties : [],
   226     sandboxPrototype: 'prototype' in options ? options.prototype : {},
   227     invisibleToDebugger: 'invisibleToDebugger' in options ?
   228                          options.invisibleToDebugger : false,
   229     metadata: 'metadata' in options ? options.metadata : {}
   230   };
   232   let sandbox = Cu.Sandbox(options.principal, options);
   234   // Each sandbox at creation gets set of own properties that will be shadowing
   235   // ones from it's prototype. We override delete such `sandbox` properties
   236   // to avoid shadowing.
   237   delete sandbox.Iterator;
   238   delete sandbox.Components;
   239   delete sandbox.importFunction;
   240   delete sandbox.debug;
   242   return sandbox;
   243 });
   244 exports.Sandbox = Sandbox;
   246 // Evaluates code from the given `uri` into given `sandbox`. If
   247 // `options.source` is passed, then that code is evaluated instead.
   248 // Optionally following options may be given:
   249 // - `options.encoding`: Source encoding, defaults to 'UTF-8'.
   250 // - `options.line`: Line number to start count from for stack traces.
   251 //    Defaults to 1.
   252 // - `options.version`: Version of JS used, defaults to '1.8'.
   253 const evaluate = iced(function evaluate(sandbox, uri, options) {
   254   let { source, line, version, encoding } = override({
   255     encoding: 'UTF-8',
   256     line: 1,
   257     version: '1.8',
   258     source: null
   259   }, options);
   261   return source ? Cu.evalInSandbox(source, sandbox, version, uri, line)
   262                 : loadSubScript(uri, sandbox, encoding);
   263 });
   264 exports.evaluate = evaluate;
   266 // Populates `exports` of the given CommonJS `module` object, in the context
   267 // of the given `loader` by evaluating code associated with it.
   268 const load = iced(function load(loader, module) {
   269   let { sandboxes, globals } = loader;
   270   let require = Require(loader, module);
   272   // We expose set of properties defined by `CommonJS` specification via
   273   // prototype of the sandbox. Also globals are deeper in the prototype
   274   // chain so that each module has access to them as well.
   275   let descriptors = descriptor({
   276     require: require,
   277     module: module,
   278     exports: module.exports,
   279     get Components() {
   280       // Expose `Components` property to throw error on usage with
   281       // additional information
   282       throw new ReferenceError(COMPONENT_ERROR);
   283     }
   284   });
   286   let sandbox = sandboxes[module.uri] = Sandbox({
   287     name: module.uri,
   288     prototype: create(globals, descriptors),
   289     wantXrays: false,
   290     wantGlobalProperties: module.id == "sdk/indexed-db" ? ["indexedDB"] : [],
   291     invisibleToDebugger: loader.invisibleToDebugger,
   292     metadata: {
   293       addonID: loader.id,
   294       URI: module.uri
   295     }
   296   });
   298   try {
   299     evaluate(sandbox, module.uri);
   300   } catch (error) {
   301     let { message, fileName, lineNumber } = error;
   302     let stack = error.stack || Error().stack;
   303     let frames = parseStack(stack).filter(isntLoaderFrame);
   304     let toString = String(error);
   305     let file = sourceURI(fileName);
   307     // Note that `String(error)` where error is from subscript loader does
   308     // not puts `:` after `"Error"` unlike regular errors thrown by JS code.
   309     // If there is a JS stack then this error has already been handled by an
   310     // inner module load.
   311     if (String(error) === "Error opening input stream (invalid filename?)") {
   312       let caller = frames.slice(0).pop();
   313       fileName = caller.fileName;
   314       lineNumber = caller.lineNumber;
   315       message = "Module `" + module.id + "` is not found at " + module.uri;
   316       toString = message;
   317     }
   318     // Workaround for a Bug 910653. Errors thrown by subscript loader
   319     // do not include `stack` field and above created error won't have
   320     // fileName or lineNumber of the module being loaded, so we ensure
   321     // it does.
   322     else if (frames[frames.length - 1].fileName !== file) {
   323       frames.push({ fileName: file, lineNumber: lineNumber, name: "" });
   324     }
   326     let prototype = typeof(error) === "object" ? error.constructor.prototype :
   327                     Error.prototype;
   329     throw create(prototype, {
   330       message: { value: message, writable: true, configurable: true },
   331       fileName: { value: fileName, writable: true, configurable: true },
   332       lineNumber: { value: lineNumber, writable: true, configurable: true },
   333       stack: { value: serializeStack(frames), writable: true, configurable: true },
   334       toString: { value: function() toString, writable: true, configurable: true },
   335     });
   336   }
   338   if (module.exports && typeof(module.exports) === 'object')
   339     freeze(module.exports);
   341   return module;
   342 });
   343 exports.load = load;
   345 // Utility function to normalize module `uri`s so they have `.js` extension.
   346 function normalizeExt (uri) {
   347   return isJSURI(uri) ? uri :
   348          isJSONURI(uri) ? uri :
   349          isJSMURI(uri) ? uri :
   350          uri + '.js';
   351 }
   353 // Strips `rootURI` from `string` -- used to remove absolute resourceURI
   354 // from a relative path
   355 function stripBase (rootURI, string) {
   356   return string.replace(rootURI, './');
   357 }
   359 // Utility function to join paths. In common case `base` is a
   360 // `requirer.uri` but in some cases it may be `baseURI`. In order to
   361 // avoid complexity we require `baseURI` with a trailing `/`.
   362 const resolve = iced(function resolve(id, base) {
   363   if (!isRelative(id)) return id;
   364   let basePaths = base.split('/');
   365   // Pop the last element in the `base`, because it is from a
   366   // relative file
   367   // '../mod.js' from '/path/to/file.js' should resolve to '/path/mod.js'
   368   basePaths.pop();
   369   if (!basePaths.length)
   370     return normalize(id);
   371   let resolved = join(basePaths.join('/'), id);
   373   // Joining and normalizing removes the './' from relative files.
   374   // We need to ensure the resolution still has the root
   375   if (isRelative(base))
   376     resolved = './' + resolved;
   378   return resolved;
   379 });
   380 exports.resolve = resolve;
   382 // Node-style module lookup
   383 // Takes an id and path and attempts to load a file using node's resolving
   384 // algorithm.
   385 // `id` should already be resolved relatively at this point.
   386 // http://nodejs.org/api/modules.html#modules_all_together
   387 const nodeResolve = iced(function nodeResolve(id, requirer, { manifest, rootURI }) {
   388   // Resolve again
   389   id = exports.resolve(id, requirer);
   391   // we assume that extensions are correct, i.e., a directory doesnt't have '.js'
   392   // and a js file isn't named 'file.json.js'
   394   let fullId = join(rootURI, id);
   396   let resolvedPath;
   397   if (resolvedPath = loadAsFile(fullId))
   398     return stripBase(rootURI, resolvedPath);
   399   else if (resolvedPath = loadAsDirectory(fullId))
   400     return stripBase(rootURI, resolvedPath);
   401   // If manifest has dependencies, attempt to look up node modules
   402   // in the `dependencies` list
   403   else if (manifest.dependencies) {
   404     let dirs = getNodeModulePaths(dirname(join(rootURI, requirer))).map(dir => join(dir, id));
   405     for (let i = 0; i < dirs.length; i++) {
   406       if (resolvedPath = loadAsFile(dirs[i]))
   407         return stripBase(rootURI, resolvedPath);
   408       if (resolvedPath = loadAsDirectory(dirs[i]))
   409         return stripBase(rootURI, resolvedPath);
   410     }
   411   }
   413   // We would not find lookup for things like `sdk/tabs`, as that's part of
   414   // the alias mapping. If during `generateMap`, the runtime lookup resolves
   415   // with `resolveURI` -- if during runtime, then `resolve` will throw.
   416   return void 0;
   417 });
   418 exports.nodeResolve = nodeResolve;
   420 // Attempts to load `path` and then `path.js`
   421 // Returns `path` with valid file, or `undefined` otherwise
   422 function loadAsFile (path) {
   423   let found;
   425   // As per node's loader spec,
   426   // we first should try and load 'path' (with no extension)
   427   // before trying 'path.js'. We will not support this feature
   428   // due to performance, but may add it if necessary for adoption.
   429   try {
   430     // Append '.js' to path name unless it's another support filetype
   431     path = normalizeExt(path);
   432     readURI(path);
   433     found = path;
   434   } catch (e) {}
   436   return found;
   437 }
   439 // Attempts to load `path/package.json`'s `main` entry,
   440 // followed by `path/index.js`, or `undefined` otherwise
   441 function loadAsDirectory (path) {
   442   let found;
   443   try {
   444     // If `path/package.json` exists, parse the `main` entry
   445     // and attempt to load that
   446     let main = getManifestMain(JSON.parse(readURI(path + '/package.json')));
   447     if (main != null) {
   448       let tmpPath = join(path, main);
   449       if (found = loadAsFile(tmpPath))
   450         return found
   451     }
   452     try {
   453       let tmpPath = path + '/index.js';
   454       readURI(tmpPath);
   455       return tmpPath;
   456     } catch (e) {}
   457   } catch (e) {
   458     try {
   459       let tmpPath = path + '/index.js';
   460       readURI(tmpPath);
   461       return tmpPath;
   462     } catch (e) {}
   463   }
   464   return void 0;
   465 }
   467 // From `resolve` module
   468 // https://github.com/substack/node-resolve/blob/master/lib/node-modules-paths.js
   469 function getNodeModulePaths (start) {
   470   // Configurable in node -- do we need this to be configurable?
   471   let moduleDir = 'node_modules';
   473   let parts = start.split('/');
   474   let dirs = [];
   475   for (let i = parts.length - 1; i >= 0; i--) {
   476     if (parts[i] === moduleDir) continue;
   477     let dir = join(parts.slice(0, i + 1).join('/'), moduleDir);
   478     dirs.push(dir);
   479   }
   480   return dirs;
   481 }
   484 function addTrailingSlash (path) {
   485   return !path ? null : !path.endsWith('/') ? path + '/' : path;
   486 }
   488 // Utility function to determine of module id `name` is a built in
   489 // module in node (fs, path, etc.);
   490 function isNodeModule (name) {
   491   return !!~NODE_MODULES.indexOf(name);
   492 }
   494 // Make mapping array that is sorted from longest path to shortest path
   495 // to allow overlays. Used by `resolveURI`, returns an array
   496 function sortPaths (paths) {
   497   return keys(paths).
   498     sort(function(a, b) { return b.length - a.length }).
   499     map(function(path) { return [ path, paths[path] ] });
   500 }
   502 const resolveURI = iced(function resolveURI(id, mapping) {
   503   let count = mapping.length, index = 0;
   505   // Do not resolve if already a resource URI
   506   if (isResourceURI(id)) return normalizeExt(id);
   508   while (index < count) {
   509     let [ path, uri ] = mapping[index ++];
   510     if (id.indexOf(path) === 0)
   511       return normalizeExt(id.replace(path, uri));
   512   }
   513   return void 0; // otherwise we raise a warning, see bug 910304
   514 });
   515 exports.resolveURI = resolveURI;
   517 // Creates version of `require` that will be exposed to the given `module`
   518 // in the context of the given `loader`. Each module gets own limited copy
   519 // of `require` that is allowed to load only a modules that are associated
   520 // with it during link time.
   521 const Require = iced(function Require(loader, requirer) {
   522   let {
   523     modules, mapping, resolve, load, manifest, rootURI, isNative, requireMap
   524   } = loader;
   526   function require(id) {
   527     if (!id) // Throw if `id` is not passed.
   528       throw Error('you must provide a module name when calling require() from '
   529                   + requirer.id, requirer.uri);
   531     let requirement;
   532     let uri;
   534     // TODO should get native Firefox modules before doing node-style lookups
   535     // to save on loading time
   537     if (isNative) {
   538       // If a requireMap is available from `generateMap`, use that to
   539       // immediately resolve the node-style mapping.
   540       if (requireMap && requireMap[requirer.id])
   541         requirement = requireMap[requirer.id][id];
   543       // For native modules, we want to check if it's a module specified
   544       // in 'modules', like `chrome`, or `@loader` -- if it exists,
   545       // just set the uri to skip resolution
   546       if (!requirement && modules[id])
   547         uri = requirement = id;
   549       // If no requireMap was provided, or resolution not found in
   550       // the requireMap, and not a npm dependency, attempt a runtime lookup
   551       if (!requirement && !isNodeModule(id)) {
   552         // If `isNative` defined, this is using the new, native-style
   553         // loader, not cuddlefish, so lets resolve using node's algorithm
   554         // and get back a path that needs to be resolved via paths mapping
   555         // in `resolveURI`
   556         requirement = resolve(id, requirer.id, {
   557           manifest: manifest,
   558           rootURI: rootURI
   559         });
   560       }
   562       // If not found in the map, not a node module, and wasn't able to be
   563       // looked up, it's something
   564       // found in the paths most likely, like `sdk/tabs`, which should
   565       // be resolved relatively if needed using traditional resolve
   566       if (!requirement) {
   567         requirement = isRelative(id) ? exports.resolve(id, requirer.id) : id;
   568       }
   569     } else {
   570       // Resolve `id` to its requirer if it's relative.
   571       requirement = requirer ? resolve(id, requirer.id) : id;
   572     }
   574     // Resolves `uri` of module using loaders resolve function.
   575     uri = uri || resolveURI(requirement, mapping);
   577     if (!uri) // Throw if `uri` can not be resolved.
   578       throw Error('Module: Can not resolve "' + id + '" module required by ' +
   579                   requirer.id + ' located at ' + requirer.uri, requirer.uri);
   581     let module = null;
   582     // If module is already cached by loader then just use it.
   583     if (uri in modules) {
   584       module = modules[uri];
   585     }
   586     else if (isJSMURI(uri)) {
   587       module = modules[uri] = Module(requirement, uri);
   588       module.exports = Cu.import(uri, {});
   589       freeze(module);
   590     }
   591     else if (isJSONURI(uri)) {
   592       let data;
   594       // First attempt to load and parse json uri
   595       // ex: `test.json`
   596       // If that doesn't exist, check for `test.json.js`
   597       // for node parity
   598       try {
   599         data = JSON.parse(readURI(uri));
   600         module = modules[uri] = Module(requirement, uri);
   601         module.exports = data;
   602         freeze(module);
   603       }
   604       catch (err) {
   605         // If error thrown from JSON parsing, throw that, do not
   606         // attempt to find .json.js file
   607         if (err && /JSON\.parse/.test(err.message))
   608           throw err;
   609         uri = uri + '.js';
   610       }
   611     }
   612     // If not yet cached, load and cache it.
   613     // We also freeze module to prevent it from further changes
   614     // at runtime.
   615     if (!(uri in modules)) {
   616       // Many of the loader's functionalities are dependent
   617       // on modules[uri] being set before loading, so we set it and
   618       // remove it if we have any errors.
   619       module = modules[uri] = Module(requirement, uri);
   620       try {
   621         freeze(load(loader, module));
   622       }
   623       catch (e) {
   624         // Clear out modules cache so we can throw on a second invalid require
   625         delete modules[uri];
   626         // Also clear out the Sandbox that was created
   627         delete loader.sandboxes[uri];
   628         throw e;
   629       }
   630     }
   632     return module.exports;
   633   }
   634   // Make `require.main === module` evaluate to true in main module scope.
   635   require.main = loader.main === requirer ? requirer : undefined;
   636   return iced(require);
   637 });
   638 exports.Require = Require;
   640 const main = iced(function main(loader, id) {
   641   // If no main entry provided, and native loader is used,
   642   // read the entry in the manifest
   643   if (!id && loader.isNative)
   644     id = getManifestMain(loader.manifest);
   645   let uri = resolveURI(id, loader.mapping);
   646   let module = loader.main = loader.modules[uri] = Module(id, uri);
   647   return loader.load(loader, module).exports;
   648 });
   649 exports.main = main;
   651 // Makes module object that is made available to CommonJS modules when they
   652 // are evaluated, along with `exports` and `require`.
   653 const Module = iced(function Module(id, uri) {
   654   return create(null, {
   655     id: { enumerable: true, value: id },
   656     exports: { enumerable: true, writable: true, value: create(null) },
   657     uri: { value: uri }
   658   });
   659 });
   660 exports.Module = Module;
   662 // Takes `loader`, and unload `reason` string and notifies all observers that
   663 // they should cleanup after them-self.
   664 const unload = iced(function unload(loader, reason) {
   665   // subject is a unique object created per loader instance.
   666   // This allows any code to cleanup on loader unload regardless of how
   667   // it was loaded. To handle unload for specific loader subject may be
   668   // asserted against loader.destructor or require('@loader/unload')
   669   // Note: We don not destroy loader's module cache or sandboxes map as
   670   // some modules may do cleanup in subsequent turns of event loop. Destroying
   671   // cache may cause module identity problems in such cases.
   672   let subject = { wrappedJSObject: loader.destructor };
   673   notifyObservers(subject, 'sdk:loader:destroy', reason);
   674 });
   675 exports.unload = unload;
   677 // Function makes new loader that can be used to load CommonJS modules
   678 // described by a given `options.manifest`. Loader takes following options:
   679 // - `globals`: Optional map of globals, that all module scopes will inherit
   680 //   from. Map is also exposed under `globals` property of the returned loader
   681 //   so it can be extended further later. Defaults to `{}`.
   682 // - `modules` Optional map of built-in module exports mapped by module id.
   683 //   These modules will incorporated into module cache. Each module will be
   684 //   frozen.
   685 // - `resolve` Optional module `id` resolution function. If given it will be
   686 //   used to resolve module URIs, by calling it with require term, requirer
   687 //   module object (that has `uri` property) and `baseURI` of the loader.
   688 //   If `resolve` does not returns `uri` string exception will be thrown by
   689 //   an associated `require` call.
   690 const Loader = iced(function Loader(options) {
   691   let {
   692     modules, globals, resolve, paths, rootURI, manifest, requireMap, isNative
   693   } = override({
   694     paths: {},
   695     modules: {},
   696     globals: {
   697       console: console
   698     },
   699     resolve: options.isNative ?
   700       exports.nodeResolve :
   701       exports.resolve,
   702   }, options);
   704   // We create an identity object that will be dispatched on an unload
   705   // event as subject. This way unload listeners will be able to assert
   706   // which loader is unloaded. Please note that we intentionally don't
   707   // use `loader` as subject to prevent a loader access leakage through
   708   // observer notifications.
   709   let destructor = freeze(create(null));
   711   let mapping = sortPaths(paths);
   713   // Define pseudo modules.
   714   modules = override({
   715     '@loader/unload': destructor,
   716     '@loader/options': options,
   717     'chrome': { Cc: Cc, Ci: Ci, Cu: Cu, Cr: Cr, Cm: Cm,
   718                 CC: bind(CC, Components), components: Components,
   719                 // `ChromeWorker` has to be inject in loader global scope.
   720                 // It is done by bootstrap.js:loadSandbox for the SDK.
   721                 ChromeWorker: ChromeWorker
   722     }
   723   }, modules);
   725   modules = keys(modules).reduce(function(result, id) {
   726     // We resolve `uri` from `id` since modules are cached by `uri`.
   727     let uri = resolveURI(id, mapping);
   728     // In native loader, the mapping will not contain values for
   729     // pseudomodules -- store them as their ID rather than the URI
   730     if (isNative && !uri)
   731       uri = id;
   732     let module = Module(id, uri);
   733     module.exports = freeze(modules[id]);
   734     result[uri] = freeze(module);
   735     return result;
   736   }, {});
   738   // Loader object is just a representation of a environment
   739   // state. We freeze it and mark make it's properties non-enumerable
   740   // as they are pure implementation detail that no one should rely upon.
   741   let returnObj = {
   742     destructor: { enumerable: false, value: destructor },
   743     globals: { enumerable: false, value: globals },
   744     mapping: { enumerable: false, value: mapping },
   745     // Map of module objects indexed by module URIs.
   746     modules: { enumerable: false, value: modules },
   747     // Map of module sandboxes indexed by module URIs.
   748     sandboxes: { enumerable: false, value: {} },
   749     resolve: { enumerable: false, value: resolve },
   750     // ID of the addon, if provided.
   751     id: { enumerable: false, value: options.id },
   752     // Whether the modules loaded should be ignored by the debugger
   753     invisibleToDebugger: { enumerable: false,
   754                            value: options.invisibleToDebugger || false },
   755     load: { enumerable: false, value: options.load || load },
   756     // Main (entry point) module, it can be set only once, since loader
   757     // instance can have only one main module.
   758     main: new function() {
   759       let main;
   760       return {
   761         enumerable: false,
   762         get: function() { return main; },
   763         // Only set main if it has not being set yet!
   764         set: function(module) { main = main || module; }
   765       }
   766     }
   767   };
   769   if (isNative) {
   770     returnObj.isNative = { enumerable: false, value: true };
   771     returnObj.manifest = { enumerable: false, value: manifest };
   772     returnObj.requireMap = { enumerable: false, value: requireMap };
   773     returnObj.rootURI = { enumerable: false, value: addTrailingSlash(rootURI) };
   774   }
   776   return freeze(create(null, returnObj));
   777 });
   778 exports.Loader = Loader;
   780 let isJSONURI = uri => uri.substr(-5) === '.json';
   781 let isJSMURI = uri => uri.substr(-4) === '.jsm';
   782 let isJSURI = uri => uri.substr(-3) === '.js';
   783 let isResourceURI = uri => uri.substr(0, 11) === 'resource://';
   784 let isRelative = id => id[0] === '.'
   786 const generateMap = iced(function generateMap(options, callback) {
   787   let { rootURI, resolve, paths } = override({
   788     paths: {},
   789     resolve: exports.nodeResolve
   790   }, options);
   792   rootURI = addTrailingSlash(rootURI);
   794   let manifest;
   795   let manifestURI = join(rootURI, 'package.json');
   797   if (rootURI)
   798     manifest = JSON.parse(readURI(manifestURI));
   799   else
   800     throw new Error('No `rootURI` given to generate map');
   802   let main = getManifestMain(manifest);
   804   findAllModuleIncludes(main, {
   805     resolve: resolve,
   806     manifest: manifest,
   807     rootURI: rootURI
   808   }, {}, callback);
   810 });
   811 exports.generateMap = generateMap;
   813 // Default `main` entry to './index.js' and ensure is relative,
   814 // since node allows 'lib/index.js' without relative `./`
   815 function getManifestMain (manifest) {
   816   let main = manifest.main || './index.js';
   817   return isRelative(main) ? main : './' + main;
   818 }
   820 function findAllModuleIncludes (uri, options, results, callback) {
   821   let { resolve, manifest, rootURI } = options;
   822   results = results || {};
   824   // Abort if JSON or JSM
   825   if (isJSONURI(uri) || isJSMURI(uri)) {
   826     callback(results);
   827     return void 0;
   828   }
   830   findModuleIncludes(join(rootURI, uri), modules => {
   831     // If no modules are included in the file, just call callback immediately
   832     if (!modules.length) {
   833       callback(results);
   834       return void 0;
   835     }
   837     results[uri] = modules.reduce((agg, mod) => {
   838       let resolved = resolve(mod, uri, { manifest: manifest, rootURI: rootURI });
   840       // If resolution found, store the resolution; otherwise,
   841       // skip storing it as runtime lookup will handle this
   842       if (!resolved)
   843         return agg;
   844       agg[mod] = resolved;
   845       return agg;
   846     }, {});
   848     let includes = keys(results[uri]);
   849     let count = 0;
   850     let subcallback = () => { if (++count >= includes.length) callback(results) };
   851     includes.map(id => {
   852       let moduleURI = results[uri][id];
   853       if (!results[moduleURI])
   854         findAllModuleIncludes(moduleURI, options, results, subcallback);
   855       else
   856         subcallback();
   857     });
   858   });
   859 }
   861 // From Substack's detector
   862 // https://github.com/substack/node-detective
   863 //
   864 // Given a resource URI or source, return an array of strings passed into
   865 // the require statements from the source
   866 function findModuleIncludes (uri, callback) {
   867   let src = isResourceURI(uri) ? readURI(uri) : uri;
   868   let modules = [];
   870   walk(src, function (node) {
   871     if (isRequire(node))
   872       modules.push(node.arguments[0].value);
   873   });
   875   callback(modules);
   876 }
   878 function walk (src, callback) {
   879   let nodes = Reflect.parse(src);
   880   traverse(nodes, callback);
   881 }
   883 function traverse (node, cb) {
   884   if (Array.isArray(node)) {
   885     node.map(x => {
   886       if (x != null) {
   887         x.parent = node;
   888         traverse(x, cb);
   889       }
   890     });
   891   }
   892   else if (node && typeof node === 'object') {
   893     cb(node);
   894     keys(node).map(key => {
   895       if (key === 'parent' || !node[key]) return;
   896       node[key].parent = node;
   897       traverse(node[key], cb);
   898     });
   899   }
   900 }
   902 // From Substack's detector
   903 // https://github.com/substack/node-detective
   904 // Check an AST node to see if its a require statement.
   905 // A modification added to only evaluate to true if it actually
   906 // has a value being passed in as an argument
   907 function isRequire (node) {
   908   var c = node.callee;
   909   return c
   910     && node.type === 'CallExpression'
   911     && c.type === 'Identifier'
   912     && c.name === 'require'
   913     && node.arguments.length
   914    && node.arguments[0].type === 'Literal';
   915 }
   917 });

mercurial