|
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 |
|
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) { |
|
27 |
|
28 'use strict'; |
|
29 |
|
30 module.metadata = { |
|
31 "stability": "unstable" |
|
32 }; |
|
33 |
|
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"); |
|
45 |
|
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; |
|
54 |
|
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"]; |
|
56 |
|
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'; |
|
69 |
|
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 } |
|
83 |
|
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; |
|
93 |
|
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); |
|
104 |
|
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 } |
|
115 |
|
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; |
|
130 |
|
131 function sourceURI(uri) { return String(uri).split(" -> ").pop(); } |
|
132 exports.sourceURI = iced(sourceURI); |
|
133 |
|
134 function isntLoaderFrame(frame) { return frame.fileName !== module.uri } |
|
135 |
|
136 function parseURI(uri) { return String(uri).split(" -> ").pop(); } |
|
137 exports.parseURI = parseURI; |
|
138 |
|
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; |
|
161 |
|
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; |
|
172 |
|
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 }); |
|
179 |
|
180 stream.close(); |
|
181 |
|
182 return data; |
|
183 } |
|
184 |
|
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; |
|
197 |
|
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 }; |
|
231 |
|
232 let sandbox = Cu.Sandbox(options.principal, options); |
|
233 |
|
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; |
|
241 |
|
242 return sandbox; |
|
243 }); |
|
244 exports.Sandbox = Sandbox; |
|
245 |
|
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); |
|
260 |
|
261 return source ? Cu.evalInSandbox(source, sandbox, version, uri, line) |
|
262 : loadSubScript(uri, sandbox, encoding); |
|
263 }); |
|
264 exports.evaluate = evaluate; |
|
265 |
|
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); |
|
271 |
|
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 }); |
|
285 |
|
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 }); |
|
297 |
|
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); |
|
306 |
|
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 } |
|
325 |
|
326 let prototype = typeof(error) === "object" ? error.constructor.prototype : |
|
327 Error.prototype; |
|
328 |
|
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 } |
|
337 |
|
338 if (module.exports && typeof(module.exports) === 'object') |
|
339 freeze(module.exports); |
|
340 |
|
341 return module; |
|
342 }); |
|
343 exports.load = load; |
|
344 |
|
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 } |
|
352 |
|
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 } |
|
358 |
|
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); |
|
372 |
|
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; |
|
377 |
|
378 return resolved; |
|
379 }); |
|
380 exports.resolve = resolve; |
|
381 |
|
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); |
|
390 |
|
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' |
|
393 |
|
394 let fullId = join(rootURI, id); |
|
395 |
|
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 } |
|
412 |
|
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; |
|
419 |
|
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; |
|
424 |
|
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) {} |
|
435 |
|
436 return found; |
|
437 } |
|
438 |
|
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 } |
|
466 |
|
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'; |
|
472 |
|
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 } |
|
482 |
|
483 |
|
484 function addTrailingSlash (path) { |
|
485 return !path ? null : !path.endsWith('/') ? path + '/' : path; |
|
486 } |
|
487 |
|
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 } |
|
493 |
|
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 } |
|
501 |
|
502 const resolveURI = iced(function resolveURI(id, mapping) { |
|
503 let count = mapping.length, index = 0; |
|
504 |
|
505 // Do not resolve if already a resource URI |
|
506 if (isResourceURI(id)) return normalizeExt(id); |
|
507 |
|
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; |
|
516 |
|
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; |
|
525 |
|
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); |
|
530 |
|
531 let requirement; |
|
532 let uri; |
|
533 |
|
534 // TODO should get native Firefox modules before doing node-style lookups |
|
535 // to save on loading time |
|
536 |
|
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]; |
|
542 |
|
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; |
|
548 |
|
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 } |
|
561 |
|
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 } |
|
573 |
|
574 // Resolves `uri` of module using loaders resolve function. |
|
575 uri = uri || resolveURI(requirement, mapping); |
|
576 |
|
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); |
|
580 |
|
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; |
|
593 |
|
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 } |
|
631 |
|
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; |
|
639 |
|
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; |
|
650 |
|
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; |
|
661 |
|
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; |
|
676 |
|
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); |
|
703 |
|
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)); |
|
710 |
|
711 let mapping = sortPaths(paths); |
|
712 |
|
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); |
|
724 |
|
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 }, {}); |
|
737 |
|
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 }; |
|
768 |
|
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 } |
|
775 |
|
776 return freeze(create(null, returnObj)); |
|
777 }); |
|
778 exports.Loader = Loader; |
|
779 |
|
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] === '.' |
|
785 |
|
786 const generateMap = iced(function generateMap(options, callback) { |
|
787 let { rootURI, resolve, paths } = override({ |
|
788 paths: {}, |
|
789 resolve: exports.nodeResolve |
|
790 }, options); |
|
791 |
|
792 rootURI = addTrailingSlash(rootURI); |
|
793 |
|
794 let manifest; |
|
795 let manifestURI = join(rootURI, 'package.json'); |
|
796 |
|
797 if (rootURI) |
|
798 manifest = JSON.parse(readURI(manifestURI)); |
|
799 else |
|
800 throw new Error('No `rootURI` given to generate map'); |
|
801 |
|
802 let main = getManifestMain(manifest); |
|
803 |
|
804 findAllModuleIncludes(main, { |
|
805 resolve: resolve, |
|
806 manifest: manifest, |
|
807 rootURI: rootURI |
|
808 }, {}, callback); |
|
809 |
|
810 }); |
|
811 exports.generateMap = generateMap; |
|
812 |
|
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 } |
|
819 |
|
820 function findAllModuleIncludes (uri, options, results, callback) { |
|
821 let { resolve, manifest, rootURI } = options; |
|
822 results = results || {}; |
|
823 |
|
824 // Abort if JSON or JSM |
|
825 if (isJSONURI(uri) || isJSMURI(uri)) { |
|
826 callback(results); |
|
827 return void 0; |
|
828 } |
|
829 |
|
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 } |
|
836 |
|
837 results[uri] = modules.reduce((agg, mod) => { |
|
838 let resolved = resolve(mod, uri, { manifest: manifest, rootURI: rootURI }); |
|
839 |
|
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 }, {}); |
|
847 |
|
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 } |
|
860 |
|
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 = []; |
|
869 |
|
870 walk(src, function (node) { |
|
871 if (isRequire(node)) |
|
872 modules.push(node.arguments[0].value); |
|
873 }); |
|
874 |
|
875 callback(modules); |
|
876 } |
|
877 |
|
878 function walk (src, callback) { |
|
879 let nodes = Reflect.parse(src); |
|
880 traverse(nodes, callback); |
|
881 } |
|
882 |
|
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 } |
|
901 |
|
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 } |
|
916 |
|
917 }); |
|
918 |