tor-browser

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

browser-loader.sys.mjs (10289B)


      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 import * as BaseLoader from "resource://devtools/shared/loader/base-loader.sys.mjs";
      6 import {
      7  require as devtoolsRequire,
      8  loader,
      9 } from "resource://devtools/shared/loader/Loader.sys.mjs";
     10 
     11 const flags = devtoolsRequire("devtools/shared/flags");
     12 const { joinURI } = devtoolsRequire("devtools/shared/path");
     13 const { assert } = devtoolsRequire("devtools/shared/DevToolsUtils");
     14 
     15 const lazy = {};
     16 
     17 loader.lazyRequireGetter(
     18  lazy,
     19  "getMockedModule",
     20  "resource://devtools/shared/loader/browser-loader-mocks.js",
     21  {}
     22 );
     23 
     24 const BROWSER_BASED_DIRS = [
     25  "resource://devtools/client/inspector/boxmodel",
     26  "resource://devtools/client/inspector/changes",
     27  "resource://devtools/client/inspector/computed",
     28  "resource://devtools/client/inspector/events",
     29  "resource://devtools/client/inspector/flexbox",
     30  "resource://devtools/client/inspector/fonts",
     31  "resource://devtools/client/inspector/grids",
     32  "resource://devtools/client/inspector/layout",
     33  "resource://devtools/client/inspector/markup",
     34  "resource://devtools/client/jsonview",
     35  "resource://devtools/client/netmonitor/src/utils",
     36  "resource://devtools/client/shared/fluent-l10n",
     37  "resource://devtools/client/shared/redux",
     38  "resource://devtools/client/shared/vendor",
     39  // Ensure loading debugger modules in the document scope
     40  // when they are loaded from SmartTrace, which requires to load Reps/ObjectInspector
     41  // in a document scope
     42  "resource://devtools/client/debugger/src",
     43 ];
     44 
     45 const COMMON_LIBRARY_DIRS = ["resource://devtools/client/shared/vendor"];
     46 
     47 const VENDOR_URI = "resource://devtools/client/shared/vendor/";
     48 const REACT_ESM_MODULES = new Set([
     49  VENDOR_URI + "react-dev.js",
     50  VENDOR_URI + "react.js",
     51  VENDOR_URI + "react-dom-dev.js",
     52  VENDOR_URI + "react-dom.js",
     53  VENDOR_URI + "react-dom-factories.js",
     54  VENDOR_URI + "react-dom-server-dev.js",
     55  VENDOR_URI + "react-dom-server.js",
     56  VENDOR_URI + "react-prop-types-dev.js",
     57  VENDOR_URI + "react-prop-types.js",
     58  VENDOR_URI + "react-test-renderer.js",
     59 ]);
     60 
     61 // Any directory that matches the following regular expression
     62 // is also considered as browser based module directory.
     63 // ('resource://devtools/client/.*/components/')
     64 //
     65 // An example:
     66 // * `resource://devtools/client/inspector/components`
     67 // * `resource://devtools/client/inspector/shared/components`
     68 const browserBasedDirsRegExp =
     69  /^resource\:\/\/devtools\/client\/\S*\/components\//;
     70 
     71 /**
     72 * @typedef {object} BrowserLoaderOptions
     73 * @property {string} baseURI
     74 *        Base path to load modules from. If null or undefined, only
     75 *        the shared vendor/components modules are loaded with the browser
     76 *        loader.
     77 * @property {Function} commonLibRequire
     78 *        Require function that should be used to load common libraries, like React.
     79 *        Allows for sharing common modules between tools, instead of loading a new
     80 *        instance into each tool. For example, pass "toolbox.browserRequire" here.
     81 * @property {boolean} useOnlyShared
     82 *        If true, ignores `baseURI` and only loads the shared
     83 *        BROWSER_BASED_DIRS via BrowserLoader.
     84 * @property {Window} window
     85 *        The window instance to evaluate modules within
     86 */
     87 
     88 /**
     89 * Create a loader to be used in a browser environment. This evaluates
     90 * modules in their own environment, but sets window (the normal
     91 * global object) as the sandbox prototype, so when a variable is not
     92 * defined it checks `window` before throwing an error. This makes all
     93 * browser APIs available to modules by default, like a normal browser
     94 * environment, but modules are still evaluated in their own scope.
     95 *
     96 * Another very important feature of this loader is that it *only*
     97 * deals with modules loaded from under `baseURI`. Anything loaded
     98 * outside of that path will still be loaded from the devtools loader,
     99 * so all system modules are still shared and cached across instances.
    100 * An exception to this is anything under
    101 * `devtools/client/shared/{vendor/components}`, which is where shared libraries
    102 * and React components live that should be evaluated in a browser environment.
    103 *
    104 * @param {BrowserLoaderOptions} options
    105 * @return Object
    106 *         An object with two properties:
    107 *         - loader: the Loader instance
    108 *         - require: a function to require modules with
    109 */
    110 export function BrowserLoader(options) {
    111  const browserLoaderBuilder = new BrowserLoaderBuilder(options);
    112  return {
    113    loader: browserLoaderBuilder.loader,
    114    require: browserLoaderBuilder.require,
    115    lazyRequireGetter: browserLoaderBuilder.lazyRequireGetter,
    116  };
    117 }
    118 
    119 /**
    120 * Private class used to build the Loader instance and require method returned
    121 * by BrowserLoader(baseURI, window).
    122 */
    123 class BrowserLoaderBuilder {
    124  /**
    125   * @param {BrowserLoaderOptions} options
    126   */
    127  constructor({ baseURI, commonLibRequire, useOnlyShared, window }) {
    128    assert(
    129      !!baseURI !== !!useOnlyShared,
    130      "Cannot use both `baseURI` and `useOnlyShared`."
    131    );
    132 
    133    const loaderOptions = devtoolsRequire("@loader/options");
    134 
    135    const opts = {
    136      sandboxPrototype: window,
    137      sandboxName: "DevTools (UI loader)",
    138      paths: loaderOptions.paths,
    139      // Make sure `define` function exists.  This allows defining some modules
    140      // in AMD format while retaining CommonJS compatibility through this hook.
    141      // JSON Viewer needs modules in AMD format, as it currently uses RequireJS
    142      // from a content document and can't access our usual loaders.  So, any
    143      // modules shared with the JSON Viewer should include a define wrapper:
    144      //
    145      //   // Make this available to both AMD and CJS environments
    146      //   define(function(require, exports, module) {
    147      //     ... code ...
    148      //   });
    149      //
    150      // Bug 1248830 will work out a better plan here for our content module
    151      // loading needs, especially as we head towards devtools.html.
    152      supportAMDModules: true,
    153      requireHook: (id, require) => {
    154        // If |id| requires special handling, simply defer to devtools
    155        // immediately.
    156        if (loader.isLoaderPluginId(id)) {
    157          return devtoolsRequire(id);
    158        }
    159 
    160        let uri = require.resolve(id);
    161 
    162        // The mocks can be set from tests using browser-loader-mocks.js setMockedModule().
    163        // If there is an entry for a given uri in the `mocks` object, return it instead of
    164        // requiring the module.
    165        if (flags.testing && lazy.getMockedModule(uri)) {
    166          return lazy.getMockedModule(uri);
    167        }
    168 
    169        // Load all React modules as ES Modules, in the Browser Loader global.
    170        // For this we have to ensure using ChromeUtils.importESModule with `global:"current"`,
    171        // but executed from the Loader global scope. `syncImport` does that.
    172        if (REACT_ESM_MODULES.has(uri)) {
    173          uri = uri.replace(/.js$/, ".mjs");
    174          const moduleExports = syncImport(uri);
    175          return moduleExports.default || moduleExports;
    176        }
    177        if (uri.endsWith(".mjs")) {
    178          const moduleExports = syncImport(uri);
    179          return moduleExports.default || moduleExports;
    180        }
    181 
    182        if (
    183          commonLibRequire &&
    184          COMMON_LIBRARY_DIRS.some(dir => uri.startsWith(dir))
    185        ) {
    186          return commonLibRequire(uri);
    187        }
    188 
    189        // Check if the URI matches one of hardcoded paths or a regexp.
    190        const isBrowserDir =
    191          BROWSER_BASED_DIRS.some(dir => uri.startsWith(dir)) ||
    192          uri.match(browserBasedDirsRegExp) != null;
    193 
    194        if ((useOnlyShared || !uri.startsWith(baseURI)) && !isBrowserDir) {
    195          return devtoolsRequire(uri);
    196        }
    197 
    198        return require(uri);
    199      },
    200      globals: {
    201        // Allow modules to use the window's console to ensure logs appear in a
    202        // tab toolbox, if one exists, instead of just the browser console.
    203        console: window.console,
    204        // Allow modules to use the DevToolsLoader lazy loading helpers.
    205        loader: {
    206          lazyGetter: loader.lazyGetter,
    207          lazyServiceGetter: loader.lazyServiceGetter,
    208          lazyRequireGetter: this.lazyRequireGetter.bind(this),
    209        },
    210      },
    211    };
    212 
    213    const mainModule = BaseLoader.Module(baseURI, joinURI(baseURI, "main.js"));
    214    this.loader = BaseLoader.Loader(opts);
    215 
    216    const scope = this.loader.sharedGlobal;
    217    Cu.evalInSandbox(
    218      "function __syncImport(uri) { return ChromeUtils.importESModule(uri, {global: 'current'})}",
    219      scope
    220    );
    221    const syncImport = scope.__syncImport;
    222 
    223    // When running tests, expose the BrowserLoader instance for metrics tests.
    224    if (flags.testing) {
    225      window.getBrowserLoaderForWindow = () => this;
    226    }
    227    this.require = BaseLoader.Require(this.loader, mainModule);
    228    this.lazyRequireGetter = this.lazyRequireGetter.bind(this);
    229  }
    230  /**
    231   * Define a getter property on the given object that requires the given
    232   * module. This enables delaying importing modules until the module is
    233   * actually used.
    234   *
    235   * Several getters can be defined at once by providing an array of
    236   * properties and enabling destructuring.
    237   *
    238   * @param {object} obj
    239   *    The object to define the property on.
    240   * @param {string | Array<string>} properties
    241   *    String: Name of the property for the getter.
    242   *    Array<String>: When destructure is true, properties can be an array of
    243   *    strings to create several getters at once.
    244   * @param {string} module
    245   *    The module path.
    246   * @param {boolean} destructure
    247   *    Pass true if the property name is a member of the module's exports.
    248   */
    249  lazyRequireGetter(obj, properties, module, destructure) {
    250    if (Array.isArray(properties) && !destructure) {
    251      throw new Error(
    252        "Pass destructure=true to call lazyRequireGetter with an array of properties"
    253      );
    254    }
    255 
    256    if (!Array.isArray(properties)) {
    257      properties = [properties];
    258    }
    259 
    260    for (const property of properties) {
    261      loader.lazyGetter(obj, property, () => {
    262        return destructure
    263          ? this.require(module)[property]
    264          : this.require(module || property);
    265      });
    266    }
    267  }
    268 }