tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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>`_.