Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 });