diff options
Diffstat (limited to 'addon-sdk/source/lib/toolkit/loader.js')
-rw-r--r-- | addon-sdk/source/lib/toolkit/loader.js | 1147 |
1 files changed, 0 insertions, 1147 deletions
diff --git a/addon-sdk/source/lib/toolkit/loader.js b/addon-sdk/source/lib/toolkit/loader.js deleted file mode 100644 index 4c22550d8..000000000 --- a/addon-sdk/source/lib/toolkit/loader.js +++ /dev/null @@ -1,1147 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -;((factory) => { // Module boilerplate :( - if (typeof(require) === 'function') { // CommonJS - require("chrome").Cu.import(module.uri, exports); - } - else if (~String(this).indexOf('BackstagePass')) { // JSM - let module = { uri: __URI__, id: "toolkit/loader", exports: Object.create(null) } - factory(module); - Object.assign(this, module.exports); - this.EXPORTED_SYMBOLS = Object.getOwnPropertyNames(module.exports); - } - else { - throw Error("Loading environment is not supported"); - } -})(module => { - -'use strict'; - -module.metadata = { - "stability": "unstable" -}; - -const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu, - results: Cr, manager: Cm } = Components; -const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')(); -const { loadSubScript } = Cc['@mozilla.org/moz/jssubscript-loader;1']. - getService(Ci.mozIJSSubScriptLoader); -const { addObserver, notifyObservers } = Cc['@mozilla.org/observer-service;1']. - getService(Ci.nsIObserverService); -const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}); -const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {}); -const { join: pathJoin, normalize, dirname } = Cu.import("resource://gre/modules/osfile/ospath_unix.jsm"); - -XPCOMUtils.defineLazyServiceGetter(this, "resProto", - "@mozilla.org/network/protocol;1?name=resource", - "nsIResProtocolHandler"); -XPCOMUtils.defineLazyServiceGetter(this, "zipCache", - "@mozilla.org/libjar/zip-reader-cache;1", - "nsIZipReaderCache"); - -XPCOMUtils.defineLazyGetter(this, "XulApp", () => { - let xulappURI = module.uri.replace("toolkit/loader.js", - "sdk/system/xul-app.jsm"); - return Cu.import(xulappURI, {}); -}); - -// Define some shortcuts. -const bind = Function.call.bind(Function.bind); -const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; -const prototypeOf = Object.getPrototypeOf; -const getOwnIdentifiers = x => [...Object.getOwnPropertyNames(x), - ...Object.getOwnPropertySymbols(x)]; - -const NODE_MODULES = new Set([ - "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", -]); - -const COMPONENT_ERROR = '`Components` is not available in this context.\n' + - 'Functionality provided by Components may be available in an SDK\n' + - 'module: https://developer.mozilla.org/en-US/Add-ons/SDK \n\n' + - 'However, if you still need to import Components, you may use the\n' + - '`chrome` module\'s properties for shortcuts to Component properties:\n\n' + - 'Shortcuts: \n' + - ' Cc = Components' + '.classes \n' + - ' Ci = Components' + '.interfaces \n' + - ' Cu = Components' + '.utils \n' + - ' CC = Components' + '.Constructor \n' + - 'Example: \n' + - ' let { Cc, Ci } = require(\'chrome\');\n'; - -// Workaround for bug 674195. Freezing objects from other compartments fail, -// so we use `Object.freeze` from the same component instead. -function freeze(object) { - if (prototypeOf(object) === null) { - Object.freeze(object); - } - else { - prototypeOf(prototypeOf(object.isPrototypeOf)). - constructor. // `Object` from the owner compartment. - freeze(object); - } - return object; -} - -// Returns map of given `object`-s own property descriptors. -const descriptor = iced(function descriptor(object) { - let value = {}; - getOwnIdentifiers(object).forEach(function(name) { - value[name] = getOwnPropertyDescriptor(object, name) - }); - return value; -}); -Loader.descriptor = descriptor; - -// Freeze important built-ins so they can't be used by untrusted code as a -// message passing channel. -freeze(Object); -freeze(Object.prototype); -freeze(Function); -freeze(Function.prototype); -freeze(Array); -freeze(Array.prototype); -freeze(String); -freeze(String.prototype); - -// This function takes `f` function sets it's `prototype` to undefined and -// freezes it. We need to do this kind of deep freeze with all the exposed -// functions so that untrusted code won't be able to use them a message -// passing channel. -function iced(f) { - if (!Object.isFrozen(f)) { - f.prototype = undefined; - } - return freeze(f); -} - -// Defines own properties of given `properties` object on the given -// target object overriding any existing property with a conflicting name. -// Returns `target` object. Note we only export this function because it's -// useful during loader bootstrap when other util modules can't be used & -// thats only case where this export should be used. -const override = iced(function override(target, source) { - let properties = descriptor(target) - let extension = descriptor(source || {}) - getOwnIdentifiers(extension).forEach(function(name) { - properties[name] = extension[name]; - }); - return Object.defineProperties({}, properties); -}); -Loader.override = override; - -function sourceURI(uri) { return String(uri).split(" -> ").pop(); } -Loader.sourceURI = iced(sourceURI); - -function isntLoaderFrame(frame) { return frame.fileName !== module.uri } - -function parseURI(uri) { return String(uri).split(" -> ").pop(); } -Loader.parseURI = parseURI; - -function parseStack(stack) { - let lines = String(stack).split("\n"); - return lines.reduce(function(frames, line) { - if (line) { - let atIndex = line.indexOf("@"); - let columnIndex = line.lastIndexOf(":"); - let lineIndex = line.lastIndexOf(":", columnIndex - 1); - let fileName = parseURI(line.slice(atIndex + 1, lineIndex)); - let lineNumber = parseInt(line.slice(lineIndex + 1, columnIndex)); - let columnNumber = parseInt(line.slice(columnIndex + 1)); - let name = line.slice(0, atIndex).split("(").shift(); - frames.unshift({ - fileName: fileName, - name: name, - lineNumber: lineNumber, - columnNumber: columnNumber - }); - } - return frames; - }, []); -} -Loader.parseStack = parseStack; - -function serializeStack(frames) { - return frames.reduce(function(stack, frame) { - return frame.name + "@" + - frame.fileName + ":" + - frame.lineNumber + ":" + - frame.columnNumber + "\n" + - stack; - }, ""); -} -Loader.serializeStack = serializeStack; - -class DefaultMap extends Map { - constructor(createItem, items = undefined) { - super(items); - - this.createItem = createItem; - } - - get(key) { - if (!this.has(key)) { - this.set(key, this.createItem(key)); - } - - return super.get(key); - } -} - -const urlCache = { - /** - * Returns a list of fully-qualified URLs for entries within the zip - * file at the given URI which are either directories or files with a - * .js or .json extension. - * - * @param {nsIJARURI} uri - * @param {string} baseURL - * The original base URL, prior to resolution. - * - * @returns {Set<string>} - */ - getZipFileContents(uri, baseURL) { - // Make sure the path has a trailing slash, and strip off the leading - // slash, so that we can easily check whether it is a path prefix. - let basePath = addTrailingSlash(uri.JAREntry).slice(1); - let file = uri.JARFile.QueryInterface(Ci.nsIFileURL).file; - - let enumerator = zipCache.getZip(file).findEntries("(*.js|*.json|*/)"); - - let results = new Set(); - for (let entry of XPCOMUtils.IterStringEnumerator(enumerator)) { - if (entry.startsWith(basePath)) { - let path = entry.slice(basePath.length); - - results.add(baseURL + path); - } - } - - return results; - }, - - zipContentsCache: new DefaultMap(baseURL => { - let uri = NetUtil.newURI(baseURL); - - if (baseURL.startsWith("resource:")) { - uri = NetUtil.newURI(resProto.resolveURI(uri)); - } - - if (uri instanceof Ci.nsIJARURI) { - return urlCache.getZipFileContents(uri, baseURL); - } - - return null; - }), - - filesCache: new DefaultMap(url => { - try { - let uri = NetUtil.newURI(url).QueryInterface(Ci.nsIFileURL); - - return uri.file.exists(); - } catch (e) { - return false; - } - }), - - QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]), - - observe() { - // Clear any module resolution caches when the startup cache is flushed, - // since it probably means we're loading new copies of extensions. - this.zipContentsCache.clear(); - this.filesCache.clear(); - }, - - /** - * Returns the base URL for the given URL, if one can be determined. For - * a resource: URL, this is the root of the resource package. For a jar: - * URL, it is the root of the JAR file. Otherwise, null is returned. - * - * @param {string} url - * @returns {string?} - */ - getBaseURL(url) { - // By using simple string matching for the common case of resource: URLs - // backed by jar: URLs, we can avoid creating any nsIURI objects for the - // common case where the JAR contents are already cached. - if (url.startsWith("resource://")) { - return /^resource:\/\/[^\/]+\//.exec(url)[0]; - } - - let uri = NetUtil.newURI(url); - if (uri instanceof Ci.nsIJARURI) { - return `jar:${uri.JARFile.spec}!/`; - } - - return null; - }, - - /** - * Returns true if the target of the given URL exists as a local file, - * or as an entry in a local zip file. - * - * @param {string} url - * @returns {boolean} - */ - exists(url) { - if (!/\.(?:js|json)$/.test(url)) { - url = addTrailingSlash(url); - } - - let baseURL = this.getBaseURL(url); - let scripts = baseURL && this.zipContentsCache.get(baseURL); - if (scripts) { - return scripts.has(url); - } - - return this.filesCache.get(url); - }, -} -addObserver(urlCache, "startupcache-invalidate", true); - -function readURI(uri) { - let nsURI = NetUtil.newURI(uri); - if (nsURI.scheme == "resource") { - // Resolve to a real URI, this will catch any obvious bad paths without - // logging assertions in debug builds, see bug 1135219 - uri = resProto.resolveURI(nsURI); - } - - let stream = NetUtil.newChannel({ - uri: NetUtil.newURI(uri, 'UTF-8'), - loadUsingSystemPrincipal: true} - ).open2(); - let count = stream.available(); - let data = NetUtil.readInputStreamToString(stream, count, { - charset: 'UTF-8' - }); - - stream.close(); - - return data; -} - -// Combines all arguments into a resolved, normalized path -function join(base, ...paths) { - // If this is an absolute URL, we need to normalize only the path portion, - // or we wind up stripping too many slashes and producing invalid URLs. - let match = /^((?:resource|file|chrome)\:\/\/[^\/]*|jar:[^!]+!)(.*)/.exec(base); - if (match) { - return match[1] + normalize(pathJoin(match[2], ...paths)); - } - - return normalize(pathJoin(base, ...paths)); -} -Loader.join = join; - -// Function takes set of options and returns a JS sandbox. Function may be -// passed set of options: -// - `name`: A string value which identifies the sandbox in about:memory. Will -// throw exception if omitted. -// - `principal`: String URI or `nsIPrincipal` for the sandbox. Defaults to -// system principal. -// - `prototype`: Ancestor for the sandbox that will be created. Defaults to -// `{}`. -// - `wantXrays`: A Boolean value indicating whether code outside the sandbox -// wants X-ray vision with respect to objects inside the sandbox. Defaults -// to `true`. -// - `sandbox`: A sandbox to share JS compartment with. If omitted new -// compartment will be created. -// - `metadata`: A metadata object associated with the sandbox. It should -// be JSON-serializable. -// For more details see: -// https://developer.mozilla.org/en/Components.utils.Sandbox -const Sandbox = iced(function Sandbox(options) { - // Normalize options and rename to match `Cu.Sandbox` expectations. - options = { - // Do not expose `Components` if you really need them (bad idea!) you - // still can expose via prototype. - wantComponents: false, - sandboxName: options.name, - principal: 'principal' in options ? options.principal : systemPrincipal, - wantXrays: 'wantXrays' in options ? options.wantXrays : true, - wantGlobalProperties: 'wantGlobalProperties' in options ? - options.wantGlobalProperties : [], - sandboxPrototype: 'prototype' in options ? options.prototype : {}, - invisibleToDebugger: 'invisibleToDebugger' in options ? - options.invisibleToDebugger : false, - metadata: 'metadata' in options ? options.metadata : {}, - waiveIntereposition: !!options.waiveIntereposition - }; - - if (options.metadata && options.metadata.addonID) { - options.addonId = options.metadata.addonID; - } - - let sandbox = Cu.Sandbox(options.principal, options); - - // Each sandbox at creation gets set of own properties that will be shadowing - // ones from it's prototype. We override delete such `sandbox` properties - // to avoid shadowing. - delete sandbox.Iterator; - delete sandbox.Components; - delete sandbox.importFunction; - delete sandbox.debug; - - return sandbox; -}); -Loader.Sandbox = Sandbox; - -// Evaluates code from the given `uri` into given `sandbox`. If -// `options.source` is passed, then that code is evaluated instead. -// Optionally following options may be given: -// - `options.encoding`: Source encoding, defaults to 'UTF-8'. -// - `options.line`: Line number to start count from for stack traces. -// Defaults to 1. -// - `options.version`: Version of JS used, defaults to '1.8'. -const evaluate = iced(function evaluate(sandbox, uri, options) { - let { source, line, version, encoding } = override({ - encoding: 'UTF-8', - line: 1, - version: '1.8', - source: null - }, options); - - return source ? Cu.evalInSandbox(source, sandbox, version, uri, line) - : loadSubScript(uri, sandbox, encoding); -}); -Loader.evaluate = evaluate; - -// Populates `exports` of the given CommonJS `module` object, in the context -// of the given `loader` by evaluating code associated with it. -const load = iced(function load(loader, module) { - let { sandboxes, globals, loadModuleHook } = loader; - let require = Require(loader, module); - - // We expose set of properties defined by `CommonJS` specification via - // prototype of the sandbox. Also globals are deeper in the prototype - // chain so that each module has access to them as well. - let descriptors = descriptor({ - require: require, - module: module, - exports: module.exports, - get Components() { - // Expose `Components` property to throw error on usage with - // additional information - throw new ReferenceError(COMPONENT_ERROR); - } - }); - - let sandbox; - if ((loader.useSharedGlobalSandbox || isSystemURI(module.uri)) && - loader.sharedGlobalBlocklist.indexOf(module.id) == -1) { - // Create a new object in this sandbox, that will be used as - // the scope object for this particular module - sandbox = new loader.sharedGlobalSandbox.Object(); - // Inject all expected globals in the scope object - getOwnIdentifiers(globals).forEach(function(name) { - descriptors[name] = getOwnPropertyDescriptor(globals, name) - descriptors[name].configurable = true; - }); - Object.defineProperties(sandbox, descriptors); - } - else { - sandbox = Sandbox({ - name: module.uri, - prototype: Object.create(globals, descriptors), - wantXrays: false, - wantGlobalProperties: module.id == "sdk/indexed-db" ? ["indexedDB"] : [], - invisibleToDebugger: loader.invisibleToDebugger, - metadata: { - addonID: loader.id, - URI: module.uri - } - }); - } - sandboxes[module.uri] = sandbox; - - try { - evaluate(sandbox, module.uri); - } - catch (error) { - let { message, fileName, lineNumber } = error; - let stack = error.stack || Error().stack; - let frames = parseStack(stack).filter(isntLoaderFrame); - let toString = String(error); - let file = sourceURI(fileName); - - // Note that `String(error)` where error is from subscript loader does - // not puts `:` after `"Error"` unlike regular errors thrown by JS code. - // If there is a JS stack then this error has already been handled by an - // inner module load. - if (/^Error opening input stream/.test(String(error))) { - let caller = frames.slice(0).pop(); - fileName = caller.fileName; - lineNumber = caller.lineNumber; - message = "Module `" + module.id + "` is not found at " + module.uri; - toString = message; - } - // Workaround for a Bug 910653. Errors thrown by subscript loader - // do not include `stack` field and above created error won't have - // fileName or lineNumber of the module being loaded, so we ensure - // it does. - else if (frames[frames.length - 1].fileName !== file) { - frames.push({ fileName: file, lineNumber: lineNumber, name: "" }); - } - - let prototype = typeof(error) === "object" ? error.constructor.prototype : - Error.prototype; - - throw Object.create(prototype, { - message: { value: message, writable: true, configurable: true }, - fileName: { value: fileName, writable: true, configurable: true }, - lineNumber: { value: lineNumber, writable: true, configurable: true }, - stack: { value: serializeStack(frames), writable: true, configurable: true }, - toString: { value: () => toString, writable: true, configurable: true }, - }); - } - - if (loadModuleHook) { - module = loadModuleHook(module, require); - } - - if (loader.checkCompatibility) { - let err = XulApp.incompatibility(module); - if (err) { - throw err; - } - } - - if (module.exports && typeof(module.exports) === 'object') - freeze(module.exports); - - return module; -}); -Loader.load = load; - -// Utility function to normalize module `uri`s so they have `.js` extension. -function normalizeExt(uri) { - return isJSURI(uri) ? uri : - isJSONURI(uri) ? uri : - isJSMURI(uri) ? uri : - uri + '.js'; -} - -// Strips `rootURI` from `string` -- used to remove absolute resourceURI -// from a relative path -function stripBase(rootURI, string) { - return string.replace(rootURI, './'); -} - -// Utility function to join paths. In common case `base` is a -// `requirer.uri` but in some cases it may be `baseURI`. In order to -// avoid complexity we require `baseURI` with a trailing `/`. -const resolve = iced(function resolve(id, base) { - if (!isRelative(id)) - return id; - - let baseDir = dirname(base); - if (!baseDir) - return normalize(id); - - let resolved = join(baseDir, id); - - // Joining and normalizing removes the './' from relative files. - // We need to ensure the resolution still has the root - if (isRelative(base)) - resolved = './' + resolved; - - return resolved; -}); -Loader.resolve = resolve; - -// Attempts to load `path` and then `path.js` -// Returns `path` with valid file, or `undefined` otherwise -function resolveAsFile(path) { - // Append '.js' to path name unless it's another support filetype - path = normalizeExt(path); - if (urlCache.exists(path)) { - return path; - } - - return null; -} - -// Attempts to load `path/package.json`'s `main` entry, -// followed by `path/index.js`, or `undefined` otherwise -function resolveAsDirectory(path) { - try { - // If `path/package.json` exists, parse the `main` entry - // and attempt to load that - let manifestPath = addTrailingSlash(path) + 'package.json'; - - let main = (urlCache.exists(manifestPath) && - getManifestMain(JSON.parse(readURI(manifestPath)))); - if (main) { - let found = resolveAsFile(join(path, main)); - if (found) { - return found - } - } - } catch (e) {} - - return resolveAsFile(addTrailingSlash(path) + 'index.js'); -} - -function resolveRelative(rootURI, modulesDir, id) { - let fullId = join(rootURI, modulesDir, id); - - let resolvedPath = (resolveAsFile(fullId) || - resolveAsDirectory(fullId)); - if (resolvedPath) { - return stripBase(rootURI, resolvedPath); - } - - return null; -} - -// From `resolve` module -// https://github.com/substack/node-resolve/blob/master/lib/node-modules-paths.js -function* getNodeModulePaths(rootURI, start) { - let moduleDir = 'node_modules'; - - let parts = start.split('/'); - while (parts.length) { - let leaf = parts.pop(); - let path = join(...parts, leaf, moduleDir); - if (leaf !== moduleDir && urlCache.exists(join(rootURI, path))) { - yield path; - } - } - - if (urlCache.exists(join(rootURI, moduleDir))) { - yield moduleDir; - } -} - -// Node-style module lookup -// Takes an id and path and attempts to load a file using node's resolving -// algorithm. -// `id` should already be resolved relatively at this point. -// http://nodejs.org/api/modules.html#modules_all_together -const nodeResolve = iced(function nodeResolve(id, requirer, { rootURI }) { - // Resolve again - id = Loader.resolve(id, requirer); - - // If this is already an absolute URI then there is no resolution to do - if (isAbsoluteURI(id)) { - return null; - } - - // we assume that extensions are correct, i.e., a directory doesnt't have '.js' - // and a js file isn't named 'file.json.js' - let resolvedPath; - - if ((resolvedPath = resolveRelative(rootURI, "", id))) { - return resolvedPath; - } - - // If the requirer is an absolute URI then the node module resolution below - // won't work correctly as we prefix everything with rootURI - if (isAbsoluteURI(requirer)) { - return null; - } - - // If manifest has dependencies, attempt to look up node modules - // in the `dependencies` list - for (let modulesDir of getNodeModulePaths(rootURI, dirname(requirer))) { - if ((resolvedPath = resolveRelative(rootURI, modulesDir, id))) { - return resolvedPath; - } - } - - // We would not find lookup for things like `sdk/tabs`, as that's part of - // the alias mapping. If during `generateMap`, the runtime lookup resolves - // with `resolveURI` -- if during runtime, then `resolve` will throw. - return null; -}); - -Loader.nodeResolve = nodeResolve; - -function addTrailingSlash(path) { - return path.replace(/\/*$/, "/"); -} - -const resolveURI = iced(function resolveURI(id, mapping) { - // Do not resolve if already a resource URI - if (isAbsoluteURI(id)) - return normalizeExt(id); - - for (let [path, uri] of mapping) { - // Strip off any trailing slashes to make comparisons simpler - let stripped = path.replace(/\/+$/, ""); - - // We only want to match path segments explicitly. Examples: - // * "foo/bar" matches for "foo/bar" - // * "foo/bar" matches for "foo/bar/baz" - // * "foo/bar" does not match for "foo/bar-1" - // * "foo/bar/" does not match for "foo/bar" - // * "foo/bar/" matches for "foo/bar/baz" - // - // Check for an empty path, an exact match, or a substring match - // with the next character being a forward slash. - if(stripped === "" || id === stripped || id.startsWith(stripped + "/")) { - return normalizeExt(id.replace(path, uri)); - } - } - return null; -}); -Loader.resolveURI = resolveURI; - -// Creates version of `require` that will be exposed to the given `module` -// in the context of the given `loader`. Each module gets own limited copy -// of `require` that is allowed to load only a modules that are associated -// with it during link time. -const Require = iced(function Require(loader, requirer) { - let { - modules, mapping, resolve: loaderResolve, load, - manifest, rootURI, isNative, requireMap, - requireHook - } = loader; - - if (isSystemURI(requirer.uri)) { - // Built-in modules don't require the expensive module resolution - // algorithm used by SDK add-ons, so give them the more efficient standard - // resolve instead. - isNative = false; - loaderResolve = Loader.resolve; - } - - function require(id) { - if (!id) // Throw if `id` is not passed. - throw Error('You must provide a module name when calling require() from ' - + requirer.id, requirer.uri); - - if (requireHook) { - return requireHook(id, _require); - } - - return _require(id); - } - - function _require(id) { - let { uri, requirement } = getRequirements(id); - let module = null; - // If module is already cached by loader then just use it. - if (uri in modules) { - module = modules[uri]; - } - else if (isJSMURI(uri)) { - module = modules[uri] = Module(requirement, uri); - module.exports = Cu.import(uri, {}); - freeze(module); - } - else if (isJSONURI(uri)) { - let data; - - // First attempt to load and parse json uri - // ex: `test.json` - // If that doesn't exist, check for `test.json.js` - // for node parity - try { - data = JSON.parse(readURI(uri)); - module = modules[uri] = Module(requirement, uri); - module.exports = data; - freeze(module); - } - catch (err) { - // If error thrown from JSON parsing, throw that, do not - // attempt to find .json.js file - if (err && /JSON\.parse/.test(err.message)) - throw err; - uri = uri + '.js'; - } - } - - // If not yet cached, load and cache it. - // We also freeze module to prevent it from further changes - // at runtime. - if (!(uri in modules)) { - // Many of the loader's functionalities are dependent - // on modules[uri] being set before loading, so we set it and - // remove it if we have any errors. - module = modules[uri] = Module(requirement, uri); - try { - freeze(load(loader, module)); - } - catch (e) { - // Clear out modules cache so we can throw on a second invalid require - delete modules[uri]; - // Also clear out the Sandbox that was created - delete loader.sandboxes[uri]; - throw e; - } - } - - return module.exports; - } - - // Resolution function taking a module name/path and - // returning a resourceURI and a `requirement` used by the loader. - // Used by both `require` and `require.resolve`. - function getRequirements(id) { - if (!id) // Throw if `id` is not passed. - throw Error('you must provide a module name when calling require() from ' - + requirer.id, requirer.uri); - - let requirement, uri; - - // TODO should get native Firefox modules before doing node-style lookups - // to save on loading time - if (isNative) { - // If a requireMap is available from `generateMap`, use that to - // immediately resolve the node-style mapping. - // TODO: write more tests for this use case - if (requireMap && requireMap[requirer.id]) - requirement = requireMap[requirer.id][id]; - - let { overrides } = manifest.jetpack; - for (let key in overrides) { - // ignore any overrides using relative keys - if (/^[.\/]/.test(key)) { - continue; - } - - // If the override is for x -> y, - // then using require("x/lib/z") to get reqire("y/lib/z") - // should also work - if (id == key || id.startsWith(key + "/")) { - id = overrides[key] + id.substr(key.length); - id = id.replace(/^[.\/]+/, ""); - } - } - - // For native modules, we want to check if it's a module specified - // in 'modules', like `chrome`, or `@loader` -- if it exists, - // just set the uri to skip resolution - if (!requirement && modules[id]) - uri = requirement = id; - - // If no requireMap was provided, or resolution not found in - // the requireMap, and not a npm dependency, attempt a runtime lookup - if (!requirement && !NODE_MODULES.has(id)) { - // If `isNative` defined, this is using the new, native-style - // loader, not cuddlefish, so lets resolve using node's algorithm - // and get back a path that needs to be resolved via paths mapping - // in `resolveURI` - requirement = loaderResolve(id, requirer.id, { - manifest: manifest, - rootURI: rootURI - }); - } - - // If not found in the map, not a node module, and wasn't able to be - // looked up, it's something - // found in the paths most likely, like `sdk/tabs`, which should - // be resolved relatively if needed using traditional resolve - if (!requirement) { - requirement = isRelative(id) ? Loader.resolve(id, requirer.id) : id; - } - } - else if (modules[id]) { - uri = requirement = id; - } - else if (requirer) { - // Resolve `id` to its requirer if it's relative. - requirement = loaderResolve(id, requirer.id); - } - else { - requirement = id; - } - - // Resolves `uri` of module using loaders resolve function. - uri = uri || resolveURI(requirement, mapping); - - // Throw if `uri` can not be resolved. - if (!uri) { - throw Error('Module: Can not resolve "' + id + '" module required by ' + - requirer.id + ' located at ' + requirer.uri, requirer.uri); - } - - return { uri: uri, requirement: requirement }; - } - - // Expose the `resolve` function for this `Require` instance - require.resolve = _require.resolve = function resolve(id) { - let { uri } = getRequirements(id); - return uri; - } - - // This is like webpack's require.context. It returns a new require - // function that prepends the prefix to any requests. - require.context = prefix => { - return id => { - return require(prefix + id); - }; - }; - - // Make `require.main === module` evaluate to true in main module scope. - require.main = loader.main === requirer ? requirer : undefined; - return iced(require); -}); -Loader.Require = Require; - -const main = iced(function main(loader, id) { - // If no main entry provided, and native loader is used, - // read the entry in the manifest - if (!id && loader.isNative) - id = getManifestMain(loader.manifest); - let uri = resolveURI(id, loader.mapping); - let module = loader.main = loader.modules[uri] = Module(id, uri); - return loader.load(loader, module).exports; -}); -Loader.main = main; - -// Makes module object that is made available to CommonJS modules when they -// are evaluated, along with `exports` and `require`. -const Module = iced(function Module(id, uri) { - return Object.create(null, { - id: { enumerable: true, value: id }, - exports: { enumerable: true, writable: true, value: Object.create(null), - configurable: true }, - uri: { value: uri } - }); -}); -Loader.Module = Module; - -// Takes `loader`, and unload `reason` string and notifies all observers that -// they should cleanup after them-self. -const unload = iced(function unload(loader, reason) { - // subject is a unique object created per loader instance. - // This allows any code to cleanup on loader unload regardless of how - // it was loaded. To handle unload for specific loader subject may be - // asserted against loader.destructor or require('@loader/unload') - // Note: We don not destroy loader's module cache or sandboxes map as - // some modules may do cleanup in subsequent turns of event loop. Destroying - // cache may cause module identity problems in such cases. - let subject = { wrappedJSObject: loader.destructor }; - notifyObservers(subject, 'sdk:loader:destroy', reason); -}); -Loader.unload = unload; - -// Function makes new loader that can be used to load CommonJS modules -// described by a given `options.manifest`. Loader takes following options: -// - `globals`: Optional map of globals, that all module scopes will inherit -// from. Map is also exposed under `globals` property of the returned loader -// so it can be extended further later. Defaults to `{}`. -// - `modules` Optional map of built-in module exports mapped by module id. -// These modules will incorporated into module cache. Each module will be -// frozen. -// - `resolve` Optional module `id` resolution function. If given it will be -// used to resolve module URIs, by calling it with require term, requirer -// module object (that has `uri` property) and `baseURI` of the loader. -// If `resolve` does not returns `uri` string exception will be thrown by -// an associated `require` call. -function Loader(options) { - if (options.sharedGlobalBlacklist && !options.sharedGlobalBlocklist) { - options.sharedGlobalBlocklist = options.sharedGlobalBlacklist; - } - let { - modules, globals, resolve, paths, rootURI, manifest, requireMap, isNative, - metadata, sharedGlobal, sharedGlobalBlocklist, checkCompatibility, waiveIntereposition - } = override({ - paths: {}, - modules: {}, - globals: { - get console() { - // Import Console.jsm from here to prevent loading it until someone uses it - let { ConsoleAPI } = Cu.import("resource://gre/modules/Console.jsm"); - let console = new ConsoleAPI({ - consoleID: options.id ? "addon/" + options.id : "" - }); - Object.defineProperty(this, "console", { value: console }); - return this.console; - } - }, - checkCompatibility: false, - resolve: options.isNative ? - // Make the returned resolve function have the same signature - (id, requirer) => Loader.nodeResolve(id, requirer, { rootURI: rootURI }) : - Loader.resolve, - sharedGlobalBlocklist: ["sdk/indexed-db"], - waiveIntereposition: false - }, options); - - // Create overrides defaults, none at the moment - if (typeof manifest != "object" || !manifest) { - manifest = {}; - } - if (typeof manifest.jetpack != "object" || !manifest.jetpack) { - manifest.jetpack = { - overrides: {} - }; - } - if (typeof manifest.jetpack.overrides != "object" || !manifest.jetpack.overrides) { - manifest.jetpack.overrides = {}; - } - - // We create an identity object that will be dispatched on an unload - // event as subject. This way unload listeners will be able to assert - // which loader is unloaded. Please note that we intentionally don't - // use `loader` as subject to prevent a loader access leakage through - // observer notifications. - let destructor = freeze(Object.create(null)); - - // Make mapping array that is sorted from longest path to shortest path. - let mapping = Object.keys(paths) - .sort((a, b) => b.length - a.length) - .map(path => [path, paths[path]]); - - // Define pseudo modules. - modules = override({ - '@loader/unload': destructor, - '@loader/options': options, - 'chrome': { Cc: Cc, Ci: Ci, Cu: Cu, Cr: Cr, Cm: Cm, - CC: bind(CC, Components), components: Components, - // `ChromeWorker` has to be inject in loader global scope. - // It is done by bootstrap.js:loadSandbox for the SDK. - ChromeWorker: ChromeWorker - } - }, modules); - - const builtinModuleExports = modules; - modules = {}; - for (let id of Object.keys(builtinModuleExports)) { - // We resolve `uri` from `id` since modules are cached by `uri`. - let uri = resolveURI(id, mapping); - // In native loader, the mapping will not contain values for - // pseudomodules -- store them as their ID rather than the URI - if (isNative && !uri) - uri = id; - let module = Module(id, uri); - - // Lazily expose built-in modules in order to - // allow them to be loaded lazily. - Object.defineProperty(module, "exports", { - enumerable: true, - get: function() { - return builtinModuleExports[id]; - } - }); - - modules[uri] = freeze(module); - } - - // Create the unique sandbox we will be using for all modules, - // so that we prevent creating a new comportment per module. - // The side effect is that all modules will share the same - // global objects. - let sharedGlobalSandbox = Sandbox({ - name: "Addon-SDK", - wantXrays: false, - wantGlobalProperties: [], - invisibleToDebugger: options.invisibleToDebugger || false, - metadata: { - addonID: options.id, - URI: "Addon-SDK" - }, - prototype: options.sandboxPrototype || {} - }); - - // Loader object is just a representation of a environment - // state. We freeze it and mark make it's properties non-enumerable - // as they are pure implementation detail that no one should rely upon. - let returnObj = { - destructor: { enumerable: false, value: destructor }, - globals: { enumerable: false, value: globals }, - mapping: { enumerable: false, value: mapping }, - // Map of module objects indexed by module URIs. - modules: { enumerable: false, value: modules }, - metadata: { enumerable: false, value: metadata }, - useSharedGlobalSandbox: { enumerable: false, value: !!sharedGlobal }, - sharedGlobalSandbox: { enumerable: false, value: sharedGlobalSandbox }, - sharedGlobalBlocklist: { enumerable: false, value: sharedGlobalBlocklist }, - sharedGlobalBlacklist: { enumerable: false, value: sharedGlobalBlocklist }, - // Map of module sandboxes indexed by module URIs. - sandboxes: { enumerable: false, value: {} }, - resolve: { enumerable: false, value: resolve }, - // ID of the addon, if provided. - id: { enumerable: false, value: options.id }, - // Whether the modules loaded should be ignored by the debugger - invisibleToDebugger: { enumerable: false, - value: options.invisibleToDebugger || false }, - load: { enumerable: false, value: options.load || load }, - checkCompatibility: { enumerable: false, value: checkCompatibility }, - requireHook: { enumerable: false, value: options.requireHook }, - loadModuleHook: { enumerable: false, value: options.loadModuleHook }, - // Main (entry point) module, it can be set only once, since loader - // instance can have only one main module. - main: new function() { - let main; - return { - enumerable: false, - get: function() { return main; }, - // Only set main if it has not being set yet! - set: function(module) { main = main || module; } - } - } - }; - - if (isNative) { - returnObj.isNative = { enumerable: false, value: true }; - returnObj.manifest = { enumerable: false, value: manifest }; - returnObj.requireMap = { enumerable: false, value: requireMap }; - returnObj.rootURI = { enumerable: false, value: addTrailingSlash(rootURI) }; - } - - return freeze(Object.create(null, returnObj)); -}; -Loader.Loader = Loader; - -var isSystemURI = uri => /^resource:\/\/(gre|devtools|testing-common)\//.test(uri); - -var isJSONURI = uri => uri.endsWith('.json'); -var isJSMURI = uri => uri.endsWith('.jsm'); -var isJSURI = uri => uri.endsWith('.js'); -var isAbsoluteURI = uri => uri.startsWith("resource://") || - uri.startsWith("chrome://") || - uri.startsWith("file://"); -var isRelative = id => id.startsWith("."); - -// Default `main` entry to './index.js' and ensure is relative, -// since node allows 'lib/index.js' without relative `./` -function getManifestMain(manifest) { - let main = manifest.main || './index.js'; - return isRelative(main) ? main : './' + main; -} - -module.exports = iced(Loader); -}); |