1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/lib/toolkit/loader.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,918 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +;(function(id, factory) { // Module boilerplate :( 1.9 + if (typeof(define) === 'function') { // RequireJS 1.10 + define(factory); 1.11 + } else if (typeof(require) === 'function') { // CommonJS 1.12 + factory.call(this, require, exports, module); 1.13 + } else if (~String(this).indexOf('BackstagePass')) { // JSM 1.14 + this[factory.name] = {}; 1.15 + factory(function require(uri) { 1.16 + var imports = {}; 1.17 + this['Components'].utils.import(uri, imports); 1.18 + return imports; 1.19 + }, this[factory.name], { uri: __URI__, id: id }); 1.20 + this.EXPORTED_SYMBOLS = [factory.name]; 1.21 + } else if (~String(this).indexOf('Sandbox')) { // Sandbox 1.22 + factory(function require(uri) {}, this, { uri: __URI__, id: id }); 1.23 + } else { // Browser or alike 1.24 + var globals = this 1.25 + factory(function require(id) { 1.26 + return globals[id]; 1.27 + }, (globals[id] = {}), { uri: document.location.href + '#' + id, id: id }); 1.28 + } 1.29 +}).call(this, 'loader', function Loader(require, exports, module) { 1.30 + 1.31 +'use strict'; 1.32 + 1.33 +module.metadata = { 1.34 + "stability": "unstable" 1.35 +}; 1.36 + 1.37 +const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu, 1.38 + results: Cr, manager: Cm } = Components; 1.39 +const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')(); 1.40 +const { loadSubScript } = Cc['@mozilla.org/moz/jssubscript-loader;1']. 1.41 + getService(Ci.mozIJSSubScriptLoader); 1.42 +const { notifyObservers } = Cc['@mozilla.org/observer-service;1']. 1.43 + getService(Ci.nsIObserverService); 1.44 +const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {}); 1.45 +const { Reflect } = Cu.import("resource://gre/modules/reflect.jsm", {}); 1.46 +const { console } = Cu.import("resource://gre/modules/devtools/Console.jsm"); 1.47 +const { join: pathJoin, normalize, dirname } = Cu.import("resource://gre/modules/osfile/ospath_unix.jsm"); 1.48 + 1.49 +// Define some shortcuts. 1.50 +const bind = Function.call.bind(Function.bind); 1.51 +const getOwnPropertyNames = Object.getOwnPropertyNames; 1.52 +const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; 1.53 +const define = Object.defineProperties; 1.54 +const prototypeOf = Object.getPrototypeOf; 1.55 +const create = Object.create; 1.56 +const keys = Object.keys; 1.57 + 1.58 +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"]; 1.59 + 1.60 +const COMPONENT_ERROR = '`Components` is not available in this context.\n' + 1.61 + 'Functionality provided by Components may be available in an SDK\n' + 1.62 + 'module: https://jetpack.mozillalabs.com/sdk/latest/docs/ \n\n' + 1.63 + 'However, if you still need to import Components, you may use the\n' + 1.64 + '`chrome` module\'s properties for shortcuts to Component properties:\n\n' + 1.65 + 'Shortcuts: \n' + 1.66 + ' Cc = Components' + '.classes \n' + 1.67 + ' Ci = Components' + '.interfaces \n' + 1.68 + ' Cu = Components' + '.utils \n' + 1.69 + ' CC = Components' + '.Constructor \n' + 1.70 + 'Example: \n' + 1.71 + ' let { Cc, Ci } = require(\'chrome\');\n'; 1.72 + 1.73 +// Workaround for bug 674195. Freezing objects from other compartments fail, 1.74 +// so we use `Object.freeze` from the same component instead. 1.75 +function freeze(object) { 1.76 + if (prototypeOf(object) === null) { 1.77 + Object.freeze(object); 1.78 + } 1.79 + else { 1.80 + prototypeOf(prototypeOf(object.isPrototypeOf)). 1.81 + constructor. // `Object` from the owner compartment. 1.82 + freeze(object); 1.83 + } 1.84 + return object; 1.85 +} 1.86 + 1.87 +// Returns map of given `object`-s own property descriptors. 1.88 +const descriptor = iced(function descriptor(object) { 1.89 + let value = {}; 1.90 + getOwnPropertyNames(object).forEach(function(name) { 1.91 + value[name] = getOwnPropertyDescriptor(object, name) 1.92 + }); 1.93 + return value; 1.94 +}); 1.95 +exports.descriptor = descriptor; 1.96 + 1.97 +// Freeze important built-ins so they can't be used by untrusted code as a 1.98 +// message passing channel. 1.99 +freeze(Object); 1.100 +freeze(Object.prototype); 1.101 +freeze(Function); 1.102 +freeze(Function.prototype); 1.103 +freeze(Array); 1.104 +freeze(Array.prototype); 1.105 +freeze(String); 1.106 +freeze(String.prototype); 1.107 + 1.108 +// This function takes `f` function sets it's `prototype` to undefined and 1.109 +// freezes it. We need to do this kind of deep freeze with all the exposed 1.110 +// functions so that untrusted code won't be able to use them a message 1.111 +// passing channel. 1.112 +function iced(f) { 1.113 + if (!Object.isFrozen(f)) { 1.114 + f.prototype = undefined; 1.115 + } 1.116 + return freeze(f); 1.117 +} 1.118 + 1.119 +// Defines own properties of given `properties` object on the given 1.120 +// target object overriding any existing property with a conflicting name. 1.121 +// Returns `target` object. Note we only export this function because it's 1.122 +// useful during loader bootstrap when other util modules can't be used & 1.123 +// thats only case where this export should be used. 1.124 +const override = iced(function override(target, source) { 1.125 + let properties = descriptor(target) 1.126 + let extension = descriptor(source || {}) 1.127 + getOwnPropertyNames(extension).forEach(function(name) { 1.128 + properties[name] = extension[name]; 1.129 + }); 1.130 + return define({}, properties); 1.131 +}); 1.132 +exports.override = override; 1.133 + 1.134 +function sourceURI(uri) { return String(uri).split(" -> ").pop(); } 1.135 +exports.sourceURI = iced(sourceURI); 1.136 + 1.137 +function isntLoaderFrame(frame) { return frame.fileName !== module.uri } 1.138 + 1.139 +function parseURI(uri) { return String(uri).split(" -> ").pop(); } 1.140 +exports.parseURI = parseURI; 1.141 + 1.142 +function parseStack(stack) { 1.143 + let lines = String(stack).split("\n"); 1.144 + return lines.reduce(function(frames, line) { 1.145 + if (line) { 1.146 + let atIndex = line.indexOf("@"); 1.147 + let columnIndex = line.lastIndexOf(":"); 1.148 + let lineIndex = line.lastIndexOf(":", columnIndex - 1); 1.149 + let fileName = parseURI(line.slice(atIndex + 1, lineIndex)); 1.150 + let lineNumber = parseInt(line.slice(lineIndex + 1, columnIndex)); 1.151 + let columnNumber = parseInt(line.slice(columnIndex + 1)); 1.152 + let name = line.slice(0, atIndex).split("(").shift(); 1.153 + frames.unshift({ 1.154 + fileName: fileName, 1.155 + name: name, 1.156 + lineNumber: lineNumber, 1.157 + columnNumber: columnNumber 1.158 + }); 1.159 + } 1.160 + return frames; 1.161 + }, []); 1.162 +} 1.163 +exports.parseStack = parseStack; 1.164 + 1.165 +function serializeStack(frames) { 1.166 + return frames.reduce(function(stack, frame) { 1.167 + return frame.name + "@" + 1.168 + frame.fileName + ":" + 1.169 + frame.lineNumber + ":" + 1.170 + frame.columnNumber + "\n" + 1.171 + stack; 1.172 + }, ""); 1.173 +} 1.174 +exports.serializeStack = serializeStack; 1.175 + 1.176 +function readURI(uri) { 1.177 + let stream = NetUtil.newChannel(uri, 'UTF-8', null).open(); 1.178 + let count = stream.available(); 1.179 + let data = NetUtil.readInputStreamToString(stream, count, { 1.180 + charset: 'UTF-8' 1.181 + }); 1.182 + 1.183 + stream.close(); 1.184 + 1.185 + return data; 1.186 +} 1.187 + 1.188 +// Combines all arguments into a resolved, normalized path 1.189 +function join (...paths) { 1.190 + let resolved = normalize(pathJoin(...paths)) 1.191 + // OS.File `normalize` strips out the second slash in 1.192 + // `resource://` or `chrome://`, and third slash in 1.193 + // `file:///`, so we work around this 1.194 + resolved = resolved.replace(/^resource\:\/([^\/])/, 'resource://$1'); 1.195 + resolved = resolved.replace(/^file\:\/([^\/])/, 'file:///$1'); 1.196 + resolved = resolved.replace(/^chrome\:\/([^\/])/, 'chrome://$1'); 1.197 + return resolved; 1.198 +} 1.199 +exports.join = join; 1.200 + 1.201 +// Function takes set of options and returns a JS sandbox. Function may be 1.202 +// passed set of options: 1.203 +// - `name`: A string value which identifies the sandbox in about:memory. Will 1.204 +// throw exception if omitted. 1.205 +// - `principal`: String URI or `nsIPrincipal` for the sandbox. Defaults to 1.206 +// system principal. 1.207 +// - `prototype`: Ancestor for the sandbox that will be created. Defaults to 1.208 +// `{}`. 1.209 +// - `wantXrays`: A Boolean value indicating whether code outside the sandbox 1.210 +// wants X-ray vision with respect to objects inside the sandbox. Defaults 1.211 +// to `true`. 1.212 +// - `sandbox`: A sandbox to share JS compartment with. If omitted new 1.213 +// compartment will be created. 1.214 +// - `metadata`: A metadata object associated with the sandbox. It should 1.215 +// be JSON-serializable. 1.216 +// For more details see: 1.217 +// https://developer.mozilla.org/en/Components.utils.Sandbox 1.218 +const Sandbox = iced(function Sandbox(options) { 1.219 + // Normalize options and rename to match `Cu.Sandbox` expectations. 1.220 + options = { 1.221 + // Do not expose `Components` if you really need them (bad idea!) you 1.222 + // still can expose via prototype. 1.223 + wantComponents: false, 1.224 + sandboxName: options.name, 1.225 + principal: 'principal' in options ? options.principal : systemPrincipal, 1.226 + wantXrays: 'wantXrays' in options ? options.wantXrays : true, 1.227 + wantGlobalProperties: 'wantGlobalProperties' in options ? 1.228 + options.wantGlobalProperties : [], 1.229 + sandboxPrototype: 'prototype' in options ? options.prototype : {}, 1.230 + invisibleToDebugger: 'invisibleToDebugger' in options ? 1.231 + options.invisibleToDebugger : false, 1.232 + metadata: 'metadata' in options ? options.metadata : {} 1.233 + }; 1.234 + 1.235 + let sandbox = Cu.Sandbox(options.principal, options); 1.236 + 1.237 + // Each sandbox at creation gets set of own properties that will be shadowing 1.238 + // ones from it's prototype. We override delete such `sandbox` properties 1.239 + // to avoid shadowing. 1.240 + delete sandbox.Iterator; 1.241 + delete sandbox.Components; 1.242 + delete sandbox.importFunction; 1.243 + delete sandbox.debug; 1.244 + 1.245 + return sandbox; 1.246 +}); 1.247 +exports.Sandbox = Sandbox; 1.248 + 1.249 +// Evaluates code from the given `uri` into given `sandbox`. If 1.250 +// `options.source` is passed, then that code is evaluated instead. 1.251 +// Optionally following options may be given: 1.252 +// - `options.encoding`: Source encoding, defaults to 'UTF-8'. 1.253 +// - `options.line`: Line number to start count from for stack traces. 1.254 +// Defaults to 1. 1.255 +// - `options.version`: Version of JS used, defaults to '1.8'. 1.256 +const evaluate = iced(function evaluate(sandbox, uri, options) { 1.257 + let { source, line, version, encoding } = override({ 1.258 + encoding: 'UTF-8', 1.259 + line: 1, 1.260 + version: '1.8', 1.261 + source: null 1.262 + }, options); 1.263 + 1.264 + return source ? Cu.evalInSandbox(source, sandbox, version, uri, line) 1.265 + : loadSubScript(uri, sandbox, encoding); 1.266 +}); 1.267 +exports.evaluate = evaluate; 1.268 + 1.269 +// Populates `exports` of the given CommonJS `module` object, in the context 1.270 +// of the given `loader` by evaluating code associated with it. 1.271 +const load = iced(function load(loader, module) { 1.272 + let { sandboxes, globals } = loader; 1.273 + let require = Require(loader, module); 1.274 + 1.275 + // We expose set of properties defined by `CommonJS` specification via 1.276 + // prototype of the sandbox. Also globals are deeper in the prototype 1.277 + // chain so that each module has access to them as well. 1.278 + let descriptors = descriptor({ 1.279 + require: require, 1.280 + module: module, 1.281 + exports: module.exports, 1.282 + get Components() { 1.283 + // Expose `Components` property to throw error on usage with 1.284 + // additional information 1.285 + throw new ReferenceError(COMPONENT_ERROR); 1.286 + } 1.287 + }); 1.288 + 1.289 + let sandbox = sandboxes[module.uri] = Sandbox({ 1.290 + name: module.uri, 1.291 + prototype: create(globals, descriptors), 1.292 + wantXrays: false, 1.293 + wantGlobalProperties: module.id == "sdk/indexed-db" ? ["indexedDB"] : [], 1.294 + invisibleToDebugger: loader.invisibleToDebugger, 1.295 + metadata: { 1.296 + addonID: loader.id, 1.297 + URI: module.uri 1.298 + } 1.299 + }); 1.300 + 1.301 + try { 1.302 + evaluate(sandbox, module.uri); 1.303 + } catch (error) { 1.304 + let { message, fileName, lineNumber } = error; 1.305 + let stack = error.stack || Error().stack; 1.306 + let frames = parseStack(stack).filter(isntLoaderFrame); 1.307 + let toString = String(error); 1.308 + let file = sourceURI(fileName); 1.309 + 1.310 + // Note that `String(error)` where error is from subscript loader does 1.311 + // not puts `:` after `"Error"` unlike regular errors thrown by JS code. 1.312 + // If there is a JS stack then this error has already been handled by an 1.313 + // inner module load. 1.314 + if (String(error) === "Error opening input stream (invalid filename?)") { 1.315 + let caller = frames.slice(0).pop(); 1.316 + fileName = caller.fileName; 1.317 + lineNumber = caller.lineNumber; 1.318 + message = "Module `" + module.id + "` is not found at " + module.uri; 1.319 + toString = message; 1.320 + } 1.321 + // Workaround for a Bug 910653. Errors thrown by subscript loader 1.322 + // do not include `stack` field and above created error won't have 1.323 + // fileName or lineNumber of the module being loaded, so we ensure 1.324 + // it does. 1.325 + else if (frames[frames.length - 1].fileName !== file) { 1.326 + frames.push({ fileName: file, lineNumber: lineNumber, name: "" }); 1.327 + } 1.328 + 1.329 + let prototype = typeof(error) === "object" ? error.constructor.prototype : 1.330 + Error.prototype; 1.331 + 1.332 + throw create(prototype, { 1.333 + message: { value: message, writable: true, configurable: true }, 1.334 + fileName: { value: fileName, writable: true, configurable: true }, 1.335 + lineNumber: { value: lineNumber, writable: true, configurable: true }, 1.336 + stack: { value: serializeStack(frames), writable: true, configurable: true }, 1.337 + toString: { value: function() toString, writable: true, configurable: true }, 1.338 + }); 1.339 + } 1.340 + 1.341 + if (module.exports && typeof(module.exports) === 'object') 1.342 + freeze(module.exports); 1.343 + 1.344 + return module; 1.345 +}); 1.346 +exports.load = load; 1.347 + 1.348 +// Utility function to normalize module `uri`s so they have `.js` extension. 1.349 +function normalizeExt (uri) { 1.350 + return isJSURI(uri) ? uri : 1.351 + isJSONURI(uri) ? uri : 1.352 + isJSMURI(uri) ? uri : 1.353 + uri + '.js'; 1.354 +} 1.355 + 1.356 +// Strips `rootURI` from `string` -- used to remove absolute resourceURI 1.357 +// from a relative path 1.358 +function stripBase (rootURI, string) { 1.359 + return string.replace(rootURI, './'); 1.360 +} 1.361 + 1.362 +// Utility function to join paths. In common case `base` is a 1.363 +// `requirer.uri` but in some cases it may be `baseURI`. In order to 1.364 +// avoid complexity we require `baseURI` with a trailing `/`. 1.365 +const resolve = iced(function resolve(id, base) { 1.366 + if (!isRelative(id)) return id; 1.367 + let basePaths = base.split('/'); 1.368 + // Pop the last element in the `base`, because it is from a 1.369 + // relative file 1.370 + // '../mod.js' from '/path/to/file.js' should resolve to '/path/mod.js' 1.371 + basePaths.pop(); 1.372 + if (!basePaths.length) 1.373 + return normalize(id); 1.374 + let resolved = join(basePaths.join('/'), id); 1.375 + 1.376 + // Joining and normalizing removes the './' from relative files. 1.377 + // We need to ensure the resolution still has the root 1.378 + if (isRelative(base)) 1.379 + resolved = './' + resolved; 1.380 + 1.381 + return resolved; 1.382 +}); 1.383 +exports.resolve = resolve; 1.384 + 1.385 +// Node-style module lookup 1.386 +// Takes an id and path and attempts to load a file using node's resolving 1.387 +// algorithm. 1.388 +// `id` should already be resolved relatively at this point. 1.389 +// http://nodejs.org/api/modules.html#modules_all_together 1.390 +const nodeResolve = iced(function nodeResolve(id, requirer, { manifest, rootURI }) { 1.391 + // Resolve again 1.392 + id = exports.resolve(id, requirer); 1.393 + 1.394 + // we assume that extensions are correct, i.e., a directory doesnt't have '.js' 1.395 + // and a js file isn't named 'file.json.js' 1.396 + 1.397 + let fullId = join(rootURI, id); 1.398 + 1.399 + let resolvedPath; 1.400 + if (resolvedPath = loadAsFile(fullId)) 1.401 + return stripBase(rootURI, resolvedPath); 1.402 + else if (resolvedPath = loadAsDirectory(fullId)) 1.403 + return stripBase(rootURI, resolvedPath); 1.404 + // If manifest has dependencies, attempt to look up node modules 1.405 + // in the `dependencies` list 1.406 + else if (manifest.dependencies) { 1.407 + let dirs = getNodeModulePaths(dirname(join(rootURI, requirer))).map(dir => join(dir, id)); 1.408 + for (let i = 0; i < dirs.length; i++) { 1.409 + if (resolvedPath = loadAsFile(dirs[i])) 1.410 + return stripBase(rootURI, resolvedPath); 1.411 + if (resolvedPath = loadAsDirectory(dirs[i])) 1.412 + return stripBase(rootURI, resolvedPath); 1.413 + } 1.414 + } 1.415 + 1.416 + // We would not find lookup for things like `sdk/tabs`, as that's part of 1.417 + // the alias mapping. If during `generateMap`, the runtime lookup resolves 1.418 + // with `resolveURI` -- if during runtime, then `resolve` will throw. 1.419 + return void 0; 1.420 +}); 1.421 +exports.nodeResolve = nodeResolve; 1.422 + 1.423 +// Attempts to load `path` and then `path.js` 1.424 +// Returns `path` with valid file, or `undefined` otherwise 1.425 +function loadAsFile (path) { 1.426 + let found; 1.427 + 1.428 + // As per node's loader spec, 1.429 + // we first should try and load 'path' (with no extension) 1.430 + // before trying 'path.js'. We will not support this feature 1.431 + // due to performance, but may add it if necessary for adoption. 1.432 + try { 1.433 + // Append '.js' to path name unless it's another support filetype 1.434 + path = normalizeExt(path); 1.435 + readURI(path); 1.436 + found = path; 1.437 + } catch (e) {} 1.438 + 1.439 + return found; 1.440 +} 1.441 + 1.442 +// Attempts to load `path/package.json`'s `main` entry, 1.443 +// followed by `path/index.js`, or `undefined` otherwise 1.444 +function loadAsDirectory (path) { 1.445 + let found; 1.446 + try { 1.447 + // If `path/package.json` exists, parse the `main` entry 1.448 + // and attempt to load that 1.449 + let main = getManifestMain(JSON.parse(readURI(path + '/package.json'))); 1.450 + if (main != null) { 1.451 + let tmpPath = join(path, main); 1.452 + if (found = loadAsFile(tmpPath)) 1.453 + return found 1.454 + } 1.455 + try { 1.456 + let tmpPath = path + '/index.js'; 1.457 + readURI(tmpPath); 1.458 + return tmpPath; 1.459 + } catch (e) {} 1.460 + } catch (e) { 1.461 + try { 1.462 + let tmpPath = path + '/index.js'; 1.463 + readURI(tmpPath); 1.464 + return tmpPath; 1.465 + } catch (e) {} 1.466 + } 1.467 + return void 0; 1.468 +} 1.469 + 1.470 +// From `resolve` module 1.471 +// https://github.com/substack/node-resolve/blob/master/lib/node-modules-paths.js 1.472 +function getNodeModulePaths (start) { 1.473 + // Configurable in node -- do we need this to be configurable? 1.474 + let moduleDir = 'node_modules'; 1.475 + 1.476 + let parts = start.split('/'); 1.477 + let dirs = []; 1.478 + for (let i = parts.length - 1; i >= 0; i--) { 1.479 + if (parts[i] === moduleDir) continue; 1.480 + let dir = join(parts.slice(0, i + 1).join('/'), moduleDir); 1.481 + dirs.push(dir); 1.482 + } 1.483 + return dirs; 1.484 +} 1.485 + 1.486 + 1.487 +function addTrailingSlash (path) { 1.488 + return !path ? null : !path.endsWith('/') ? path + '/' : path; 1.489 +} 1.490 + 1.491 +// Utility function to determine of module id `name` is a built in 1.492 +// module in node (fs, path, etc.); 1.493 +function isNodeModule (name) { 1.494 + return !!~NODE_MODULES.indexOf(name); 1.495 +} 1.496 + 1.497 +// Make mapping array that is sorted from longest path to shortest path 1.498 +// to allow overlays. Used by `resolveURI`, returns an array 1.499 +function sortPaths (paths) { 1.500 + return keys(paths). 1.501 + sort(function(a, b) { return b.length - a.length }). 1.502 + map(function(path) { return [ path, paths[path] ] }); 1.503 +} 1.504 + 1.505 +const resolveURI = iced(function resolveURI(id, mapping) { 1.506 + let count = mapping.length, index = 0; 1.507 + 1.508 + // Do not resolve if already a resource URI 1.509 + if (isResourceURI(id)) return normalizeExt(id); 1.510 + 1.511 + while (index < count) { 1.512 + let [ path, uri ] = mapping[index ++]; 1.513 + if (id.indexOf(path) === 0) 1.514 + return normalizeExt(id.replace(path, uri)); 1.515 + } 1.516 + return void 0; // otherwise we raise a warning, see bug 910304 1.517 +}); 1.518 +exports.resolveURI = resolveURI; 1.519 + 1.520 +// Creates version of `require` that will be exposed to the given `module` 1.521 +// in the context of the given `loader`. Each module gets own limited copy 1.522 +// of `require` that is allowed to load only a modules that are associated 1.523 +// with it during link time. 1.524 +const Require = iced(function Require(loader, requirer) { 1.525 + let { 1.526 + modules, mapping, resolve, load, manifest, rootURI, isNative, requireMap 1.527 + } = loader; 1.528 + 1.529 + function require(id) { 1.530 + if (!id) // Throw if `id` is not passed. 1.531 + throw Error('you must provide a module name when calling require() from ' 1.532 + + requirer.id, requirer.uri); 1.533 + 1.534 + let requirement; 1.535 + let uri; 1.536 + 1.537 + // TODO should get native Firefox modules before doing node-style lookups 1.538 + // to save on loading time 1.539 + 1.540 + if (isNative) { 1.541 + // If a requireMap is available from `generateMap`, use that to 1.542 + // immediately resolve the node-style mapping. 1.543 + if (requireMap && requireMap[requirer.id]) 1.544 + requirement = requireMap[requirer.id][id]; 1.545 + 1.546 + // For native modules, we want to check if it's a module specified 1.547 + // in 'modules', like `chrome`, or `@loader` -- if it exists, 1.548 + // just set the uri to skip resolution 1.549 + if (!requirement && modules[id]) 1.550 + uri = requirement = id; 1.551 + 1.552 + // If no requireMap was provided, or resolution not found in 1.553 + // the requireMap, and not a npm dependency, attempt a runtime lookup 1.554 + if (!requirement && !isNodeModule(id)) { 1.555 + // If `isNative` defined, this is using the new, native-style 1.556 + // loader, not cuddlefish, so lets resolve using node's algorithm 1.557 + // and get back a path that needs to be resolved via paths mapping 1.558 + // in `resolveURI` 1.559 + requirement = resolve(id, requirer.id, { 1.560 + manifest: manifest, 1.561 + rootURI: rootURI 1.562 + }); 1.563 + } 1.564 + 1.565 + // If not found in the map, not a node module, and wasn't able to be 1.566 + // looked up, it's something 1.567 + // found in the paths most likely, like `sdk/tabs`, which should 1.568 + // be resolved relatively if needed using traditional resolve 1.569 + if (!requirement) { 1.570 + requirement = isRelative(id) ? exports.resolve(id, requirer.id) : id; 1.571 + } 1.572 + } else { 1.573 + // Resolve `id` to its requirer if it's relative. 1.574 + requirement = requirer ? resolve(id, requirer.id) : id; 1.575 + } 1.576 + 1.577 + // Resolves `uri` of module using loaders resolve function. 1.578 + uri = uri || resolveURI(requirement, mapping); 1.579 + 1.580 + if (!uri) // Throw if `uri` can not be resolved. 1.581 + throw Error('Module: Can not resolve "' + id + '" module required by ' + 1.582 + requirer.id + ' located at ' + requirer.uri, requirer.uri); 1.583 + 1.584 + let module = null; 1.585 + // If module is already cached by loader then just use it. 1.586 + if (uri in modules) { 1.587 + module = modules[uri]; 1.588 + } 1.589 + else if (isJSMURI(uri)) { 1.590 + module = modules[uri] = Module(requirement, uri); 1.591 + module.exports = Cu.import(uri, {}); 1.592 + freeze(module); 1.593 + } 1.594 + else if (isJSONURI(uri)) { 1.595 + let data; 1.596 + 1.597 + // First attempt to load and parse json uri 1.598 + // ex: `test.json` 1.599 + // If that doesn't exist, check for `test.json.js` 1.600 + // for node parity 1.601 + try { 1.602 + data = JSON.parse(readURI(uri)); 1.603 + module = modules[uri] = Module(requirement, uri); 1.604 + module.exports = data; 1.605 + freeze(module); 1.606 + } 1.607 + catch (err) { 1.608 + // If error thrown from JSON parsing, throw that, do not 1.609 + // attempt to find .json.js file 1.610 + if (err && /JSON\.parse/.test(err.message)) 1.611 + throw err; 1.612 + uri = uri + '.js'; 1.613 + } 1.614 + } 1.615 + // If not yet cached, load and cache it. 1.616 + // We also freeze module to prevent it from further changes 1.617 + // at runtime. 1.618 + if (!(uri in modules)) { 1.619 + // Many of the loader's functionalities are dependent 1.620 + // on modules[uri] being set before loading, so we set it and 1.621 + // remove it if we have any errors. 1.622 + module = modules[uri] = Module(requirement, uri); 1.623 + try { 1.624 + freeze(load(loader, module)); 1.625 + } 1.626 + catch (e) { 1.627 + // Clear out modules cache so we can throw on a second invalid require 1.628 + delete modules[uri]; 1.629 + // Also clear out the Sandbox that was created 1.630 + delete loader.sandboxes[uri]; 1.631 + throw e; 1.632 + } 1.633 + } 1.634 + 1.635 + return module.exports; 1.636 + } 1.637 + // Make `require.main === module` evaluate to true in main module scope. 1.638 + require.main = loader.main === requirer ? requirer : undefined; 1.639 + return iced(require); 1.640 +}); 1.641 +exports.Require = Require; 1.642 + 1.643 +const main = iced(function main(loader, id) { 1.644 + // If no main entry provided, and native loader is used, 1.645 + // read the entry in the manifest 1.646 + if (!id && loader.isNative) 1.647 + id = getManifestMain(loader.manifest); 1.648 + let uri = resolveURI(id, loader.mapping); 1.649 + let module = loader.main = loader.modules[uri] = Module(id, uri); 1.650 + return loader.load(loader, module).exports; 1.651 +}); 1.652 +exports.main = main; 1.653 + 1.654 +// Makes module object that is made available to CommonJS modules when they 1.655 +// are evaluated, along with `exports` and `require`. 1.656 +const Module = iced(function Module(id, uri) { 1.657 + return create(null, { 1.658 + id: { enumerable: true, value: id }, 1.659 + exports: { enumerable: true, writable: true, value: create(null) }, 1.660 + uri: { value: uri } 1.661 + }); 1.662 +}); 1.663 +exports.Module = Module; 1.664 + 1.665 +// Takes `loader`, and unload `reason` string and notifies all observers that 1.666 +// they should cleanup after them-self. 1.667 +const unload = iced(function unload(loader, reason) { 1.668 + // subject is a unique object created per loader instance. 1.669 + // This allows any code to cleanup on loader unload regardless of how 1.670 + // it was loaded. To handle unload for specific loader subject may be 1.671 + // asserted against loader.destructor or require('@loader/unload') 1.672 + // Note: We don not destroy loader's module cache or sandboxes map as 1.673 + // some modules may do cleanup in subsequent turns of event loop. Destroying 1.674 + // cache may cause module identity problems in such cases. 1.675 + let subject = { wrappedJSObject: loader.destructor }; 1.676 + notifyObservers(subject, 'sdk:loader:destroy', reason); 1.677 +}); 1.678 +exports.unload = unload; 1.679 + 1.680 +// Function makes new loader that can be used to load CommonJS modules 1.681 +// described by a given `options.manifest`. Loader takes following options: 1.682 +// - `globals`: Optional map of globals, that all module scopes will inherit 1.683 +// from. Map is also exposed under `globals` property of the returned loader 1.684 +// so it can be extended further later. Defaults to `{}`. 1.685 +// - `modules` Optional map of built-in module exports mapped by module id. 1.686 +// These modules will incorporated into module cache. Each module will be 1.687 +// frozen. 1.688 +// - `resolve` Optional module `id` resolution function. If given it will be 1.689 +// used to resolve module URIs, by calling it with require term, requirer 1.690 +// module object (that has `uri` property) and `baseURI` of the loader. 1.691 +// If `resolve` does not returns `uri` string exception will be thrown by 1.692 +// an associated `require` call. 1.693 +const Loader = iced(function Loader(options) { 1.694 + let { 1.695 + modules, globals, resolve, paths, rootURI, manifest, requireMap, isNative 1.696 + } = override({ 1.697 + paths: {}, 1.698 + modules: {}, 1.699 + globals: { 1.700 + console: console 1.701 + }, 1.702 + resolve: options.isNative ? 1.703 + exports.nodeResolve : 1.704 + exports.resolve, 1.705 + }, options); 1.706 + 1.707 + // We create an identity object that will be dispatched on an unload 1.708 + // event as subject. This way unload listeners will be able to assert 1.709 + // which loader is unloaded. Please note that we intentionally don't 1.710 + // use `loader` as subject to prevent a loader access leakage through 1.711 + // observer notifications. 1.712 + let destructor = freeze(create(null)); 1.713 + 1.714 + let mapping = sortPaths(paths); 1.715 + 1.716 + // Define pseudo modules. 1.717 + modules = override({ 1.718 + '@loader/unload': destructor, 1.719 + '@loader/options': options, 1.720 + 'chrome': { Cc: Cc, Ci: Ci, Cu: Cu, Cr: Cr, Cm: Cm, 1.721 + CC: bind(CC, Components), components: Components, 1.722 + // `ChromeWorker` has to be inject in loader global scope. 1.723 + // It is done by bootstrap.js:loadSandbox for the SDK. 1.724 + ChromeWorker: ChromeWorker 1.725 + } 1.726 + }, modules); 1.727 + 1.728 + modules = keys(modules).reduce(function(result, id) { 1.729 + // We resolve `uri` from `id` since modules are cached by `uri`. 1.730 + let uri = resolveURI(id, mapping); 1.731 + // In native loader, the mapping will not contain values for 1.732 + // pseudomodules -- store them as their ID rather than the URI 1.733 + if (isNative && !uri) 1.734 + uri = id; 1.735 + let module = Module(id, uri); 1.736 + module.exports = freeze(modules[id]); 1.737 + result[uri] = freeze(module); 1.738 + return result; 1.739 + }, {}); 1.740 + 1.741 + // Loader object is just a representation of a environment 1.742 + // state. We freeze it and mark make it's properties non-enumerable 1.743 + // as they are pure implementation detail that no one should rely upon. 1.744 + let returnObj = { 1.745 + destructor: { enumerable: false, value: destructor }, 1.746 + globals: { enumerable: false, value: globals }, 1.747 + mapping: { enumerable: false, value: mapping }, 1.748 + // Map of module objects indexed by module URIs. 1.749 + modules: { enumerable: false, value: modules }, 1.750 + // Map of module sandboxes indexed by module URIs. 1.751 + sandboxes: { enumerable: false, value: {} }, 1.752 + resolve: { enumerable: false, value: resolve }, 1.753 + // ID of the addon, if provided. 1.754 + id: { enumerable: false, value: options.id }, 1.755 + // Whether the modules loaded should be ignored by the debugger 1.756 + invisibleToDebugger: { enumerable: false, 1.757 + value: options.invisibleToDebugger || false }, 1.758 + load: { enumerable: false, value: options.load || load }, 1.759 + // Main (entry point) module, it can be set only once, since loader 1.760 + // instance can have only one main module. 1.761 + main: new function() { 1.762 + let main; 1.763 + return { 1.764 + enumerable: false, 1.765 + get: function() { return main; }, 1.766 + // Only set main if it has not being set yet! 1.767 + set: function(module) { main = main || module; } 1.768 + } 1.769 + } 1.770 + }; 1.771 + 1.772 + if (isNative) { 1.773 + returnObj.isNative = { enumerable: false, value: true }; 1.774 + returnObj.manifest = { enumerable: false, value: manifest }; 1.775 + returnObj.requireMap = { enumerable: false, value: requireMap }; 1.776 + returnObj.rootURI = { enumerable: false, value: addTrailingSlash(rootURI) }; 1.777 + } 1.778 + 1.779 + return freeze(create(null, returnObj)); 1.780 +}); 1.781 +exports.Loader = Loader; 1.782 + 1.783 +let isJSONURI = uri => uri.substr(-5) === '.json'; 1.784 +let isJSMURI = uri => uri.substr(-4) === '.jsm'; 1.785 +let isJSURI = uri => uri.substr(-3) === '.js'; 1.786 +let isResourceURI = uri => uri.substr(0, 11) === 'resource://'; 1.787 +let isRelative = id => id[0] === '.' 1.788 + 1.789 +const generateMap = iced(function generateMap(options, callback) { 1.790 + let { rootURI, resolve, paths } = override({ 1.791 + paths: {}, 1.792 + resolve: exports.nodeResolve 1.793 + }, options); 1.794 + 1.795 + rootURI = addTrailingSlash(rootURI); 1.796 + 1.797 + let manifest; 1.798 + let manifestURI = join(rootURI, 'package.json'); 1.799 + 1.800 + if (rootURI) 1.801 + manifest = JSON.parse(readURI(manifestURI)); 1.802 + else 1.803 + throw new Error('No `rootURI` given to generate map'); 1.804 + 1.805 + let main = getManifestMain(manifest); 1.806 + 1.807 + findAllModuleIncludes(main, { 1.808 + resolve: resolve, 1.809 + manifest: manifest, 1.810 + rootURI: rootURI 1.811 + }, {}, callback); 1.812 + 1.813 +}); 1.814 +exports.generateMap = generateMap; 1.815 + 1.816 +// Default `main` entry to './index.js' and ensure is relative, 1.817 +// since node allows 'lib/index.js' without relative `./` 1.818 +function getManifestMain (manifest) { 1.819 + let main = manifest.main || './index.js'; 1.820 + return isRelative(main) ? main : './' + main; 1.821 +} 1.822 + 1.823 +function findAllModuleIncludes (uri, options, results, callback) { 1.824 + let { resolve, manifest, rootURI } = options; 1.825 + results = results || {}; 1.826 + 1.827 + // Abort if JSON or JSM 1.828 + if (isJSONURI(uri) || isJSMURI(uri)) { 1.829 + callback(results); 1.830 + return void 0; 1.831 + } 1.832 + 1.833 + findModuleIncludes(join(rootURI, uri), modules => { 1.834 + // If no modules are included in the file, just call callback immediately 1.835 + if (!modules.length) { 1.836 + callback(results); 1.837 + return void 0; 1.838 + } 1.839 + 1.840 + results[uri] = modules.reduce((agg, mod) => { 1.841 + let resolved = resolve(mod, uri, { manifest: manifest, rootURI: rootURI }); 1.842 + 1.843 + // If resolution found, store the resolution; otherwise, 1.844 + // skip storing it as runtime lookup will handle this 1.845 + if (!resolved) 1.846 + return agg; 1.847 + agg[mod] = resolved; 1.848 + return agg; 1.849 + }, {}); 1.850 + 1.851 + let includes = keys(results[uri]); 1.852 + let count = 0; 1.853 + let subcallback = () => { if (++count >= includes.length) callback(results) }; 1.854 + includes.map(id => { 1.855 + let moduleURI = results[uri][id]; 1.856 + if (!results[moduleURI]) 1.857 + findAllModuleIncludes(moduleURI, options, results, subcallback); 1.858 + else 1.859 + subcallback(); 1.860 + }); 1.861 + }); 1.862 +} 1.863 + 1.864 +// From Substack's detector 1.865 +// https://github.com/substack/node-detective 1.866 +// 1.867 +// Given a resource URI or source, return an array of strings passed into 1.868 +// the require statements from the source 1.869 +function findModuleIncludes (uri, callback) { 1.870 + let src = isResourceURI(uri) ? readURI(uri) : uri; 1.871 + let modules = []; 1.872 + 1.873 + walk(src, function (node) { 1.874 + if (isRequire(node)) 1.875 + modules.push(node.arguments[0].value); 1.876 + }); 1.877 + 1.878 + callback(modules); 1.879 +} 1.880 + 1.881 +function walk (src, callback) { 1.882 + let nodes = Reflect.parse(src); 1.883 + traverse(nodes, callback); 1.884 +} 1.885 + 1.886 +function traverse (node, cb) { 1.887 + if (Array.isArray(node)) { 1.888 + node.map(x => { 1.889 + if (x != null) { 1.890 + x.parent = node; 1.891 + traverse(x, cb); 1.892 + } 1.893 + }); 1.894 + } 1.895 + else if (node && typeof node === 'object') { 1.896 + cb(node); 1.897 + keys(node).map(key => { 1.898 + if (key === 'parent' || !node[key]) return; 1.899 + node[key].parent = node; 1.900 + traverse(node[key], cb); 1.901 + }); 1.902 + } 1.903 +} 1.904 + 1.905 +// From Substack's detector 1.906 +// https://github.com/substack/node-detective 1.907 +// Check an AST node to see if its a require statement. 1.908 +// A modification added to only evaluate to true if it actually 1.909 +// has a value being passed in as an argument 1.910 +function isRequire (node) { 1.911 + var c = node.callee; 1.912 + return c 1.913 + && node.type === 'CallExpression' 1.914 + && c.type === 'Identifier' 1.915 + && c.name === 'require' 1.916 + && node.arguments.length 1.917 + && node.arguments[0].type === 'Literal'; 1.918 +} 1.919 + 1.920 +}); 1.921 +