diff options
Diffstat (limited to '')
-rw-r--r-- | devtools/shared/loader/worker-loader.js | 534 |
1 files changed, 0 insertions, 534 deletions
diff --git a/devtools/shared/loader/worker-loader.js b/devtools/shared/loader/worker-loader.js deleted file mode 100644 index d69ddd9d23..0000000000 --- a/devtools/shared/loader/worker-loader.js +++ /dev/null @@ -1,534 +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/. */ - -"use strict"; - -/* global worker, DebuggerNotificationObserver */ - -// A CommonJS module loader that is designed to run inside a worker debugger. -// We can't simply use the SDK module loader, because it relies heavily on -// Components, which isn't available in workers. -// -// In principle, the standard instance of the worker loader should provide the -// same built-in modules as its devtools counterpart, so that both loaders are -// interchangable on the main thread, making them easier to test. -// -// On the worker thread, some of these modules, in particular those that rely on -// the use of Components, and for which the worker debugger doesn't provide an -// alternative API, will be replaced by vacuous objects. Consequently, they can -// still be required, but any attempts to use them will lead to an exception. -// -// Note: to see dump output when running inside the worker thread, you might -// need to enable the browser.dom.window.dump.enabled pref. - -this.EXPORTED_SYMBOLS = ["WorkerDebuggerLoader", "worker"]; - -// Some notes on module ids and URLs: -// -// An id is either a relative id or an absolute id. An id is relative if and -// only if it starts with a dot. An absolute id is a normalized id if and only -// if it contains no redundant components. -// -// Every normalized id is a URL. A URL is either an absolute URL or a relative -// URL. A URL is absolute if and only if it starts with a scheme name followed -// by a colon and 2 or 3 slashes. - -/** - * Convert the given relative id to an absolute id. - * - * @param String id - * The relative id to be resolved. - * @param String baseId - * The absolute base id to resolve the relative id against. - * - * @return String - * An absolute id - */ -function resolveId(id, baseId) { - return baseId + "/../" + id; -} - -/** - * Convert the given absolute id to a normalized id. - * - * @param String id - * The absolute id to be normalized. - * - * @return String - * A normalized id. - */ -function normalizeId(id) { - // An id consists of an optional root and a path. A root consists of either - // a scheme name followed by 2 or 3 slashes, or a single slash. Slashes in the - // root are not used as separators, so only normalize the path. - const [, root, path] = id.match(/^(\w+:\/\/\/?|\/)?(.*)/); - - const stack = []; - path.split("/").forEach(function (component) { - switch (component) { - case "": - case ".": - break; - case "..": - if (stack.length === 0) { - if (root !== undefined) { - throw new Error("Can't normalize absolute id '" + id + "'!"); - } else { - stack.push(".."); - } - } else if (stack[stack.length - 1] == "..") { - stack.push(".."); - } else { - stack.pop(); - } - break; - default: - stack.push(component); - break; - } - }); - - return (root ? root : "") + stack.join("/"); -} - -/** - * Create a module object with the given normalized id. - * - * @param String - * The normalized id of the module to be created. - * - * @return Object - * A module with the given id. - */ -function createModule(id) { - return Object.create(null, { - // CommonJS specifies the id property to be non-configurable and - // non-writable. - id: { - configurable: false, - enumerable: true, - value: id, - writable: false, - }, - - // CommonJS does not specify an exports property, so follow the NodeJS - // convention, which is to make it non-configurable and writable. - exports: { - configurable: false, - enumerable: true, - value: Object.create(null), - writable: true, - }, - }); -} - -/** - * Create a CommonJS loader with the following options: - * - createSandbox: - * A function that will be used to create sandboxes. It should take the name - * and prototype of the sandbox to be created, and return the newly created - * sandbox as result. This option is required. - * - globals: - * A map of names to built-in globals that will be exposed to every module. - * Defaults to the empty map. - * - loadSubScript: - * A function that will be used to load scripts in sandboxes. It should take - * the URL from and the sandbox in which the script is to be loaded, and not - * return a result. This option is required. - * - modules: - * A map from normalized ids to built-in modules that will be added to the - * module cache. Defaults to the empty map. - * - paths: - * A map of paths to base URLs that will be used to resolve relative URLs to - * absolute URLS. Defaults to the empty map. - * - resolve: - * A function that will be used to resolve relative ids to absolute ids. It - * should take the relative id of a module to be required and the absolute - * id of the requiring module as arguments, and return the absolute id of - * the module to be required as result. Defaults to resolveId above. - */ -function WorkerDebuggerLoader(options) { - /** - * Convert the given relative URL to an absolute URL, using the map of paths - * given below. - * - * @param String url - * The relative URL to be resolved. - * - * @return String - * An absolute URL. - */ - function resolveURL(url) { - let found = false; - for (const [path, baseURL] of paths) { - if (url.startsWith(path)) { - found = true; - url = url.replace(path, baseURL); - break; - } - } - if (!found) { - throw new Error("Can't resolve relative URL '" + url + "'!"); - } - - // If the url has no extension, use ".js" by default. - // Also allow loading JSMs, but they would need a shim in order to - // be loaded as a CommonJS module. (See SessionDataHelpers.jsm) - return url.endsWith(".js") || url.endsWith(".jsm") ? url : url + ".js"; - } - - /** - * Load the given module with the given url. - * - * @param Object module - * The module object to be loaded. - * @param String url - * The URL to load the module from. - */ - function loadModule(module, url) { - // CommonJS specifies 3 free variables: require, exports, and module. These - // must be exposed to every module, so define these as properties on the - // sandbox prototype. Additional built-in globals are exposed by making - // the map of built-in globals the prototype of the sandbox prototype. - const prototype = Object.create(globals); - prototype.Components = {}; - prototype.require = createRequire(module); - prototype.exports = module.exports; - prototype.module = module; - - const sandbox = createSandbox(url, prototype); - try { - loadSubScript(url, sandbox); - } catch (error) { - if (/^Error opening input stream/.test(String(error))) { - throw new Error( - "Can't load module '" + module.id + "' with url '" + url + "'!" - ); - } - throw error; - } - - // The value of exports may have been changed by the module script, so - // freeze it if and only if it is still an object. - if (typeof module.exports === "object" && module.exports !== null) { - Object.freeze(module.exports); - } - } - - /** - * Create a require function for the given module. If no module is given, - * create a require function for the top-level module instead. - * - * @param Object requirer - * The module for which the require function is to be created. - * - * @return Function - * A require function for the given module. - */ - function createRequire(requirer) { - return function require(id) { - // Make sure an id was passed. - if (id === undefined) { - throw new Error("Can't require module without id!"); - } - - // Built-in modules are cached by id rather than URL, so try to find the - // module to be required by id first. - let module = modules[id]; - if (module === undefined) { - // Failed to find the module to be required by id, so convert the id to - // a URL and try again. - - // If the id is relative, convert it to an absolute id. - if (id.startsWith(".")) { - if (requirer === undefined) { - throw new Error( - "Can't require top-level module with relative id " + - "'" + - id + - "'!" - ); - } - id = resolve(id, requirer.id); - } - - // Convert the absolute id to a normalized id. - id = normalizeId(id); - - // Convert the normalized id to a URL. - let url = id; - - // If the URL is relative, resolve it to an absolute URL. - if (url.match(/^\w+:\/\//) === null) { - url = resolveURL(id); - } - - // Try to find the module to be required by URL. - module = modules[url]; - if (module === undefined) { - // Failed to find the module to be required in the cache, so create - // a new module, load it from the given URL, and add it to the cache. - - // Add modules to the cache early so that any recursive calls to - // require for the same module will return the partially-loaded module - // from the cache instead of triggering a new load. - module = modules[url] = createModule(id); - - try { - loadModule(module, url); - } catch (error) { - // If the module failed to load, remove it from the cache so that - // subsequent calls to require for the same module will trigger a - // new load, instead of returning a partially-loaded module from - // the cache. - delete modules[url]; - throw error; - } - - Object.freeze(module); - } - } - - return module.exports; - }; - } - - const createSandbox = options.createSandbox; - const globals = options.globals || Object.create(null); - const loadSubScript = options.loadSubScript; - - // Create the module cache, by converting each entry in the map from - // normalized ids to built-in modules to a module object, with the exports - // property of each module set to a frozen version of the original entry. - const modules = options.modules || {}; - for (const id in modules) { - const module = createModule(id); - module.exports = Object.freeze(modules[id]); - modules[id] = module; - } - - // Convert the map of paths to base URLs into an array for use by resolveURL. - // The array is sorted from longest to shortest path to ensure that the - // longest path is always the first to be found. - let paths = options.paths || Object.create(null); - paths = Object.keys(paths) - .sort((a, b) => b.length - a.length) - .map(path => [path, paths[path]]); - - const resolve = options.resolve || resolveId; - - this.require = createRequire(); -} - -this.WorkerDebuggerLoader = WorkerDebuggerLoader; - -var loader = { - lazyGetter(object, name, lambda) { - Object.defineProperty(object, name, { - get() { - delete object[name]; - object[name] = lambda.apply(object); - return object[name]; - }, - configurable: true, - enumerable: true, - }); - }, - lazyServiceGetter() { - throw new Error("Can't import XPCOM service from worker thread!"); - }, - lazyRequireGetter(obj, properties, module, destructure) { - if (Array.isArray(properties) && !destructure) { - throw new Error( - "Pass destructure=true to call lazyRequireGetter with an array of properties" - ); - } - - if (!Array.isArray(properties)) { - properties = [properties]; - } - - for (const property of properties) { - Object.defineProperty(obj, property, { - get: () => - destructure - ? worker.require(module)[property] - : worker.require(module || property), - }); - } - }, -}; - -// The following APIs are defined differently depending on whether we are on the -// main thread or a worker thread. On the main thread, we use the Components -// object to implement them. On worker threads, we use the APIs provided by -// the worker debugger. - -/* eslint-disable no-shadow */ -var { - Debugger, - URL, - createSandbox, - dump, - rpc, - loadSubScript, - setImmediate, - xpcInspector, -} = function () { - // Main thread - if (typeof Components === "object") { - const principal = Components.Constructor( - "@mozilla.org/systemprincipal;1", - "nsIPrincipal" - )(); - - // To ensure that the this passed to addDebuggerToGlobal is a global, the - // Debugger object needs to be defined in a sandbox. - const sandbox = Cu.Sandbox(principal, { - wantGlobalProperties: ["ChromeUtils"], - }); - Cu.evalInSandbox( - ` -const { addDebuggerToGlobal } = ChromeUtils.importESModule( - 'resource://gre/modules/jsdebugger.sys.mjs' -); -addDebuggerToGlobal(globalThis); -`, - sandbox - ); - const Debugger = sandbox.Debugger; - - const createSandbox = function (name, prototype) { - return Cu.Sandbox(principal, { - invisibleToDebugger: true, - sandboxName: name, - sandboxPrototype: prototype, - wantComponents: false, - wantXrays: false, - }); - }; - - const rpc = undefined; - - // eslint-disable-next-line mozilla/use-services - const subScriptLoader = Cc[ - "@mozilla.org/moz/jssubscript-loader;1" - ].getService(Ci.mozIJSSubScriptLoader); - - const loadSubScript = function (url, sandbox) { - subScriptLoader.loadSubScript(url, sandbox); - }; - - const Timer = ChromeUtils.importESModule( - "resource://gre/modules/Timer.sys.mjs" - ); - - const setImmediate = function (callback) { - Timer.setTimeout(callback, 0); - }; - - const xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService( - Ci.nsIJSInspector - ); - - const { URL } = Cu.Sandbox(principal, { - wantGlobalProperties: ["URL"], - }); - - return { - Debugger, - URL, - createSandbox, - dump: this.dump, - rpc, - loadSubScript, - setImmediate, - xpcInspector, - }; - } - // Worker thread - const requestors = []; - - const scope = this; - - const xpcInspector = { - get eventLoopNestLevel() { - return requestors.length; - }, - - get lastNestRequestor() { - return requestors.length === 0 ? null : requestors[requestors.length - 1]; - }, - - enterNestedEventLoop(requestor) { - requestors.push(requestor); - scope.enterEventLoop(); - return requestors.length; - }, - - exitNestedEventLoop() { - requestors.pop(); - scope.leaveEventLoop(); - return requestors.length; - }, - }; - - return { - Debugger: this.Debugger, - URL: this.URL, - createSandbox: this.createSandbox, - dump: this.dump, - rpc: this.rpc, - loadSubScript: this.loadSubScript, - setImmediate: this.setImmediate, - xpcInspector, - }; -}.call(this); -/* eslint-enable no-shadow */ - -// Create the default instance of the worker loader, using the APIs we defined -// above. - -this.worker = new WorkerDebuggerLoader({ - createSandbox, - globals: { - isWorker: true, - dump, - loader, - rpc, - URL, - setImmediate, - retrieveConsoleEvents: this.retrieveConsoleEvents, - setConsoleEventHandler: this.setConsoleEventHandler, - clearConsoleEvents: this.clearConsoleEvents, - console, - btoa: this.btoa, - atob: this.atob, - Services: Object.create(null), - ChromeUtils, - DebuggerNotificationObserver, - - // The following APIs rely on the use of Components, and the worker debugger - // does not provide alternative definitions for them. Consequently, they are - // stubbed out both on the main thread and worker threads. - Cc: undefined, - ChromeWorker: undefined, - Ci: undefined, - Cu: undefined, - Cr: undefined, - Components: undefined, - }, - loadSubScript, - modules: { - Debugger, - xpcInspector, - }, - paths: { - // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠ - devtools: "resource://devtools", - // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠ - "xpcshell-test": "resource://test", - // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠ - }, -}); |