diff options
Diffstat (limited to 'devtools/shared/loader/Loader.sys.mjs')
-rw-r--r-- | devtools/shared/loader/Loader.sys.mjs | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/devtools/shared/loader/Loader.sys.mjs b/devtools/shared/loader/Loader.sys.mjs new file mode 100644 index 0000000000..e34810dde8 --- /dev/null +++ b/devtools/shared/loader/Loader.sys.mjs @@ -0,0 +1,209 @@ +/* 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/. */ + +/** + * Manages the base loader (base-loader.sys.mjs) instance used to load the developer tools. + */ + +import { + Loader, + Require, + resolveURI, + unload, +} from "resource://devtools/shared/loader/base-loader.sys.mjs"; +import { requireRawId } from "resource://devtools/shared/loader/loader-plugin-raw.sys.mjs"; + +export const DEFAULT_SANDBOX_NAME = "DevTools (Module loader)"; + +var gNextLoaderID = 0; + +/** + * The main devtools API. The standard instance of this loader is exported as + * |loader| below, but if a fresh copy of the loader is needed, then a new + * one can also be created. + * + * The two following boolean flags are used to control the sandboxes into + * which the modules are loaded. + * @param invisibleToDebugger boolean + * If true, the modules won't be visible by the Debugger API. + * This typically allows to hide server modules from the debugger panel. + * @param freshCompartment boolean + * If true, the modules will be forced to be loaded in a distinct + * compartment. It is typically used to load the modules in a distinct + * system compartment, different from the main one, which is shared by + * all JSMs, XPCOMs and modules loaded with this flag set to true. + * We use this in order to debug modules loaded in this shared system + * compartment. The debugger actor has to be running in a distinct + * compartment than the context it is debugging. + * @param useDevToolsLoaderGlobal boolean + * If true, the loader will reuse the current global to load other + * modules instead of creating a sandbox with custom options. Cannot be + * used with invisibleToDebugger and/or freshCompartment. + * TODO: This should ultimately replace invisibleToDebugger. + */ +export function DevToolsLoader({ + invisibleToDebugger = false, + freshCompartment = false, + useDevToolsLoaderGlobal = false, +} = {}) { + if (useDevToolsLoaderGlobal && (invisibleToDebugger || freshCompartment)) { + throw new Error( + "Loader cannot use invisibleToDebugger or freshCompartment if useDevToolsLoaderGlobal is true" + ); + } + + const paths = { + // This resource:// URI is only registered when running DAMP tests. + // This is done by: testing/talos/talos/tests/devtools/addon/api.js + "damp-test": "resource://damp-test/content", + // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠ + devtools: "resource://devtools", + // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠ + // Allow access to xpcshell test items from the loader. + "xpcshell-test": "resource://test", + + // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠ + // Allow access to locale data using paths closer to what is + // used in the source tree. + "devtools/client/locales": "chrome://devtools/locale", + "devtools/shared/locales": "chrome://devtools-shared/locale", + "devtools/startup/locales": "chrome://devtools-startup/locale", + "toolkit/locales": "chrome://global/locale", + }; + + const sharedGlobal = useDevToolsLoaderGlobal + ? Cu.getGlobalForObject({}) + : undefined; + this.loader = new Loader({ + paths, + sharedGlobal, + invisibleToDebugger, + freshCompartment, + sandboxName: useDevToolsLoaderGlobal + ? "DevTools (Server Module Loader)" + : DEFAULT_SANDBOX_NAME, + // Make sure `define` function exists. JSON Viewer needs modules in AMD + // format, as it currently uses RequireJS from a content document and + // can't access our usual loaders. So, any modules shared with the JSON + // Viewer should include a define wrapper: + // + // // Make this available to both AMD and CJS environments + // define(function(require, exports, module) { + // ... code ... + // }); + // + // Bug 1248830 will work out a better plan here for our content module + // loading needs, especially as we head towards devtools.html. + supportAMDModules: true, + requireHook: (id, require) => { + if (id.startsWith("raw!") || id.startsWith("theme-loader!")) { + return requireRawId(id, require); + } + return require(id); + }, + }); + + this.require = Require(this.loader, { id: "devtools" }); + + // Various globals are available from ESM, but not from sandboxes, + // inject them into the globals list. + // Changes here should be mirrored to devtools/.eslintrc. + const injectedGlobals = { + CanonicalBrowsingContext, + console, + BrowsingContext, + ChromeWorker, + DebuggerNotificationObserver, + DOMPoint, + DOMQuad, + DOMRect, + fetch, + HeapSnapshot, + IOUtils, + L10nRegistry, + Localization, + NamedNodeMap, + NodeFilter, + PathUtils, + Services, + StructuredCloneHolder, + TelemetryStopwatch, + WebExtensionPolicy, + WindowGlobalParent, + WindowGlobalChild, + }; + for (const name in injectedGlobals) { + this.loader.globals[name] = injectedGlobals[name]; + } + + // Fetch custom pseudo modules and globals + const { modules, globals } = this.require( + "resource://devtools/shared/loader/builtin-modules.js" + ); + + // Register custom pseudo modules to the current loader instance + for (const id in modules) { + const uri = resolveURI(id, this.loader.mapping); + this.loader.modules[uri] = { + get exports() { + return modules[id]; + }, + }; + } + + // Register custom globals to the current loader instance + Object.defineProperties( + this.loader.sharedGlobal, + Object.getOwnPropertyDescriptors(globals) + ); + + // Define the loader id for these two usecases: + // * access via the JSM (this.id) + // let { loader } = ChromeUtils.importESModule("resource://devtools/shared/loader/Loader.sys.mjs"); + // loader.id + this.id = gNextLoaderID++; + // * access via module's `loader` global + // loader.id + globals.loader.id = this.id; + globals.loader.invisibleToDebugger = invisibleToDebugger; + + // Expose lazy helpers on `loader` + // ie. when you use it like that from a JSM: + // let { loader } = ChromeUtils.importESModule("resource://devtools/shared/loader/Loader.sys.mjs"); + // loader.lazyGetter(...); + this.lazyGetter = globals.loader.lazyGetter; + this.lazyServiceGetter = globals.loader.lazyServiceGetter; + this.lazyRequireGetter = globals.loader.lazyRequireGetter; +} + +DevToolsLoader.prototype = { + destroy(reason = "shutdown") { + unload(this.loader, reason); + delete this.loader; + }, + + /** + * Return true if |id| refers to something requiring help from a + * loader plugin. + */ + isLoaderPluginId(id) { + return id.startsWith("raw!"); + }, +}; + +// Export the standard instance of DevToolsLoader used by the tools. +export var loader = new DevToolsLoader({ + /** + * Sets whether the compartments loaded by this instance should be invisible + * to the debugger. Invisibility is needed for loaders that support debugging + * of chrome code. This is true of remote target environments, like Fennec or + * B2G. It is not the default case for desktop Firefox because we offer the + * Browser Toolbox for chrome debugging there, which uses its own, separate + * loader instance. + * @see devtools/client/framework/browser-toolbox/Launcher.sys.mjs + */ + invisibleToDebugger: Services.appinfo.name !== "Firefox", +}); + +export var require = loader.require; |