tor-browser

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

cache.worker.js (6829B)


      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 /* global ReactDOMServer, NewtabRenderUtils */
      6 
      7 const PAGE_TEMPLATE_RESOURCE_PATH =
      8  "resource://newtab/data/content/abouthomecache/page.html.template";
      9 const SCRIPT_TEMPLATE_RESOURCE_PATH =
     10  "resource://newtab/data/content/abouthomecache/script.js.template";
     11 
     12 // If we don't stub these functions out, React throws warnings in the console
     13 // upon being loaded.
     14 let window = self;
     15 window.requestAnimationFrame = () => {};
     16 window.cancelAnimationFrame = () => {};
     17 
     18 /* import-globals-from /toolkit/components/workerloader/require.js */
     19 importScripts("resource://gre/modules/workers/require.js");
     20 
     21 {
     22  let oldChromeUtils = ChromeUtils;
     23 
     24  // ChromeUtils is defined inside of a Worker, but we don't want the
     25  // activity-stream.bundle.js to detect it when loading, since that results
     26  // in it attempting to import JSMs on load, which is not allowed in
     27  // a Worker. So we temporarily clear ChromeUtils so that activity-stream.bundle.js
     28  // thinks its being loaded in content scope.
     29  //
     30  // eslint-disable-next-line no-implicit-globals, no-global-assign
     31  ChromeUtils = undefined;
     32 
     33  /* import-globals-from ../../../../toolkit/content/vendor/react/react.js */
     34  /* import-globals-from ../../../../toolkit/content/vendor/react/react-dom.js */
     35  /* import-globals-from ../../../../toolkit/content/vendor/react/react-dom-server.js */
     36  /* import-globals-from ../../../../toolkit/content/vendor/react/redux.js */
     37  /* import-globals-from ../../../../toolkit/content/vendor/react/react-transition-group.js */
     38  /* import-globals-from ../../../../toolkit/content/vendor/react/prop-types.js */
     39  /* import-globals-from ../../../../toolkit/content/vendor/react/react-redux.js */
     40  /* import-globals-from ../data/content/activity-stream.bundle.js */
     41  importScripts(
     42    "chrome://global/content/vendor/react.js",
     43    "chrome://global/content/vendor/react-dom.js",
     44    "chrome://global/content/vendor/react-dom-server.js",
     45    "chrome://global/content/vendor/redux.js",
     46    "chrome://global/content/vendor/react-transition-group.js",
     47    "chrome://global/content/vendor/prop-types.js",
     48    "chrome://global/content/vendor/react-redux.js",
     49    "resource://newtab/data/content/activity-stream.bundle.js"
     50  );
     51 
     52  // eslint-disable-next-line no-global-assign, no-implicit-globals
     53  ChromeUtils = oldChromeUtils;
     54 }
     55 
     56 let PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js");
     57 
     58 let Agent = {
     59  _templates: null,
     60 
     61  /**
     62   * Synchronously loads the template files off of the file
     63   * system, and returns them as an object. If the Worker has loaded
     64   * these templates before, a cached copy of the templates is returned
     65   * instead.
     66   *
     67   * @return Object
     68   *   An object with the following properties:
     69   *
     70   *   pageTemplate (String):
     71   *     The template for the document markup.
     72   *
     73   *   scriptTempate (String):
     74   *     The template for the script.
     75   */
     76  getOrCreateTemplates() {
     77    if (this._templates) {
     78      return this._templates;
     79    }
     80 
     81    const templateResources = new Map([
     82      ["pageTemplate", PAGE_TEMPLATE_RESOURCE_PATH],
     83      ["scriptTemplate", SCRIPT_TEMPLATE_RESOURCE_PATH],
     84    ]);
     85 
     86    this._templates = {};
     87 
     88    for (let [templateName, path] of templateResources) {
     89      const xhr = new XMLHttpRequest();
     90      // Using a synchronous XHR in a worker is fine.
     91      xhr.open("GET", path, false);
     92      xhr.responseType = "text";
     93      xhr.send(null);
     94      this._templates[templateName] = xhr.responseText;
     95    }
     96 
     97    return this._templates;
     98  },
     99 
    100  /**
    101   * Constructs the cached about:home document using ReactDOMServer. This will
    102   * be called when "construct" messages are sent to this PromiseWorker.
    103   *
    104   * @param state (Object)
    105   *   The most recent Activity Stream Redux state.
    106   * @return Object
    107   *   An object with the following properties:
    108   *
    109   *   page (String):
    110   *     The generated markup for the document.
    111   *
    112   *   script (String):
    113   *     The generated script for the document.
    114   */
    115  construct(state) {
    116    // If anything in this function throws an exception, PromiseWorker
    117    // runs the risk of leaving the Promise associated with this method
    118    // forever unresolved. This is particularly bad when this method is
    119    // called via AsyncShutdown, since the forever unresolved Promise can
    120    // result in a AsyncShutdown timeout crash.
    121    //
    122    // To help ensure that no matter what, the Promise resolves with something,
    123    // we wrap the whole operation in a try/catch.
    124    try {
    125      return this._construct(state);
    126    } catch (e) {
    127      console.error("about:home startup cache construction failed:", e);
    128      return { page: null, script: null };
    129    }
    130  },
    131 
    132  /**
    133   * Internal method that actually does the work of constructing the cached
    134   * about:home document using ReactDOMServer. This should be called from
    135   * `construct` only.
    136   *
    137   * @param state (Object)
    138   *   The most recent Activity Stream Redux state.
    139   * @return Object
    140   *   An object with the following properties:
    141   *
    142   *   page (String):
    143   *     The generated markup for the document.
    144   *
    145   *   script (String):
    146   *     The generated script for the document.
    147   */
    148  _construct(state) {
    149    for (const key of Object.keys(state.App.isForStartupCache)) {
    150      state.App.isForStartupCache[key] = true;
    151    }
    152 
    153    // ReactDOMServer.renderToString expects a Redux store to pull
    154    // the state from, so we mock out a minimal store implementation.
    155    let fakeStore = {
    156      getState() {
    157        return state;
    158      },
    159      dispatch() {},
    160    };
    161 
    162    let markup = ReactDOMServer.renderToString(
    163      NewtabRenderUtils.NewTab({
    164        store: fakeStore,
    165        isFirstrun: false,
    166      })
    167    );
    168 
    169    let { pageTemplate, scriptTemplate } = this.getOrCreateTemplates();
    170    let cacheTime = new Date().toUTCString();
    171    let page = pageTemplate
    172      .replace("{{ MARKUP }}", markup)
    173      .replace("{{ CACHE_TIME }}", cacheTime);
    174    let script = scriptTemplate.replace(
    175      "{{ STATE }}",
    176      JSON.stringify(state, null, "\t")
    177    );
    178 
    179    return { page, script };
    180  },
    181 };
    182 
    183 // This boilerplate connects the PromiseWorker to the Agent so
    184 // that messages from the main thread map to methods on the
    185 // Agent.
    186 let worker = new PromiseWorker.AbstractWorker();
    187 worker.dispatch = function (method, args = []) {
    188  return Agent[method](...args);
    189 };
    190 worker.postMessage = function (result, ...transfers) {
    191  self.postMessage(result, ...transfers);
    192 };
    193 worker.close = function () {
    194  self.close();
    195 };
    196 
    197 self.addEventListener("message", msg => worker.handleMessage(msg));
    198 self.addEventListener("unhandledrejection", function (error) {
    199  throw error.reason;
    200 });