michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: ;(function(id, factory) { // Module boilerplate :( michael@0: if (typeof(define) === 'function') { // RequireJS michael@0: define(factory); michael@0: } else if (typeof(require) === 'function') { // CommonJS michael@0: factory.call(this, require, exports, module); michael@0: } else if (~String(this).indexOf('BackstagePass')) { // JSM michael@0: this[factory.name] = {}; michael@0: factory(function require(uri) { michael@0: var imports = {}; michael@0: this['Components'].utils.import(uri, imports); michael@0: return imports; michael@0: }, this[factory.name], { uri: __URI__, id: id }); michael@0: this.EXPORTED_SYMBOLS = [factory.name]; michael@0: } else if (~String(this).indexOf('Sandbox')) { // Sandbox michael@0: factory(function require(uri) {}, this, { uri: __URI__, id: id }); michael@0: } else { // Browser or alike michael@0: var globals = this michael@0: factory(function require(id) { michael@0: return globals[id]; michael@0: }, (globals[id] = {}), { uri: document.location.href + '#' + id, id: id }); michael@0: } michael@0: }).call(this, 'loader', function Loader(require, exports, module) { michael@0: michael@0: 'use strict'; michael@0: michael@0: module.metadata = { michael@0: "stability": "unstable" michael@0: }; michael@0: michael@0: const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu, michael@0: results: Cr, manager: Cm } = Components; michael@0: const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')(); michael@0: const { loadSubScript } = Cc['@mozilla.org/moz/jssubscript-loader;1']. michael@0: getService(Ci.mozIJSSubScriptLoader); michael@0: const { notifyObservers } = Cc['@mozilla.org/observer-service;1']. michael@0: getService(Ci.nsIObserverService); michael@0: const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {}); michael@0: const { Reflect } = Cu.import("resource://gre/modules/reflect.jsm", {}); michael@0: const { console } = Cu.import("resource://gre/modules/devtools/Console.jsm"); michael@0: const { join: pathJoin, normalize, dirname } = Cu.import("resource://gre/modules/osfile/ospath_unix.jsm"); michael@0: michael@0: // Define some shortcuts. michael@0: const bind = Function.call.bind(Function.bind); michael@0: const getOwnPropertyNames = Object.getOwnPropertyNames; michael@0: const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; michael@0: const define = Object.defineProperties; michael@0: const prototypeOf = Object.getPrototypeOf; michael@0: const create = Object.create; michael@0: const keys = Object.keys; michael@0: michael@0: 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"]; michael@0: michael@0: const COMPONENT_ERROR = '`Components` is not available in this context.\n' + michael@0: 'Functionality provided by Components may be available in an SDK\n' + michael@0: 'module: https://jetpack.mozillalabs.com/sdk/latest/docs/ \n\n' + michael@0: 'However, if you still need to import Components, you may use the\n' + michael@0: '`chrome` module\'s properties for shortcuts to Component properties:\n\n' + michael@0: 'Shortcuts: \n' + michael@0: ' Cc = Components' + '.classes \n' + michael@0: ' Ci = Components' + '.interfaces \n' + michael@0: ' Cu = Components' + '.utils \n' + michael@0: ' CC = Components' + '.Constructor \n' + michael@0: 'Example: \n' + michael@0: ' let { Cc, Ci } = require(\'chrome\');\n'; michael@0: michael@0: // Workaround for bug 674195. Freezing objects from other compartments fail, michael@0: // so we use `Object.freeze` from the same component instead. michael@0: function freeze(object) { michael@0: if (prototypeOf(object) === null) { michael@0: Object.freeze(object); michael@0: } michael@0: else { michael@0: prototypeOf(prototypeOf(object.isPrototypeOf)). michael@0: constructor. // `Object` from the owner compartment. michael@0: freeze(object); michael@0: } michael@0: return object; michael@0: } michael@0: michael@0: // Returns map of given `object`-s own property descriptors. michael@0: const descriptor = iced(function descriptor(object) { michael@0: let value = {}; michael@0: getOwnPropertyNames(object).forEach(function(name) { michael@0: value[name] = getOwnPropertyDescriptor(object, name) michael@0: }); michael@0: return value; michael@0: }); michael@0: exports.descriptor = descriptor; michael@0: michael@0: // Freeze important built-ins so they can't be used by untrusted code as a michael@0: // message passing channel. michael@0: freeze(Object); michael@0: freeze(Object.prototype); michael@0: freeze(Function); michael@0: freeze(Function.prototype); michael@0: freeze(Array); michael@0: freeze(Array.prototype); michael@0: freeze(String); michael@0: freeze(String.prototype); michael@0: michael@0: // This function takes `f` function sets it's `prototype` to undefined and michael@0: // freezes it. We need to do this kind of deep freeze with all the exposed michael@0: // functions so that untrusted code won't be able to use them a message michael@0: // passing channel. michael@0: function iced(f) { michael@0: if (!Object.isFrozen(f)) { michael@0: f.prototype = undefined; michael@0: } michael@0: return freeze(f); michael@0: } michael@0: michael@0: // Defines own properties of given `properties` object on the given michael@0: // target object overriding any existing property with a conflicting name. michael@0: // Returns `target` object. Note we only export this function because it's michael@0: // useful during loader bootstrap when other util modules can't be used & michael@0: // thats only case where this export should be used. michael@0: const override = iced(function override(target, source) { michael@0: let properties = descriptor(target) michael@0: let extension = descriptor(source || {}) michael@0: getOwnPropertyNames(extension).forEach(function(name) { michael@0: properties[name] = extension[name]; michael@0: }); michael@0: return define({}, properties); michael@0: }); michael@0: exports.override = override; michael@0: michael@0: function sourceURI(uri) { return String(uri).split(" -> ").pop(); } michael@0: exports.sourceURI = iced(sourceURI); michael@0: michael@0: function isntLoaderFrame(frame) { return frame.fileName !== module.uri } michael@0: michael@0: function parseURI(uri) { return String(uri).split(" -> ").pop(); } michael@0: exports.parseURI = parseURI; michael@0: michael@0: function parseStack(stack) { michael@0: let lines = String(stack).split("\n"); michael@0: return lines.reduce(function(frames, line) { michael@0: if (line) { michael@0: let atIndex = line.indexOf("@"); michael@0: let columnIndex = line.lastIndexOf(":"); michael@0: let lineIndex = line.lastIndexOf(":", columnIndex - 1); michael@0: let fileName = parseURI(line.slice(atIndex + 1, lineIndex)); michael@0: let lineNumber = parseInt(line.slice(lineIndex + 1, columnIndex)); michael@0: let columnNumber = parseInt(line.slice(columnIndex + 1)); michael@0: let name = line.slice(0, atIndex).split("(").shift(); michael@0: frames.unshift({ michael@0: fileName: fileName, michael@0: name: name, michael@0: lineNumber: lineNumber, michael@0: columnNumber: columnNumber michael@0: }); michael@0: } michael@0: return frames; michael@0: }, []); michael@0: } michael@0: exports.parseStack = parseStack; michael@0: michael@0: function serializeStack(frames) { michael@0: return frames.reduce(function(stack, frame) { michael@0: return frame.name + "@" + michael@0: frame.fileName + ":" + michael@0: frame.lineNumber + ":" + michael@0: frame.columnNumber + "\n" + michael@0: stack; michael@0: }, ""); michael@0: } michael@0: exports.serializeStack = serializeStack; michael@0: michael@0: function readURI(uri) { michael@0: let stream = NetUtil.newChannel(uri, 'UTF-8', null).open(); michael@0: let count = stream.available(); michael@0: let data = NetUtil.readInputStreamToString(stream, count, { michael@0: charset: 'UTF-8' michael@0: }); michael@0: michael@0: stream.close(); michael@0: michael@0: return data; michael@0: } michael@0: michael@0: // Combines all arguments into a resolved, normalized path michael@0: function join (...paths) { michael@0: let resolved = normalize(pathJoin(...paths)) michael@0: // OS.File `normalize` strips out the second slash in michael@0: // `resource://` or `chrome://`, and third slash in michael@0: // `file:///`, so we work around this michael@0: resolved = resolved.replace(/^resource\:\/([^\/])/, 'resource://$1'); michael@0: resolved = resolved.replace(/^file\:\/([^\/])/, 'file:///$1'); michael@0: resolved = resolved.replace(/^chrome\:\/([^\/])/, 'chrome://$1'); michael@0: return resolved; michael@0: } michael@0: exports.join = join; michael@0: michael@0: // Function takes set of options and returns a JS sandbox. Function may be michael@0: // passed set of options: michael@0: // - `name`: A string value which identifies the sandbox in about:memory. Will michael@0: // throw exception if omitted. michael@0: // - `principal`: String URI or `nsIPrincipal` for the sandbox. Defaults to michael@0: // system principal. michael@0: // - `prototype`: Ancestor for the sandbox that will be created. Defaults to michael@0: // `{}`. michael@0: // - `wantXrays`: A Boolean value indicating whether code outside the sandbox michael@0: // wants X-ray vision with respect to objects inside the sandbox. Defaults michael@0: // to `true`. michael@0: // - `sandbox`: A sandbox to share JS compartment with. If omitted new michael@0: // compartment will be created. michael@0: // - `metadata`: A metadata object associated with the sandbox. It should michael@0: // be JSON-serializable. michael@0: // For more details see: michael@0: // https://developer.mozilla.org/en/Components.utils.Sandbox michael@0: const Sandbox = iced(function Sandbox(options) { michael@0: // Normalize options and rename to match `Cu.Sandbox` expectations. michael@0: options = { michael@0: // Do not expose `Components` if you really need them (bad idea!) you michael@0: // still can expose via prototype. michael@0: wantComponents: false, michael@0: sandboxName: options.name, michael@0: principal: 'principal' in options ? options.principal : systemPrincipal, michael@0: wantXrays: 'wantXrays' in options ? options.wantXrays : true, michael@0: wantGlobalProperties: 'wantGlobalProperties' in options ? michael@0: options.wantGlobalProperties : [], michael@0: sandboxPrototype: 'prototype' in options ? options.prototype : {}, michael@0: invisibleToDebugger: 'invisibleToDebugger' in options ? michael@0: options.invisibleToDebugger : false, michael@0: metadata: 'metadata' in options ? options.metadata : {} michael@0: }; michael@0: michael@0: let sandbox = Cu.Sandbox(options.principal, options); michael@0: michael@0: // Each sandbox at creation gets set of own properties that will be shadowing michael@0: // ones from it's prototype. We override delete such `sandbox` properties michael@0: // to avoid shadowing. michael@0: delete sandbox.Iterator; michael@0: delete sandbox.Components; michael@0: delete sandbox.importFunction; michael@0: delete sandbox.debug; michael@0: michael@0: return sandbox; michael@0: }); michael@0: exports.Sandbox = Sandbox; michael@0: michael@0: // Evaluates code from the given `uri` into given `sandbox`. If michael@0: // `options.source` is passed, then that code is evaluated instead. michael@0: // Optionally following options may be given: michael@0: // - `options.encoding`: Source encoding, defaults to 'UTF-8'. michael@0: // - `options.line`: Line number to start count from for stack traces. michael@0: // Defaults to 1. michael@0: // - `options.version`: Version of JS used, defaults to '1.8'. michael@0: const evaluate = iced(function evaluate(sandbox, uri, options) { michael@0: let { source, line, version, encoding } = override({ michael@0: encoding: 'UTF-8', michael@0: line: 1, michael@0: version: '1.8', michael@0: source: null michael@0: }, options); michael@0: michael@0: return source ? Cu.evalInSandbox(source, sandbox, version, uri, line) michael@0: : loadSubScript(uri, sandbox, encoding); michael@0: }); michael@0: exports.evaluate = evaluate; michael@0: michael@0: // Populates `exports` of the given CommonJS `module` object, in the context michael@0: // of the given `loader` by evaluating code associated with it. michael@0: const load = iced(function load(loader, module) { michael@0: let { sandboxes, globals } = loader; michael@0: let require = Require(loader, module); michael@0: michael@0: // We expose set of properties defined by `CommonJS` specification via michael@0: // prototype of the sandbox. Also globals are deeper in the prototype michael@0: // chain so that each module has access to them as well. michael@0: let descriptors = descriptor({ michael@0: require: require, michael@0: module: module, michael@0: exports: module.exports, michael@0: get Components() { michael@0: // Expose `Components` property to throw error on usage with michael@0: // additional information michael@0: throw new ReferenceError(COMPONENT_ERROR); michael@0: } michael@0: }); michael@0: michael@0: let sandbox = sandboxes[module.uri] = Sandbox({ michael@0: name: module.uri, michael@0: prototype: create(globals, descriptors), michael@0: wantXrays: false, michael@0: wantGlobalProperties: module.id == "sdk/indexed-db" ? ["indexedDB"] : [], michael@0: invisibleToDebugger: loader.invisibleToDebugger, michael@0: metadata: { michael@0: addonID: loader.id, michael@0: URI: module.uri michael@0: } michael@0: }); michael@0: michael@0: try { michael@0: evaluate(sandbox, module.uri); michael@0: } catch (error) { michael@0: let { message, fileName, lineNumber } = error; michael@0: let stack = error.stack || Error().stack; michael@0: let frames = parseStack(stack).filter(isntLoaderFrame); michael@0: let toString = String(error); michael@0: let file = sourceURI(fileName); michael@0: michael@0: // Note that `String(error)` where error is from subscript loader does michael@0: // not puts `:` after `"Error"` unlike regular errors thrown by JS code. michael@0: // If there is a JS stack then this error has already been handled by an michael@0: // inner module load. michael@0: if (String(error) === "Error opening input stream (invalid filename?)") { michael@0: let caller = frames.slice(0).pop(); michael@0: fileName = caller.fileName; michael@0: lineNumber = caller.lineNumber; michael@0: message = "Module `" + module.id + "` is not found at " + module.uri; michael@0: toString = message; michael@0: } michael@0: // Workaround for a Bug 910653. Errors thrown by subscript loader michael@0: // do not include `stack` field and above created error won't have michael@0: // fileName or lineNumber of the module being loaded, so we ensure michael@0: // it does. michael@0: else if (frames[frames.length - 1].fileName !== file) { michael@0: frames.push({ fileName: file, lineNumber: lineNumber, name: "" }); michael@0: } michael@0: michael@0: let prototype = typeof(error) === "object" ? error.constructor.prototype : michael@0: Error.prototype; michael@0: michael@0: throw create(prototype, { michael@0: message: { value: message, writable: true, configurable: true }, michael@0: fileName: { value: fileName, writable: true, configurable: true }, michael@0: lineNumber: { value: lineNumber, writable: true, configurable: true }, michael@0: stack: { value: serializeStack(frames), writable: true, configurable: true }, michael@0: toString: { value: function() toString, writable: true, configurable: true }, michael@0: }); michael@0: } michael@0: michael@0: if (module.exports && typeof(module.exports) === 'object') michael@0: freeze(module.exports); michael@0: michael@0: return module; michael@0: }); michael@0: exports.load = load; michael@0: michael@0: // Utility function to normalize module `uri`s so they have `.js` extension. michael@0: function normalizeExt (uri) { michael@0: return isJSURI(uri) ? uri : michael@0: isJSONURI(uri) ? uri : michael@0: isJSMURI(uri) ? uri : michael@0: uri + '.js'; michael@0: } michael@0: michael@0: // Strips `rootURI` from `string` -- used to remove absolute resourceURI michael@0: // from a relative path michael@0: function stripBase (rootURI, string) { michael@0: return string.replace(rootURI, './'); michael@0: } michael@0: michael@0: // Utility function to join paths. In common case `base` is a michael@0: // `requirer.uri` but in some cases it may be `baseURI`. In order to michael@0: // avoid complexity we require `baseURI` with a trailing `/`. michael@0: const resolve = iced(function resolve(id, base) { michael@0: if (!isRelative(id)) return id; michael@0: let basePaths = base.split('/'); michael@0: // Pop the last element in the `base`, because it is from a michael@0: // relative file michael@0: // '../mod.js' from '/path/to/file.js' should resolve to '/path/mod.js' michael@0: basePaths.pop(); michael@0: if (!basePaths.length) michael@0: return normalize(id); michael@0: let resolved = join(basePaths.join('/'), id); michael@0: michael@0: // Joining and normalizing removes the './' from relative files. michael@0: // We need to ensure the resolution still has the root michael@0: if (isRelative(base)) michael@0: resolved = './' + resolved; michael@0: michael@0: return resolved; michael@0: }); michael@0: exports.resolve = resolve; michael@0: michael@0: // Node-style module lookup michael@0: // Takes an id and path and attempts to load a file using node's resolving michael@0: // algorithm. michael@0: // `id` should already be resolved relatively at this point. michael@0: // http://nodejs.org/api/modules.html#modules_all_together michael@0: const nodeResolve = iced(function nodeResolve(id, requirer, { manifest, rootURI }) { michael@0: // Resolve again michael@0: id = exports.resolve(id, requirer); michael@0: michael@0: // we assume that extensions are correct, i.e., a directory doesnt't have '.js' michael@0: // and a js file isn't named 'file.json.js' michael@0: michael@0: let fullId = join(rootURI, id); michael@0: michael@0: let resolvedPath; michael@0: if (resolvedPath = loadAsFile(fullId)) michael@0: return stripBase(rootURI, resolvedPath); michael@0: else if (resolvedPath = loadAsDirectory(fullId)) michael@0: return stripBase(rootURI, resolvedPath); michael@0: // If manifest has dependencies, attempt to look up node modules michael@0: // in the `dependencies` list michael@0: else if (manifest.dependencies) { michael@0: let dirs = getNodeModulePaths(dirname(join(rootURI, requirer))).map(dir => join(dir, id)); michael@0: for (let i = 0; i < dirs.length; i++) { michael@0: if (resolvedPath = loadAsFile(dirs[i])) michael@0: return stripBase(rootURI, resolvedPath); michael@0: if (resolvedPath = loadAsDirectory(dirs[i])) michael@0: return stripBase(rootURI, resolvedPath); michael@0: } michael@0: } michael@0: michael@0: // We would not find lookup for things like `sdk/tabs`, as that's part of michael@0: // the alias mapping. If during `generateMap`, the runtime lookup resolves michael@0: // with `resolveURI` -- if during runtime, then `resolve` will throw. michael@0: return void 0; michael@0: }); michael@0: exports.nodeResolve = nodeResolve; michael@0: michael@0: // Attempts to load `path` and then `path.js` michael@0: // Returns `path` with valid file, or `undefined` otherwise michael@0: function loadAsFile (path) { michael@0: let found; michael@0: michael@0: // As per node's loader spec, michael@0: // we first should try and load 'path' (with no extension) michael@0: // before trying 'path.js'. We will not support this feature michael@0: // due to performance, but may add it if necessary for adoption. michael@0: try { michael@0: // Append '.js' to path name unless it's another support filetype michael@0: path = normalizeExt(path); michael@0: readURI(path); michael@0: found = path; michael@0: } catch (e) {} michael@0: michael@0: return found; michael@0: } michael@0: michael@0: // Attempts to load `path/package.json`'s `main` entry, michael@0: // followed by `path/index.js`, or `undefined` otherwise michael@0: function loadAsDirectory (path) { michael@0: let found; michael@0: try { michael@0: // If `path/package.json` exists, parse the `main` entry michael@0: // and attempt to load that michael@0: let main = getManifestMain(JSON.parse(readURI(path + '/package.json'))); michael@0: if (main != null) { michael@0: let tmpPath = join(path, main); michael@0: if (found = loadAsFile(tmpPath)) michael@0: return found michael@0: } michael@0: try { michael@0: let tmpPath = path + '/index.js'; michael@0: readURI(tmpPath); michael@0: return tmpPath; michael@0: } catch (e) {} michael@0: } catch (e) { michael@0: try { michael@0: let tmpPath = path + '/index.js'; michael@0: readURI(tmpPath); michael@0: return tmpPath; michael@0: } catch (e) {} michael@0: } michael@0: return void 0; michael@0: } michael@0: michael@0: // From `resolve` module michael@0: // https://github.com/substack/node-resolve/blob/master/lib/node-modules-paths.js michael@0: function getNodeModulePaths (start) { michael@0: // Configurable in node -- do we need this to be configurable? michael@0: let moduleDir = 'node_modules'; michael@0: michael@0: let parts = start.split('/'); michael@0: let dirs = []; michael@0: for (let i = parts.length - 1; i >= 0; i--) { michael@0: if (parts[i] === moduleDir) continue; michael@0: let dir = join(parts.slice(0, i + 1).join('/'), moduleDir); michael@0: dirs.push(dir); michael@0: } michael@0: return dirs; michael@0: } michael@0: michael@0: michael@0: function addTrailingSlash (path) { michael@0: return !path ? null : !path.endsWith('/') ? path + '/' : path; michael@0: } michael@0: michael@0: // Utility function to determine of module id `name` is a built in michael@0: // module in node (fs, path, etc.); michael@0: function isNodeModule (name) { michael@0: return !!~NODE_MODULES.indexOf(name); michael@0: } michael@0: michael@0: // Make mapping array that is sorted from longest path to shortest path michael@0: // to allow overlays. Used by `resolveURI`, returns an array michael@0: function sortPaths (paths) { michael@0: return keys(paths). michael@0: sort(function(a, b) { return b.length - a.length }). michael@0: map(function(path) { return [ path, paths[path] ] }); michael@0: } michael@0: michael@0: const resolveURI = iced(function resolveURI(id, mapping) { michael@0: let count = mapping.length, index = 0; michael@0: michael@0: // Do not resolve if already a resource URI michael@0: if (isResourceURI(id)) return normalizeExt(id); michael@0: michael@0: while (index < count) { michael@0: let [ path, uri ] = mapping[index ++]; michael@0: if (id.indexOf(path) === 0) michael@0: return normalizeExt(id.replace(path, uri)); michael@0: } michael@0: return void 0; // otherwise we raise a warning, see bug 910304 michael@0: }); michael@0: exports.resolveURI = resolveURI; michael@0: michael@0: // Creates version of `require` that will be exposed to the given `module` michael@0: // in the context of the given `loader`. Each module gets own limited copy michael@0: // of `require` that is allowed to load only a modules that are associated michael@0: // with it during link time. michael@0: const Require = iced(function Require(loader, requirer) { michael@0: let { michael@0: modules, mapping, resolve, load, manifest, rootURI, isNative, requireMap michael@0: } = loader; michael@0: michael@0: function require(id) { michael@0: if (!id) // Throw if `id` is not passed. michael@0: throw Error('you must provide a module name when calling require() from ' michael@0: + requirer.id, requirer.uri); michael@0: michael@0: let requirement; michael@0: let uri; michael@0: michael@0: // TODO should get native Firefox modules before doing node-style lookups michael@0: // to save on loading time michael@0: michael@0: if (isNative) { michael@0: // If a requireMap is available from `generateMap`, use that to michael@0: // immediately resolve the node-style mapping. michael@0: if (requireMap && requireMap[requirer.id]) michael@0: requirement = requireMap[requirer.id][id]; michael@0: michael@0: // For native modules, we want to check if it's a module specified michael@0: // in 'modules', like `chrome`, or `@loader` -- if it exists, michael@0: // just set the uri to skip resolution michael@0: if (!requirement && modules[id]) michael@0: uri = requirement = id; michael@0: michael@0: // If no requireMap was provided, or resolution not found in michael@0: // the requireMap, and not a npm dependency, attempt a runtime lookup michael@0: if (!requirement && !isNodeModule(id)) { michael@0: // If `isNative` defined, this is using the new, native-style michael@0: // loader, not cuddlefish, so lets resolve using node's algorithm michael@0: // and get back a path that needs to be resolved via paths mapping michael@0: // in `resolveURI` michael@0: requirement = resolve(id, requirer.id, { michael@0: manifest: manifest, michael@0: rootURI: rootURI michael@0: }); michael@0: } michael@0: michael@0: // If not found in the map, not a node module, and wasn't able to be michael@0: // looked up, it's something michael@0: // found in the paths most likely, like `sdk/tabs`, which should michael@0: // be resolved relatively if needed using traditional resolve michael@0: if (!requirement) { michael@0: requirement = isRelative(id) ? exports.resolve(id, requirer.id) : id; michael@0: } michael@0: } else { michael@0: // Resolve `id` to its requirer if it's relative. michael@0: requirement = requirer ? resolve(id, requirer.id) : id; michael@0: } michael@0: michael@0: // Resolves `uri` of module using loaders resolve function. michael@0: uri = uri || resolveURI(requirement, mapping); michael@0: michael@0: if (!uri) // Throw if `uri` can not be resolved. michael@0: throw Error('Module: Can not resolve "' + id + '" module required by ' + michael@0: requirer.id + ' located at ' + requirer.uri, requirer.uri); michael@0: michael@0: let module = null; michael@0: // If module is already cached by loader then just use it. michael@0: if (uri in modules) { michael@0: module = modules[uri]; michael@0: } michael@0: else if (isJSMURI(uri)) { michael@0: module = modules[uri] = Module(requirement, uri); michael@0: module.exports = Cu.import(uri, {}); michael@0: freeze(module); michael@0: } michael@0: else if (isJSONURI(uri)) { michael@0: let data; michael@0: michael@0: // First attempt to load and parse json uri michael@0: // ex: `test.json` michael@0: // If that doesn't exist, check for `test.json.js` michael@0: // for node parity michael@0: try { michael@0: data = JSON.parse(readURI(uri)); michael@0: module = modules[uri] = Module(requirement, uri); michael@0: module.exports = data; michael@0: freeze(module); michael@0: } michael@0: catch (err) { michael@0: // If error thrown from JSON parsing, throw that, do not michael@0: // attempt to find .json.js file michael@0: if (err && /JSON\.parse/.test(err.message)) michael@0: throw err; michael@0: uri = uri + '.js'; michael@0: } michael@0: } michael@0: // If not yet cached, load and cache it. michael@0: // We also freeze module to prevent it from further changes michael@0: // at runtime. michael@0: if (!(uri in modules)) { michael@0: // Many of the loader's functionalities are dependent michael@0: // on modules[uri] being set before loading, so we set it and michael@0: // remove it if we have any errors. michael@0: module = modules[uri] = Module(requirement, uri); michael@0: try { michael@0: freeze(load(loader, module)); michael@0: } michael@0: catch (e) { michael@0: // Clear out modules cache so we can throw on a second invalid require michael@0: delete modules[uri]; michael@0: // Also clear out the Sandbox that was created michael@0: delete loader.sandboxes[uri]; michael@0: throw e; michael@0: } michael@0: } michael@0: michael@0: return module.exports; michael@0: } michael@0: // Make `require.main === module` evaluate to true in main module scope. michael@0: require.main = loader.main === requirer ? requirer : undefined; michael@0: return iced(require); michael@0: }); michael@0: exports.Require = Require; michael@0: michael@0: const main = iced(function main(loader, id) { michael@0: // If no main entry provided, and native loader is used, michael@0: // read the entry in the manifest michael@0: if (!id && loader.isNative) michael@0: id = getManifestMain(loader.manifest); michael@0: let uri = resolveURI(id, loader.mapping); michael@0: let module = loader.main = loader.modules[uri] = Module(id, uri); michael@0: return loader.load(loader, module).exports; michael@0: }); michael@0: exports.main = main; michael@0: michael@0: // Makes module object that is made available to CommonJS modules when they michael@0: // are evaluated, along with `exports` and `require`. michael@0: const Module = iced(function Module(id, uri) { michael@0: return create(null, { michael@0: id: { enumerable: true, value: id }, michael@0: exports: { enumerable: true, writable: true, value: create(null) }, michael@0: uri: { value: uri } michael@0: }); michael@0: }); michael@0: exports.Module = Module; michael@0: michael@0: // Takes `loader`, and unload `reason` string and notifies all observers that michael@0: // they should cleanup after them-self. michael@0: const unload = iced(function unload(loader, reason) { michael@0: // subject is a unique object created per loader instance. michael@0: // This allows any code to cleanup on loader unload regardless of how michael@0: // it was loaded. To handle unload for specific loader subject may be michael@0: // asserted against loader.destructor or require('@loader/unload') michael@0: // Note: We don not destroy loader's module cache or sandboxes map as michael@0: // some modules may do cleanup in subsequent turns of event loop. Destroying michael@0: // cache may cause module identity problems in such cases. michael@0: let subject = { wrappedJSObject: loader.destructor }; michael@0: notifyObservers(subject, 'sdk:loader:destroy', reason); michael@0: }); michael@0: exports.unload = unload; michael@0: michael@0: // Function makes new loader that can be used to load CommonJS modules michael@0: // described by a given `options.manifest`. Loader takes following options: michael@0: // - `globals`: Optional map of globals, that all module scopes will inherit michael@0: // from. Map is also exposed under `globals` property of the returned loader michael@0: // so it can be extended further later. Defaults to `{}`. michael@0: // - `modules` Optional map of built-in module exports mapped by module id. michael@0: // These modules will incorporated into module cache. Each module will be michael@0: // frozen. michael@0: // - `resolve` Optional module `id` resolution function. If given it will be michael@0: // used to resolve module URIs, by calling it with require term, requirer michael@0: // module object (that has `uri` property) and `baseURI` of the loader. michael@0: // If `resolve` does not returns `uri` string exception will be thrown by michael@0: // an associated `require` call. michael@0: const Loader = iced(function Loader(options) { michael@0: let { michael@0: modules, globals, resolve, paths, rootURI, manifest, requireMap, isNative michael@0: } = override({ michael@0: paths: {}, michael@0: modules: {}, michael@0: globals: { michael@0: console: console michael@0: }, michael@0: resolve: options.isNative ? michael@0: exports.nodeResolve : michael@0: exports.resolve, michael@0: }, options); michael@0: michael@0: // We create an identity object that will be dispatched on an unload michael@0: // event as subject. This way unload listeners will be able to assert michael@0: // which loader is unloaded. Please note that we intentionally don't michael@0: // use `loader` as subject to prevent a loader access leakage through michael@0: // observer notifications. michael@0: let destructor = freeze(create(null)); michael@0: michael@0: let mapping = sortPaths(paths); michael@0: michael@0: // Define pseudo modules. michael@0: modules = override({ michael@0: '@loader/unload': destructor, michael@0: '@loader/options': options, michael@0: 'chrome': { Cc: Cc, Ci: Ci, Cu: Cu, Cr: Cr, Cm: Cm, michael@0: CC: bind(CC, Components), components: Components, michael@0: // `ChromeWorker` has to be inject in loader global scope. michael@0: // It is done by bootstrap.js:loadSandbox for the SDK. michael@0: ChromeWorker: ChromeWorker michael@0: } michael@0: }, modules); michael@0: michael@0: modules = keys(modules).reduce(function(result, id) { michael@0: // We resolve `uri` from `id` since modules are cached by `uri`. michael@0: let uri = resolveURI(id, mapping); michael@0: // In native loader, the mapping will not contain values for michael@0: // pseudomodules -- store them as their ID rather than the URI michael@0: if (isNative && !uri) michael@0: uri = id; michael@0: let module = Module(id, uri); michael@0: module.exports = freeze(modules[id]); michael@0: result[uri] = freeze(module); michael@0: return result; michael@0: }, {}); michael@0: michael@0: // Loader object is just a representation of a environment michael@0: // state. We freeze it and mark make it's properties non-enumerable michael@0: // as they are pure implementation detail that no one should rely upon. michael@0: let returnObj = { michael@0: destructor: { enumerable: false, value: destructor }, michael@0: globals: { enumerable: false, value: globals }, michael@0: mapping: { enumerable: false, value: mapping }, michael@0: // Map of module objects indexed by module URIs. michael@0: modules: { enumerable: false, value: modules }, michael@0: // Map of module sandboxes indexed by module URIs. michael@0: sandboxes: { enumerable: false, value: {} }, michael@0: resolve: { enumerable: false, value: resolve }, michael@0: // ID of the addon, if provided. michael@0: id: { enumerable: false, value: options.id }, michael@0: // Whether the modules loaded should be ignored by the debugger michael@0: invisibleToDebugger: { enumerable: false, michael@0: value: options.invisibleToDebugger || false }, michael@0: load: { enumerable: false, value: options.load || load }, michael@0: // Main (entry point) module, it can be set only once, since loader michael@0: // instance can have only one main module. michael@0: main: new function() { michael@0: let main; michael@0: return { michael@0: enumerable: false, michael@0: get: function() { return main; }, michael@0: // Only set main if it has not being set yet! michael@0: set: function(module) { main = main || module; } michael@0: } michael@0: } michael@0: }; michael@0: michael@0: if (isNative) { michael@0: returnObj.isNative = { enumerable: false, value: true }; michael@0: returnObj.manifest = { enumerable: false, value: manifest }; michael@0: returnObj.requireMap = { enumerable: false, value: requireMap }; michael@0: returnObj.rootURI = { enumerable: false, value: addTrailingSlash(rootURI) }; michael@0: } michael@0: michael@0: return freeze(create(null, returnObj)); michael@0: }); michael@0: exports.Loader = Loader; michael@0: michael@0: let isJSONURI = uri => uri.substr(-5) === '.json'; michael@0: let isJSMURI = uri => uri.substr(-4) === '.jsm'; michael@0: let isJSURI = uri => uri.substr(-3) === '.js'; michael@0: let isResourceURI = uri => uri.substr(0, 11) === 'resource://'; michael@0: let isRelative = id => id[0] === '.' michael@0: michael@0: const generateMap = iced(function generateMap(options, callback) { michael@0: let { rootURI, resolve, paths } = override({ michael@0: paths: {}, michael@0: resolve: exports.nodeResolve michael@0: }, options); michael@0: michael@0: rootURI = addTrailingSlash(rootURI); michael@0: michael@0: let manifest; michael@0: let manifestURI = join(rootURI, 'package.json'); michael@0: michael@0: if (rootURI) michael@0: manifest = JSON.parse(readURI(manifestURI)); michael@0: else michael@0: throw new Error('No `rootURI` given to generate map'); michael@0: michael@0: let main = getManifestMain(manifest); michael@0: michael@0: findAllModuleIncludes(main, { michael@0: resolve: resolve, michael@0: manifest: manifest, michael@0: rootURI: rootURI michael@0: }, {}, callback); michael@0: michael@0: }); michael@0: exports.generateMap = generateMap; michael@0: michael@0: // Default `main` entry to './index.js' and ensure is relative, michael@0: // since node allows 'lib/index.js' without relative `./` michael@0: function getManifestMain (manifest) { michael@0: let main = manifest.main || './index.js'; michael@0: return isRelative(main) ? main : './' + main; michael@0: } michael@0: michael@0: function findAllModuleIncludes (uri, options, results, callback) { michael@0: let { resolve, manifest, rootURI } = options; michael@0: results = results || {}; michael@0: michael@0: // Abort if JSON or JSM michael@0: if (isJSONURI(uri) || isJSMURI(uri)) { michael@0: callback(results); michael@0: return void 0; michael@0: } michael@0: michael@0: findModuleIncludes(join(rootURI, uri), modules => { michael@0: // If no modules are included in the file, just call callback immediately michael@0: if (!modules.length) { michael@0: callback(results); michael@0: return void 0; michael@0: } michael@0: michael@0: results[uri] = modules.reduce((agg, mod) => { michael@0: let resolved = resolve(mod, uri, { manifest: manifest, rootURI: rootURI }); michael@0: michael@0: // If resolution found, store the resolution; otherwise, michael@0: // skip storing it as runtime lookup will handle this michael@0: if (!resolved) michael@0: return agg; michael@0: agg[mod] = resolved; michael@0: return agg; michael@0: }, {}); michael@0: michael@0: let includes = keys(results[uri]); michael@0: let count = 0; michael@0: let subcallback = () => { if (++count >= includes.length) callback(results) }; michael@0: includes.map(id => { michael@0: let moduleURI = results[uri][id]; michael@0: if (!results[moduleURI]) michael@0: findAllModuleIncludes(moduleURI, options, results, subcallback); michael@0: else michael@0: subcallback(); michael@0: }); michael@0: }); michael@0: } michael@0: michael@0: // From Substack's detector michael@0: // https://github.com/substack/node-detective michael@0: // michael@0: // Given a resource URI or source, return an array of strings passed into michael@0: // the require statements from the source michael@0: function findModuleIncludes (uri, callback) { michael@0: let src = isResourceURI(uri) ? readURI(uri) : uri; michael@0: let modules = []; michael@0: michael@0: walk(src, function (node) { michael@0: if (isRequire(node)) michael@0: modules.push(node.arguments[0].value); michael@0: }); michael@0: michael@0: callback(modules); michael@0: } michael@0: michael@0: function walk (src, callback) { michael@0: let nodes = Reflect.parse(src); michael@0: traverse(nodes, callback); michael@0: } michael@0: michael@0: function traverse (node, cb) { michael@0: if (Array.isArray(node)) { michael@0: node.map(x => { michael@0: if (x != null) { michael@0: x.parent = node; michael@0: traverse(x, cb); michael@0: } michael@0: }); michael@0: } michael@0: else if (node && typeof node === 'object') { michael@0: cb(node); michael@0: keys(node).map(key => { michael@0: if (key === 'parent' || !node[key]) return; michael@0: node[key].parent = node; michael@0: traverse(node[key], cb); michael@0: }); michael@0: } michael@0: } michael@0: michael@0: // From Substack's detector michael@0: // https://github.com/substack/node-detective michael@0: // Check an AST node to see if its a require statement. michael@0: // A modification added to only evaluate to true if it actually michael@0: // has a value being passed in as an argument michael@0: function isRequire (node) { michael@0: var c = node.callee; michael@0: return c michael@0: && node.type === 'CallExpression' michael@0: && c.type === 'Identifier' michael@0: && c.name === 'require' michael@0: && node.arguments.length michael@0: && node.arguments[0].type === 'Literal'; michael@0: } michael@0: michael@0: }); michael@0: