loader.js 32 KB

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