commit ad0ae2c01b2fcd3f159d45e3733ddb53cc551af1
parent 1e90610dee834dc9aa17c036ab8b578f2e47c0a8
Author: Lorenz A <me@lorenzackermann.xyz>
Date: Thu, 11 Dec 2025 07:07:47 +0000
Bug 2004233 - [devtools] Turn devtools/shared/loader/browser-loader.sys.mjs into an ES class. r=devtools-reviewers,bomsy
Differential Revision: https://phabricator.services.mozilla.com/D275636
Diffstat:
1 file changed, 113 insertions(+), 119 deletions(-)
diff --git a/devtools/shared/loader/browser-loader.sys.mjs b/devtools/shared/loader/browser-loader.sys.mjs
@@ -69,6 +69,23 @@ const browserBasedDirsRegExp =
/^resource\:\/\/devtools\/client\/\S*\/components\//;
/**
+ * @typedef {object} BrowserLoaderOptions
+ * @property {string} baseURI
+ * Base path to load modules from. If null or undefined, only
+ * the shared vendor/components modules are loaded with the browser
+ * loader.
+ * @property {Function} commonLibRequire
+ * Require function that should be used to load common libraries, like React.
+ * Allows for sharing common modules between tools, instead of loading a new
+ * instance into each tool. For example, pass "toolbox.browserRequire" here.
+ * @property {boolean} useOnlyShared
+ * If true, ignores `baseURI` and only loads the shared
+ * BROWSER_BASED_DIRS via BrowserLoader.
+ * @property {Window} window
+ * The window instance to evaluate modules within
+ */
+
+/**
* Create a loader to be used in a browser environment. This evaluates
* modules in their own environment, but sets window (the normal
* global object) as the sandbox prototype, so when a variable is not
@@ -84,15 +101,7 @@ const browserBasedDirsRegExp =
* `devtools/client/shared/{vendor/components}`, which is where shared libraries
* and React components live that should be evaluated in a browser environment.
*
- * @param string baseURI
- * Base path to load modules from. If null or undefined, only
- * the shared vendor/components modules are loaded with the browser
- * loader.
- * @param Object window
- * The window instance to evaluate modules within
- * @param Boolean useOnlyShared
- * If true, ignores `baseURI` and only loads the shared
- * BROWSER_BASED_DIRS via BrowserLoader.
+ * @param {BrowserLoaderOptions} options
* @return Object
* An object with two properties:
* - loader: the Loader instance
@@ -110,129 +119,114 @@ export function BrowserLoader(options) {
/**
* Private class used to build the Loader instance and require method returned
* by BrowserLoader(baseURI, window).
- *
- * @param string baseURI
- * Base path to load modules from.
- * @param Function commonLibRequire
- * Require function that should be used to load common libraries, like React.
- * Allows for sharing common modules between tools, instead of loading a new
- * instance into each tool. For example, pass "toolbox.browserRequire" here.
- * @param Boolean useOnlyShared
- * If true, ignores `baseURI` and only loads the shared
- * BROWSER_BASED_DIRS via BrowserLoader.
- * @param Object window
- * The window instance to evaluate modules within
*/
-function BrowserLoaderBuilder({
- baseURI,
- commonLibRequire,
- useOnlyShared,
- window,
-}) {
- assert(
- !!baseURI !== !!useOnlyShared,
- "Cannot use both `baseURI` and `useOnlyShared`."
- );
+class BrowserLoaderBuilder {
+ /**
+ * @param {BrowserLoaderOptions} options
+ */
+ constructor({ baseURI, commonLibRequire, useOnlyShared, window }) {
+ assert(
+ !!baseURI !== !!useOnlyShared,
+ "Cannot use both `baseURI` and `useOnlyShared`."
+ );
- const loaderOptions = devtoolsRequire("@loader/options");
+ const loaderOptions = devtoolsRequire("@loader/options");
- const opts = {
- sandboxPrototype: window,
- sandboxName: "DevTools (UI loader)",
- paths: loaderOptions.paths,
- // Make sure `define` function exists. This allows defining some modules
- // in AMD format while retaining CommonJS compatibility through this hook.
- // 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| requires special handling, simply defer to devtools
- // immediately.
- if (loader.isLoaderPluginId(id)) {
- return devtoolsRequire(id);
- }
+ const opts = {
+ sandboxPrototype: window,
+ sandboxName: "DevTools (UI loader)",
+ paths: loaderOptions.paths,
+ // Make sure `define` function exists. This allows defining some modules
+ // in AMD format while retaining CommonJS compatibility through this hook.
+ // 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| requires special handling, simply defer to devtools
+ // immediately.
+ if (loader.isLoaderPluginId(id)) {
+ return devtoolsRequire(id);
+ }
- let uri = require.resolve(id);
+ let uri = require.resolve(id);
- // The mocks can be set from tests using browser-loader-mocks.js setMockedModule().
- // If there is an entry for a given uri in the `mocks` object, return it instead of
- // requiring the module.
- if (flags.testing && lazy.getMockedModule(uri)) {
- return lazy.getMockedModule(uri);
- }
+ // The mocks can be set from tests using browser-loader-mocks.js setMockedModule().
+ // If there is an entry for a given uri in the `mocks` object, return it instead of
+ // requiring the module.
+ if (flags.testing && lazy.getMockedModule(uri)) {
+ return lazy.getMockedModule(uri);
+ }
- // Load all React modules as ES Modules, in the Browser Loader global.
- // For this we have to ensure using ChromeUtils.importESModule with `global:"current"`,
- // but executed from the Loader global scope. `syncImport` does that.
- if (REACT_ESM_MODULES.has(uri)) {
- uri = uri.replace(/.js$/, ".mjs");
- const moduleExports = syncImport(uri);
- return moduleExports.default || moduleExports;
- }
- if (uri.endsWith(".mjs")) {
- const moduleExports = syncImport(uri);
- return moduleExports.default || moduleExports;
- }
+ // Load all React modules as ES Modules, in the Browser Loader global.
+ // For this we have to ensure using ChromeUtils.importESModule with `global:"current"`,
+ // but executed from the Loader global scope. `syncImport` does that.
+ if (REACT_ESM_MODULES.has(uri)) {
+ uri = uri.replace(/.js$/, ".mjs");
+ const moduleExports = syncImport(uri);
+ return moduleExports.default || moduleExports;
+ }
+ if (uri.endsWith(".mjs")) {
+ const moduleExports = syncImport(uri);
+ return moduleExports.default || moduleExports;
+ }
- if (
- commonLibRequire &&
- COMMON_LIBRARY_DIRS.some(dir => uri.startsWith(dir))
- ) {
- return commonLibRequire(uri);
- }
+ if (
+ commonLibRequire &&
+ COMMON_LIBRARY_DIRS.some(dir => uri.startsWith(dir))
+ ) {
+ return commonLibRequire(uri);
+ }
- // Check if the URI matches one of hardcoded paths or a regexp.
- const isBrowserDir =
- BROWSER_BASED_DIRS.some(dir => uri.startsWith(dir)) ||
- uri.match(browserBasedDirsRegExp) != null;
+ // Check if the URI matches one of hardcoded paths or a regexp.
+ const isBrowserDir =
+ BROWSER_BASED_DIRS.some(dir => uri.startsWith(dir)) ||
+ uri.match(browserBasedDirsRegExp) != null;
- if ((useOnlyShared || !uri.startsWith(baseURI)) && !isBrowserDir) {
- return devtoolsRequire(uri);
- }
+ if ((useOnlyShared || !uri.startsWith(baseURI)) && !isBrowserDir) {
+ return devtoolsRequire(uri);
+ }
- return require(uri);
- },
- globals: {
- // Allow modules to use the window's console to ensure logs appear in a
- // tab toolbox, if one exists, instead of just the browser console.
- console: window.console,
- // Allow modules to use the DevToolsLoader lazy loading helpers.
- loader: {
- lazyGetter: loader.lazyGetter,
- lazyServiceGetter: loader.lazyServiceGetter,
- lazyRequireGetter: this.lazyRequireGetter.bind(this),
+ return require(uri);
},
- },
- };
+ globals: {
+ // Allow modules to use the window's console to ensure logs appear in a
+ // tab toolbox, if one exists, instead of just the browser console.
+ console: window.console,
+ // Allow modules to use the DevToolsLoader lazy loading helpers.
+ loader: {
+ lazyGetter: loader.lazyGetter,
+ lazyServiceGetter: loader.lazyServiceGetter,
+ lazyRequireGetter: this.lazyRequireGetter.bind(this),
+ },
+ },
+ };
- const mainModule = BaseLoader.Module(baseURI, joinURI(baseURI, "main.js"));
- this.loader = BaseLoader.Loader(opts);
+ const mainModule = BaseLoader.Module(baseURI, joinURI(baseURI, "main.js"));
+ this.loader = BaseLoader.Loader(opts);
- const scope = this.loader.sharedGlobal;
- Cu.evalInSandbox(
- "function __syncImport(uri) { return ChromeUtils.importESModule(uri, {global: 'current'})}",
- scope
- );
- const syncImport = scope.__syncImport;
+ const scope = this.loader.sharedGlobal;
+ Cu.evalInSandbox(
+ "function __syncImport(uri) { return ChromeUtils.importESModule(uri, {global: 'current'})}",
+ scope
+ );
+ const syncImport = scope.__syncImport;
- // When running tests, expose the BrowserLoader instance for metrics tests.
- if (flags.testing) {
- window.getBrowserLoaderForWindow = () => this;
+ // When running tests, expose the BrowserLoader instance for metrics tests.
+ if (flags.testing) {
+ window.getBrowserLoaderForWindow = () => this;
+ }
+ this.require = BaseLoader.Require(this.loader, mainModule);
+ this.lazyRequireGetter = this.lazyRequireGetter.bind(this);
}
- this.require = BaseLoader.Require(this.loader, mainModule);
- this.lazyRequireGetter = this.lazyRequireGetter.bind(this);
-}
-
-BrowserLoaderBuilder.prototype = {
/**
* Define a getter property on the given object that requires the given
* module. This enables delaying importing modules until the module is
@@ -270,5 +264,5 @@ BrowserLoaderBuilder.prototype = {
: this.require(module || property);
});
}
- },
-};
+ }
+}