Loader.sys.mjs (7060B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /** 6 * Manages the base loader (base-loader.sys.mjs) instance used to load the developer tools. 7 */ 8 9 import { 10 Loader, 11 Require, 12 resolveURI, 13 unload, 14 } from "resource://devtools/shared/loader/base-loader.sys.mjs"; 15 import { requireRawId } from "resource://devtools/shared/loader/loader-plugin-raw.sys.mjs"; 16 17 export const DEFAULT_SANDBOX_NAME = "DevTools (Module loader)"; 18 19 var gNextLoaderID = 0; 20 21 /** 22 * The main devtools API. The standard instance of this loader is exported as 23 * |loader| below, but if a fresh copy of the loader is needed, then a new 24 * one can also be created. 25 * 26 27 */ 28 export class DevToolsLoader { 29 /** 30 * The two following boolean flags are used to control the sandboxes into 31 * which the modules are loaded. 32 * 33 * @param {object} options 34 * @param {boolean} options.freshCompartment 35 * If true, the modules will be forced to be loaded in a distinct 36 * compartment. It is typically used to load the modules in a distinct 37 * system compartment, different from the main one, which is shared by 38 * all ESMs, XPCOMs and modules loaded with this flag set to true. 39 * We use this in order to debug modules loaded in this shared system 40 * compartment. The debugger actor has to be running in a distinct 41 * compartment than the context it is debugging. 42 * @param {boolean} options.useDevToolsLoaderGlobal 43 * If true, the loader will reuse the current global to load other 44 * modules instead of creating a sandbox with custom options. Cannot be 45 * used with freshCompartment. 46 */ 47 48 constructor({ 49 freshCompartment = false, 50 useDevToolsLoaderGlobal = false, 51 } = {}) { 52 if (useDevToolsLoaderGlobal && freshCompartment) { 53 throw new Error( 54 "Loader cannot use freshCompartment if useDevToolsLoaderGlobal is true" 55 ); 56 } 57 58 const paths = { 59 // This resource:// URI is only registered when running DAMP tests. 60 // This is done by: testing/talos/talos/tests/devtools/addon/api.js 61 "damp-test": "resource://damp-test/content", 62 // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠ 63 devtools: "resource://devtools", 64 // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠ 65 // Allow access to xpcshell test items from the loader. 66 "xpcshell-test": "resource://test", 67 68 // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠ 69 // Allow access to locale data using paths closer to what is 70 // used in the source tree. 71 "devtools/client/locales": "chrome://devtools/locale", 72 "devtools/shared/locales": "chrome://devtools-shared/locale", 73 "devtools/startup/locales": "chrome://devtools-startup/locale", 74 "toolkit/locales": "chrome://global/locale", 75 }; 76 77 // In case the Loader ESM is loaded in DevTools global, 78 // also reuse this global for all CommonJS modules. 79 const sharedGlobal = 80 useDevToolsLoaderGlobal || 81 // eslint-disable-next-line mozilla/reject-globalThis-modification 82 Cu.getRealmLocation(globalThis) == "DevTools global" 83 ? Cu.getGlobalForObject({}) 84 : undefined; 85 this.loader = new Loader({ 86 paths, 87 sharedGlobal, 88 freshCompartment, 89 sandboxName: useDevToolsLoaderGlobal 90 ? "DevTools (Server Module Loader)" 91 : DEFAULT_SANDBOX_NAME, 92 // Make sure `define` function exists. JSON Viewer needs modules in AMD 93 // format, as it currently uses RequireJS from a content document and 94 // can't access our usual loaders. So, any modules shared with the JSON 95 // Viewer should include a define wrapper: 96 // 97 // // Make this available to both AMD and CJS environments 98 // define(function(require, exports, module) { 99 // ... code ... 100 // }); 101 // 102 // Bug 1248830 will work out a better plan here for our content module 103 // loading needs, especially as we head towards devtools.html. 104 supportAMDModules: true, 105 requireHook: (id, require) => { 106 if (id.startsWith("raw!") || id.startsWith("theme-loader!")) { 107 return requireRawId(id, require); 108 } 109 return require(id); 110 }, 111 }); 112 113 this.require = Require(this.loader, { id: "devtools" }); 114 115 // Various globals are available from ESM, but not from sandboxes, 116 // inject them into the globals list. 117 // Changes here should be mirrored to devtools/.eslintrc. 118 const injectedGlobals = { 119 BrowsingContext, 120 CanonicalBrowsingContext, 121 ChromeWorker, 122 console, 123 DebuggerNotificationObserver, 124 DOMPoint, 125 DOMQuad, 126 DOMRect, 127 fetch, 128 Glean, 129 HeapSnapshot, 130 IOUtils, 131 L10nRegistry, 132 Localization, 133 NamedNodeMap, 134 NodeFilter, 135 PathUtils, 136 Services, 137 StructuredCloneHolder, 138 WebExtensionPolicy, 139 WebSocket, 140 WindowGlobalChild, 141 WindowGlobalParent, 142 }; 143 for (const name in injectedGlobals) { 144 this.loader.globals[name] = injectedGlobals[name]; 145 } 146 147 // Fetch custom pseudo modules and globals 148 const { modules, globals } = this.require( 149 "resource://devtools/shared/loader/builtin-modules.js" 150 ); 151 152 // Register custom pseudo modules to the current loader instance 153 for (const id in modules) { 154 const uri = resolveURI(id, this.loader.mapping); 155 this.loader.modules[uri] = { 156 get exports() { 157 return modules[id]; 158 }, 159 }; 160 } 161 162 // Register custom globals to the current loader instance 163 Object.defineProperties( 164 this.loader.sharedGlobal, 165 Object.getOwnPropertyDescriptors(globals) 166 ); 167 168 // Define the loader id for these two usecases: 169 // * access via the ESM (this.id) 170 // let { loader } = ChromeUtils.importESModule("resource://devtools/shared/loader/Loader.sys.mjs"); 171 // loader.id 172 this.id = gNextLoaderID++; 173 // * access via module's `loader` global 174 // loader.id 175 globals.loader.id = this.id; 176 177 // Expose lazy helpers on `loader` 178 // ie. when you use it like that from a ESM: 179 // let { loader } = ChromeUtils.importESModule("resource://devtools/shared/loader/Loader.sys.mjs"); 180 // loader.lazyGetter(...); 181 this.lazyGetter = globals.loader.lazyGetter; 182 this.lazyServiceGetter = globals.loader.lazyServiceGetter; 183 this.lazyRequireGetter = globals.loader.lazyRequireGetter; 184 } 185 destroy(reason = "shutdown") { 186 unload(this.loader, reason); 187 delete this.loader; 188 } 189 190 /** 191 * Return true if |id| refers to something requiring help from a 192 * loader plugin. 193 */ 194 isLoaderPluginId(id) { 195 return id.startsWith("raw!"); 196 } 197 } 198 199 // Export the standard instance of DevToolsLoader used by the tools. 200 export var loader = new DevToolsLoader(); 201 202 export var require = loader.require;