system-modules.rst (11603B)
1 System Modules 2 ============== 3 4 Gecko uses a variant of the standard ECMAScript module to implement the 5 browser internals. 6 7 Each system module is a per-process singleton, shared among all consumers in 8 the process. 9 10 Shared System Global 11 -------------------- 12 13 The shared system global is a privileged global dedicated for the system 14 modules. 15 16 All system modules are imported into the shared system global (except for 17 modules loaded into the `DevTools distinct system global`_). 18 19 See ``mozJSModuleLoader::CreateLoaderGlobal`` in `mozJSModuleLoader.cpp <https://searchfox.org/mozilla-central/source/js/xpconnect/loader/mozJSModuleLoader.cpp>`_ for details about the global and built-in functions. 20 21 Defining a Module 22 ----------------- 23 24 The system module is written as a subset of the standard ECMAScript module 25 (see `Limitations`_ below), and symbols can be exported with the standard 26 ``export`` declarations. 27 28 The system module uses the ``.sys.mjs`` filename extension. 29 30 .. code:: JavaScript 31 32 // Test.sys.mjs 33 34 export const TestUtils = { 35 hello() { 36 console.log("hello"); 37 } 38 }; 39 40 export function TestFunc() { 41 console.log("hi"); 42 } 43 44 System modules can use other extensions than ``.sys.mjs``, but in that case 45 make sure the right ESLint rules are applied to them. 46 47 Importing a Module 48 ------------------ 49 50 Immediate Import 51 ^^^^^^^^^^^^^^^^ 52 53 Inside all privileged code, system modules can be imported with 54 ``ChromeUtils.importESModule``. 55 The system module is imported synchronously, and the namespace object is 56 returned. 57 58 .. note:: 59 60 At the script or module top-level, if the module is not going to be 61 immediately and unconditionally used, please consider using 62 ``ChromeUtils.defineESModuleGetters`` below instead, in order to improve 63 the browser startup performance and the window open performance. 64 65 .. code:: JavaScript 66 67 // Privileged code. 68 69 const { TestUtils } = 70 ChromeUtils.importESModule("resource://gre/modules/Test.sys.mjs"); 71 72 TestUtils.hello(); 73 74 Inside system modules, other system modules can be imported with the regular 75 ``import`` declaration and the dynamic ``import()``. 76 77 .. code:: JavaScript 78 79 // System module top-level scope. 80 81 import { TestUtils } from "resource://gre/modules/Test.sys.mjs"; 82 83 TestUtils.hello(); 84 85 .. code:: JavaScript 86 87 // A function inside a system module. 88 89 async function f() { 90 const { TestUtils } = await import("resource://gre/modules/Test.sys.mjs"); 91 TestUtils.hello(); 92 } 93 94 .. note:: 95 96 The ``import`` declaration and the dynamic ``import()`` can be used only 97 from system modules. 98 If the system module is imported from regular modules in some random global 99 with these ways, the module is imported into that global instead of 100 the shared system global, and it becomes a different instance. 101 102 Lazy Import 103 ^^^^^^^^^^^ 104 105 Modules can be lazily imported with ``ChromeUtils.defineESModuleGetters``. 106 ``ChromeUtils.defineESModuleGetters`` receives a target object, and a object 107 that defines a map from the exported symbol name to the module URI. 108 Those symbols are defined on the target object as a lazy getter. 109 The module is imported on the first access, and the getter is replaced with 110 a data property with the exported symbol's value. 111 112 .. note:: 113 114 ``ChromeUtils.defineESModuleGetters`` is applicable only to the exported 115 symbol, and not to the namespace object. 116 See the next section for how to define the lazy getter for the namespace 117 object. 118 119 The convention for the target object's name is ``lazy``. 120 121 .. code:: JavaScript 122 123 // Privileged code. 124 125 const lazy = {} 126 ChromeUtils.defineESModuleGetters(lazy, { 127 TestUtils: "resource://gre/modules/Test.sys.mjs", 128 }); 129 130 function f() { 131 // Test.sys.mjs is imported on the first access. 132 lazy.TestUtils.hello(); 133 } 134 135 In order to import multiple symbols from the same module, add the corresponding 136 property with the symbol name and the module URI for each. 137 138 .. code:: JavaScript 139 140 // Privileged code. 141 142 const lazy = {} 143 ChromeUtils.defineESModuleGetters(lazy, { 144 TestUtils: "resource://gre/modules/Test.sys.mjs", 145 TestFunc: "resource://gre/modules/Test.sys.mjs", 146 }); 147 148 See `ChromeUtils.webidl <https://searchfox.org/mozilla-central/source/dom/chrome-webidl/ChromeUtils.webidl>`_ for more details. 149 150 Using the Namespace Object 151 -------------------------- 152 153 The namespace object returned by the ``ChromeUtils.importESModule`` call 154 can also be directly used. 155 156 .. code:: JavaScript 157 158 // Privileged code. 159 160 const TestNS = 161 ChromeUtils.importESModule("resource://gre/modules/Test.sys.mjs"); 162 163 TestNS.TestUtils.hello(); 164 165 This is almost same as the following normal ``import`` declaration. 166 167 .. code:: JavaScript 168 169 // System module top-level scope. 170 171 import * as TestNS from "resource://gre/modules/Test.sys.mjs"; 172 173 TestNS.TestUtils.hello(); 174 175 or the dynamic import without the destructuring assignment. 176 177 .. code:: JavaScript 178 179 async function f() { 180 const TestNS = await import("resource://gre/modules/Test.sys.mjs"); 181 TestNS.TestUtils.hello(); 182 } 183 184 185 ``ChromeUtils.defineESModuleGetters`` does not support directly using 186 the namespace object. 187 Possible workaround is to use ``ChromeUtils.defineLazyGetter`` with 188 ``ChromeUtils.importESModule``. 189 190 .. code:: JavaScript 191 192 const lazy = {} 193 ChromeUtils.defineLazyGetter(lazy, "TestNS", () => 194 ChromeUtils.importESModule("resource://gre/modules/Test.sys.mjs")); 195 196 function f() { 197 // Test.sys.mjs is imported on the first access. 198 lazy.TestNS.TestUtils.hello(); 199 } 200 201 202 Importing from Unprivileged Testing Code 203 ---------------------------------------- 204 205 In unprivileged testing code such as mochitest plain, 206 ``ChromeUtils.importESModule`` is available as 207 ``SpecialPowers.ChromeUtils.importESModule``. 208 209 .. code:: JavaScript 210 211 // Mochitest-plain testcase. 212 213 const { TestUtils } = 214 SpecialPowers.ChromeUtils.importESModule( 215 "resource://gre/modules/Test.sys.mjs" 216 ); 217 218 Importing from C++ Code 219 ----------------------- 220 221 C++ code can import ES modules with ``do_ImportESModule`` function. 222 The exported object should follow the specified XPCOM interface. 223 224 .. code:: c++ 225 226 nsCOMPtr<nsIUtils> utils = do_ImportESModule( 227 "resource://gre/modules/Test.sys.mjs", "Utils"); 228 229 See `nsImportModule.h <https://searchfox.org/mozilla-central/source/js/xpconnect/loader/nsImportModule.h>`_ for more details. 230 231 Lifetime 232 -------- 233 234 The shared system global has the almost same lifetime as the process, and the 235 system modules are never unloaded until the end of the shared system global's 236 lifetime. 237 238 If a module need to be dynamically updated with the same URI, for example with 239 privileged extensions getting updated, they can add query string to distinguish 240 different versions. 241 242 Lifetime of the Global Variables 243 -------------------------------- 244 245 Unlike the classic scripts, the ECMAScript's module's global variables are not 246 properties of any objects. 247 248 If the all strong references to the document goes away, the objects held by 249 the module global variables are ready to be GCed. This means, the module global 250 variables don't have the same lifetime as the module itself. 251 252 In privileged scripts, there can be multiple usage of weak-references and 253 similar things, such as XPCOM ``nsISupportsWeakReference``, or 254 the window-less ``browser`` element and its content document. 255 256 If those objects needs to be kept alive longer, for example, if they need to 257 have the same lifetime as the module itself, there should be another strong 258 reference to them. 259 260 Possible options for those objects are the following: 261 262 * Export the variable that holds the object 263 * Store the object into the exported object's property 264 * Close over the variable from the function that's reachable from the exported objects 265 * Do not use weak reference 266 267 Utility Functions 268 ----------------- 269 270 ``Cu.isESmoduleLoaded`` is a function to query whether the module is already 271 imported to the shared system global. 272 273 .. code:: JavaScript 274 275 if (Cu.isESmoduleLoaded("resource://gre/modules/Test.sys.mjs")) { 276 // ... 277 } 278 279 ``Cu.loadedESModules`` returns a list of URLs of the already-imported modules. 280 This is only for startup testing purpose, and this shouldn't be used in 281 the production code. 282 283 .. code:: JavaScript 284 285 for (const uri of Cu.loadedESModules) { 286 // ... 287 } 288 289 If ``browser.startup.record`` preference is set to ``true`` at the point of 290 importing modules, ``Cu.getModuleImportStack`` returns the call stack of the 291 module import. 292 This is only for the debugging purpose. 293 294 .. code:: JavaScript 295 296 Services.prefs.setBoolPref("browser.startup.record", true); 297 298 const { TestUtils } = 299 ChromeUtils.importESModule("resource://gre/modules/Test.sys.mjs"); 300 301 console.log( 302 Cu.getModuleImportStack("resource://gre/modules/Test.sys.mjs")); 303 304 See `xpccomponents.idl <https://searchfox.org/mozilla-central/source/js/xpconnect/idl/xpccomponents.idl>`_ for more details. 305 306 Limitations 307 ----------- 308 309 Top-level ``await`` is not supported in the system module, due to the 310 requirement for synchronous loading. 311 312 DevTools Distinct System Global 313 ------------------------------- 314 315 DevTools-related system modules can be imported into a separate dedicate global, 316 which is used when debugging the browser. 317 318 The target global can be controlled by the ``global`` property of the 2nd 319 parameter of ``ChromeUtils.importESModule``, or the 3rd parameter of 320 ``ChromeUtils.defineESModuleGetters``. 321 322 The ``global`` property defaults to ``"shared"``, which is the shared system 323 global. 324 Passing ``"devtools"`` imports the module in the DevTools distinct system 325 global. 326 327 .. code:: JavaScript 328 329 const { TestUtils } = 330 ChromeUtils.importESModule("resource://gre/modules/Test.sys.mjs", { 331 global: "devtools", 332 }); 333 334 TestUtils.hello(); 335 336 .. code:: JavaScript 337 338 const lazy = {} 339 ChromeUtils.defineESModuleGetters(lazy, { 340 TestUtils: "resource://gre/modules/Test.sys.mjs", 341 }, { 342 global: "devtools", 343 }); 344 345 If the system module file is shared between both cases, ``"contextual"`` can be 346 used. The module is imported into the DevTools distinct system global if the 347 current global is the DevTools distinct system global. Otherwise the module 348 is imported into the shared system global. 349 350 See ``ImportESModuleTargetGlobal`` in `ChromeUtils.webidl <https://searchfox.org/mozilla-central/source/dom/chrome-webidl/ChromeUtils.webidl>`_ for more details. 351 352 Integration with JSActors 353 ------------------------- 354 355 :ref:`JSActors <JSActors>` are implemented with system modules. 356 357 See the :ref:`JSActors <JSActors>` document for more details. 358 359 Integration with XPCOM Components 360 --------------------------------- 361 362 :ref:`XPCOM Components <Defining XPCOM Components>` can be implemented with 363 system modules, by passing ``esModule`` option. 364 365 See the :ref:`XPCOM Components <Defining XPCOM Components>` document for more 366 details. 367 368 Importing into Current Global 369 ----------------------------- 370 371 ``ChromeUtils.importESModule`` can be used also for importing modules into 372 the current global, by passing ``{ global: "current" }`` option. 373 In this case the imported module is not a system module. 374 375 See the :ref:`JS Loader APIs <JS Loader APIs>` document for more details. 376 377 JSM 378 --- 379 380 Prior to the ECMAScript-module-based system modules, Firefox codebase had been 381 using a Mozilla-specific module system called JSM. 382 383 The details around the migration is described in `the migration document <https://docs.google.com/document/d/1cpzIK-BdP7u6RJSar-Z955GV--2Rj8V4x2vl34m36Go/edit?usp=sharing>`_.