tor-browser

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

page_localStorage.js (5019B)


      1 /**
      2 * Helper page used by browser_localStorage_xxx.js.
      3 *
      4 * We expose methods to be invoked by SpecialPowers.spawn() calls.
      5 * SpecialPowers.spawn() uses the message manager and is PContent-based.  When
      6 * LocalStorage was PContent-managed, ordering was inherently ensured so we
      7 * could assume each page had already received all relevant events.  Now some
      8 * explicit type of coordination is required.
      9 *
     10 * This gets complicated because:
     11 * - LocalStorage is an ugly API that gives us almost unlimited implementation
     12 *   flexibility in the face of multiple processes.  It's also an API that sites
     13 *   may misuse which may encourage us to leverage that flexibility in the
     14 *   future to improve performance at the expense of propagation latency, and
     15 *   possibly involving content-observable coalescing of events.
     16 * - The Quantum DOM effort and its event labeling and separate task queues and
     17 *   green threading and current LocalStorage implementation mean that using
     18 *   other PBackground-based APIs such as BroadcastChannel may not provide
     19 *   reliable ordering guarantees.  Specifically, it's hard to guarantee that
     20 *   a BroadcastChannel postMessage() issued after a series of LocalStorage
     21 *   writes won't be received by the target window before the writes are
     22 *   perceived.  At least not without constraining the implementations of both
     23 *   APIs.
     24 * - Some of our tests explicitly want to verify LocalStorage behavior without
     25 *   having a "storage" listener, so we can't add a storage listener if the test
     26 *   didn't already want one.
     27 *
     28 * We use 2 approaches for coordination:
     29 * 1. If we're already listening for events, we listen for the sentinel value to
     30 *    be written.  This is efficient and appropriate in this case.
     31 * 2. If we're not listening for events, we use setTimeout(0) to poll the
     32 *    localStorage key and value until it changes to our expected value.
     33 *    setTimeout(0) eventually clamps to setTimeout(4), so in the event we are
     34 *    experiencing delays, we have reasonable, non-CPU-consuming back-off in
     35 *    place that leaves the CPU free to time out and fail our test if something
     36 *    broke.  This is ugly but makes us less brittle.
     37 *
     38 * Both of these involve mutateStorage writing the sentinel value at the end of
     39 * the batch.  All of our result-returning methods accordingly filter out the
     40 * sentinel key/value pair.
     41 */
     42 
     43 var pageName = document.location.search.substring(1);
     44 window.addEventListener("load", () => {
     45  document.getElementById("pageNameH").textContent = pageName;
     46 });
     47 
     48 // Key that conveys the end of a write batch.  Filtered out from state and
     49 // events.
     50 const SENTINEL_KEY = "WRITE_BATCH_SENTINEL";
     51 
     52 var storageEventsPromise = null;
     53 function listenForStorageEvents(sentinelValue) {
     54  const recordedEvents = [];
     55  storageEventsPromise = new Promise(function (resolve, reject) {
     56    window.addEventListener("storage", function thisHandler(event) {
     57      if (event.key === SENTINEL_KEY) {
     58        // There should be no way for this to have the wrong value, but reject
     59        // if it is wrong.
     60        if (event.newValue === sentinelValue) {
     61          window.removeEventListener("storage", thisHandler);
     62          resolve(recordedEvents);
     63        } else {
     64          reject(event.newValue);
     65        }
     66      } else {
     67        recordedEvents.push([event.key, event.newValue, event.oldValue]);
     68      }
     69    });
     70  });
     71 }
     72 
     73 function mutateStorage({ mutations, sentinelValue }) {
     74  mutations.forEach(function ([key, value]) {
     75    if (key !== null) {
     76      if (value === null) {
     77        localStorage.removeItem(key);
     78      } else {
     79        localStorage.setItem(key, value);
     80      }
     81    } else {
     82      localStorage.clear();
     83    }
     84  });
     85  localStorage.setItem(SENTINEL_KEY, sentinelValue);
     86 }
     87 
     88 // Returns a promise that is resolve when the sentinel key has taken on the
     89 // sentinel value.  Oddly structured to make sure promises don't let us
     90 // accidentally side-step the timeout clamping logic.
     91 function waitForSentinelValue(sentinelValue) {
     92  return new Promise(function (resolve) {
     93    function checkFunc() {
     94      if (localStorage.getItem(SENTINEL_KEY) === sentinelValue) {
     95        resolve();
     96      } else {
     97        // I believe linters will only yell at us if we use a non-zero constant.
     98        // Other forms of back-off were considered, including attempting to
     99        // issue a round-trip through PBackground, but that still potentially
    100        // runs afoul of labeling while also making us dependent on unrelated
    101        // APIs.
    102        setTimeout(checkFunc, 0);
    103      }
    104    }
    105    checkFunc();
    106  });
    107 }
    108 
    109 async function getStorageState(maybeSentinel) {
    110  if (maybeSentinel) {
    111    await waitForSentinelValue(maybeSentinel);
    112  }
    113 
    114  let numKeys = localStorage.length;
    115  let state = {};
    116  for (var iKey = 0; iKey < numKeys; iKey++) {
    117    let key = localStorage.key(iKey);
    118    if (key !== SENTINEL_KEY) {
    119      state[key] = localStorage.getItem(key);
    120    }
    121  }
    122  return state;
    123 }
    124 
    125 function returnAndClearStorageEvents() {
    126  return storageEventsPromise;
    127 }