--- a/devtools/shared/base-loader.js
+++ b/devtools/shared/base-loader.js
@@ -1,23 +1,25 @@
/* 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/. */
-'use strict';
+/* global __URI__ */
-this.EXPORTED_SYMBOLS = ["Loader", "resolveURI", "Module", "Require"]
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["Loader", "resolveURI", "Module", "Require"];
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 { notifyObservers } = Cc['@mozilla.org/observer-service;1'].
- getService(Ci.nsIObserverService);
+const systemPrincipal = CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")();
+const { loadSubScript } = Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader);
+const { notifyObservers } = Cc["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService);
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
const { normalize, dirname } = Cu.import("resource://gre/modules/osfile/ospath_unix.jsm", {});
XPCOMUtils.defineLazyServiceGetter(this, "resProto",
"@mozilla.org/network/protocol;1?name=resource",
"nsIResProtocolHandler");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
@@ -25,46 +27,52 @@ const { defineLazyGetter } = XPCOMUtils;
// Define some shortcuts.
const bind = Function.call.bind(Function.bind);
function* getOwnIdentifiers(x) {
yield* Object.getOwnPropertyNames(x);
yield* Object.getOwnPropertySymbols(x);
}
-function sourceURI(uri) { return String(uri).split(" -> ").pop(); }
+function sourceURI(uri) {
+ return String(uri).split(" -> ").pop();
+}
-function isntLoaderFrame(frame) { return frame.fileName !== __URI__ }
+function isntLoaderFrame(frame) {
+ return frame.fileName !== __URI__;
+}
-function parseURI(uri) { return String(uri).split(" -> ").pop(); }
+function parseURI(uri) {
+ return String(uri).split(" -> ").pop();
+}
function parseStack(stack) {
let lines = String(stack).split("\n");
- return lines.reduce(function(frames, line) {
+ 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 lineNumber = parseInt(line.slice(lineIndex + 1, columnIndex), 10);
+ let columnNumber = parseInt(line.slice(columnIndex + 1), 10);
let name = line.slice(0, atIndex).split("(").shift();
frames.unshift({
fileName: fileName,
name: name,
lineNumber: lineNumber,
columnNumber: columnNumber
});
}
return frames;
}, []);
}
function serializeStack(frames) {
- return frames.reduce(function(stack, frame) {
+ return frames.reduce(function (stack, frame) {
return frame.name + "@" +
frame.fileName + ":" +
frame.lineNumber + ":" +
frame.columnNumber + "\n" +
stack;
}, "");
}
@@ -72,22 +80,22 @@ 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'),
+ uri: NetUtil.newURI(uri, "UTF-8"),
loadUsingSystemPrincipal: true}
).open2();
let count = stream.available();
let data = NetUtil.readInputStreamToString(stream, count, {
- charset: 'UTF-8'
+ charset: "UTF-8"
});
stream.close();
return data;
}
// Combines all arguments into a resolved, normalized path
@@ -116,18 +124,18 @@ function join(base, ...paths) {
// https://developer.mozilla.org/en/Components.utils.Sandbox
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,
- sandboxPrototype: 'prototype' in options ? options.prototype : {},
- invisibleToDebugger: 'invisibleToDebugger' in options ?
+ sandboxPrototype: "prototype" in options ? options.prototype : {},
+ invisibleToDebugger: "invisibleToDebugger" in options ?
options.invisibleToDebugger : false,
waiveInterposition: false
};
let sandbox = Cu.Sandbox(systemPrincipal, options);
delete sandbox.Components;
@@ -182,121 +190,124 @@ function load(loader, module) {
descriptors.console = {
configurable: true,
get() {
return globals.console;
},
};
}
let define = Object.getOwnPropertyDescriptor(globals, "define");
- if (define && define.value)
+ if (define && define.value) {
descriptors.define = define;
- if ("DOMParser" in globals)
+ }
+ if ("DOMParser" in globals) {
descriptors.DOMParser = Object.getOwnPropertyDescriptor(globals, "DOMParser");
+ }
Object.defineProperties(sandbox, descriptors);
- }
- else {
+ } else {
sandbox = Sandbox({
name: module.uri,
prototype: Object.create(globals, descriptors),
invisibleToDebugger: loader.invisibleToDebugger
});
}
sandboxes[module.uri] = sandbox;
let originalExports = module.exports;
try {
- loadSubScript(module.uri, sandbox, 'UTF-8');
- }
- catch (error) {
+ loadSubScript(module.uri, sandbox, "UTF-8");
+ } 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) {
+ } else if (frames[frames.length - 1].fileName !== file) {
+ // 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.
frames.push({ fileName: file, lineNumber: lineNumber, name: "" });
}
- let prototype = typeof(error) === "object" ? error.constructor.prototype :
+ 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 },
});
}
// Only freeze the exports object if we created it ourselves. Modules
// which completely replace the exports object and still want it
// frozen need to freeze it themselves.
- if (module.exports === originalExports)
+ if (module.exports === originalExports) {
Object.freeze(module.exports);
+ }
return module;
}
// 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';
+ if (isJSURI(uri) || isJSONURI(uri) || isJSMURI(uri)) {
+ return uri;
+ }
+ return uri + ".js";
}
// 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 `/`.
function resolve(id, base) {
- if (!isRelative(id))
+ if (!isRelative(id)) {
return id;
+ }
let baseDir = dirname(base);
let resolved;
- if (baseDir.includes(":"))
+ if (baseDir.includes(":")) {
resolved = join(baseDir, id);
- else
+ } else {
resolved = normalize(`${baseDir}/${id}`);
+ }
- // Joining and normalizing removes the './' from relative files.
+ // Joining and normalizing removes the "./" from relative files.
// We need to ensure the resolution still has the root
- if (base.startsWith('./'))
- resolved = './' + resolved;
+ if (base.startsWith("./")) {
+ resolved = "./" + resolved;
+ }
return resolved;
}
function compileMapping(paths) {
// 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]]);
const PATTERN = /([.\\?+*(){}[\]^$])/g;
- const escapeMeta = str => str.replace(PATTERN, '\\$1')
+ const escapeMeta = str => str.replace(PATTERN, "\\$1");
let patterns = [];
paths = {};
for (let [path, uri] of mapping) {
// Strip off any trailing slashes to make comparisons simpler
if (path.endsWith("/")) {
path = path.slice(0, -1);
@@ -309,37 +320,39 @@ function compileMapping(paths) {
// * "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 (path == "")
+ if (path == "") {
patterns.push("");
- else
+ } else {
patterns.push(`${escapeMeta(path)}(?=$|/)`);
+ }
}
- let pattern = new RegExp(`^(${patterns.join('|')})`);
+ let pattern = new RegExp(`^(${patterns.join("|")})`);
// This will replace the longest matching path mapping at the start of
// the ID string with its mapped value.
return id => {
return id.replace(pattern, (m0, m1) => paths[m1]);
};
}
function resolveURI(id, mapping) {
// Do not resolve if already a resource URI
- if (isAbsoluteURI(id))
+ if (isAbsoluteURI(id)) {
return normalizeExt(id);
+ }
- return normalizeExt(mapping(id))
+ return normalizeExt(mapping(id));
}
/**
* Defines lazy getters on the given object, which lazily require the
* given module the first time they are accessed, and then resolve that
* module's exported properties.
*
* @param {object} obj
@@ -352,27 +365,30 @@ function resolveURI(id, mapping) {
* same property in the module's exports. An object will define a
* lazy getter for every value in the object which corresponds to
* the given key in the module's exports, as in an ordinary
* destructuring assignment.
*/
function lazyRequire(obj, moduleId, ...args) {
let module;
let getModule = () => {
- if (!module)
+ if (!module) {
module = this.require(moduleId);
+ }
return module;
};
for (let props of args) {
- if (typeof props !== "object")
+ if (typeof props !== "object") {
props = {[props]: props};
+ }
- for (let [fromName, toName] of Object.entries(props))
+ for (let [fromName, toName] of Object.entries(props)) {
defineLazyGetter(obj, toName, () => getModule()[fromName]);
+ }
}
}
/**
* Defines a lazy getter on the given object which causes a module to be
* lazily imported the first time it is accessed.
*
* @param {object} obj
@@ -381,139 +397,137 @@ function lazyRequire(obj, moduleId, ...a
* The ID of the module to require, as passed to require().
* @param {string} [prop = moduleId]
* The name of the lazy getter property to define.
*/
function lazyRequireModule(obj, moduleId, prop = moduleId) {
defineLazyGetter(obj, prop, () => this.require(moduleId));
}
-
// 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.
function Require(loader, requirer) {
let {
modules, mapping, mappingCache, requireHook
} = loader;
function require(id) {
- if (!id) // Throw if `id` is not passed.
- throw Error('You must provide a module name when calling require() from '
+ 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)) {
+ } else if (isJSMURI(uri)) {
module = modules[uri] = Module(requirement, uri);
module.exports = Cu.import(uri, {});
- }
- else if (isJSONURI(uri)) {
+ } 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`
+ // 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;
- }
- catch (err) {
+ } 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))
+ if (err && /JSON\.parse/.test(err.message)) {
throw err;
- uri = uri + '.js';
+ }
+ 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 {
Object.freeze(load(loader, module));
- }
- catch (e) {
+ } 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 '
+ 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;
if (modules[id]) {
uri = requirement = id;
- }
- else if (requirer) {
+ } else if (requirer) {
// Resolve `id` to its requirer if it's relative.
requirement = resolve(id, requirer.id);
- }
- else {
+ } else {
requirement = id;
}
// Resolves `uri` of module using loaders resolve function.
if (!uri) {
if (mappingCache.has(requirement)) {
uri = mappingCache.get(requirement);
} else {
uri = resolveURI(requirement, mapping);
mappingCache.set(requirement, uri);
}
}
// 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);
+ 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) {
+ require.resolve = _require.resolve = function (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);
};
};
@@ -529,61 +543,61 @@ function Module(id, uri) {
exports: { enumerable: true, writable: true, value: Object.create(null),
configurable: true },
uri: { value: uri }
});
}
// Takes `loader`, and unload `reason` string and notifies all observers that
// they should cleanup after them-self.
-function unload(loader, reason) {
+function unload(loader, reason) { // eslint-disable-line no-unused-vars
// 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')
+ // 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);
-};
+ notifyObservers(subject, "sdk:loader:destroy", reason);
+}
// Function makes new loader that can be used to load CommonJS modules.
// 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) {
+function Loader(options) { // eslint-disable-line no-unused-vars
let { paths, sharedGlobal, globals } = options;
if (!globals) {
globals = {};
}
// 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
+ // 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 = Object.create(null);
let mapping = compileMapping(paths);
// Define pseudo modules.
let modules = {
- '@loader/unload': destructor,
- '@loader/options': options,
- 'chrome': { Cc: Cc, Ci: Ci, Cu: Cu, Cr: Cr, Cm: Cm,
+ "@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
}
};
const builtinModuleExports = modules;
@@ -592,17 +606,17 @@ function Loader(options) {
// We resolve `uri` from `id` since modules are cached by `uri`.
let uri = resolveURI(id, mapping);
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() {
+ get: function () {
return builtinModuleExports[id];
}
});
modules[uri] = module;
}
// Create the unique sandbox we will be using for all modules,
@@ -614,19 +628,20 @@ function Loader(options) {
invisibleToDebugger: options.invisibleToDebugger || false,
prototype: options.sandboxPrototype || globals,
});
if (options.sandboxPrototype) {
// If we were given a sandboxPrototype, we have to define the globals on
// the sandbox directly. Note that this will not work for callers who
// depend on being able to add globals after the loader was created.
- for (let name of getOwnIdentifiers(globals))
+ for (let name of getOwnIdentifiers(globals)) {
Object.defineProperty(sharedGlobalSandbox, name,
Object.getOwnPropertyDescriptor(globals, name));
+ }
}
// 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 },
@@ -642,16 +657,16 @@ function Loader(options) {
id: { enumerable: false, value: options.id },
// Whether the modules loaded should be ignored by the debugger
invisibleToDebugger: { enumerable: false,
value: options.invisibleToDebugger || false },
requireHook: { enumerable: false, value: options.requireHook },
};
return Object.create(null, returnObj);
-};
+}
-var isJSONURI = uri => uri.endsWith('.json');
-var isJSMURI = uri => uri.endsWith('.jsm');
-var isJSURI = uri => uri.endsWith('.js');
+var isJSONURI = uri => uri.endsWith(".json");
+var isJSMURI = uri => uri.endsWith(".jsm");
+var isJSURI = uri => uri.endsWith(".js");
var AbsoluteRegExp = /^(resource|chrome|file|jar):/;
var isAbsoluteURI = uri => AbsoluteRegExp.test(uri);
var isRelative = id => id.startsWith(".");