tor-browser

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

SessionDataHelpers.sys.mjs (6990B)


      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 /**
      6 * Helper module alongside ParentProcessWatcherRegistry, which focus on updating the "sessionData" object.
      7 * This object is shared across processes and threads and have to be maintained in all these runtimes.
      8 */
      9 
     10 const lazy = {};
     11 ChromeUtils.defineESModuleGetters(
     12  lazy,
     13  {
     14    validateBreakpointLocation:
     15      "resource://devtools/shared/validate-breakpoint.sys.mjs",
     16  },
     17  { global: "contextual" }
     18 );
     19 
     20 ChromeUtils.defineLazyGetter(lazy, "validateEventBreakpoint", () => {
     21  const { loader } = ChromeUtils.importESModule(
     22    "resource://devtools/shared/loader/Loader.sys.mjs",
     23    { global: "contextual" }
     24  );
     25  return loader.require(
     26    "resource://devtools/server/actors/utils/event-breakpoints.js"
     27  ).validateEventBreakpoint;
     28 });
     29 
     30 // List of all arrays stored in `sessionData`, which are replicated across processes and threads
     31 const SUPPORTED_DATA = {
     32  BLACKBOXING: "blackboxing",
     33  BREAKPOINTS: "breakpoints",
     34  BROWSER_ELEMENT_HOST: "browser-element-host",
     35  XHR_BREAKPOINTS: "xhr-breakpoints",
     36  EVENT_BREAKPOINTS: "event-breakpoints",
     37  RESOURCES: "resources",
     38  TARGET_CONFIGURATION: "target-configuration",
     39  THREAD_CONFIGURATION: "thread-configuration",
     40  TARGETS: "targets",
     41 };
     42 
     43 // Optional function, if data isn't a primitive data type in order to produce a key
     44 // for the given data entry
     45 const DATA_KEY_FUNCTION = {
     46  [SUPPORTED_DATA.BLACKBOXING]({ url, range }) {
     47    return (
     48      url +
     49      (range
     50        ? `:${range.start.line}:${range.start.column}-${range.end.line}:${range.end.column}`
     51        : "")
     52    );
     53  },
     54  [SUPPORTED_DATA.BREAKPOINTS]({ location }) {
     55    lazy.validateBreakpointLocation(location);
     56    const { sourceUrl, sourceId, line, column } = location;
     57    return `${sourceUrl}:${sourceId}:${line}:${column}`;
     58  },
     59  [SUPPORTED_DATA.TARGET_CONFIGURATION]({ key }) {
     60    // Configuration data entries are { key, value } objects, `key` can be used
     61    // as the unique identifier for the entry.
     62    return key;
     63  },
     64  [SUPPORTED_DATA.THREAD_CONFIGURATION]({ key }) {
     65    // See target configuration comment
     66    return key;
     67  },
     68  [SUPPORTED_DATA.XHR_BREAKPOINTS]({ path, method }) {
     69    if (typeof path != "string") {
     70      throw new Error(
     71        `XHR Breakpoints expect to have path string, got ${typeof path} instead.`
     72      );
     73    }
     74    if (typeof method != "string") {
     75      throw new Error(
     76        `XHR Breakpoints expect to have method string, got ${typeof method} instead.`
     77      );
     78    }
     79    return `${path}:${method}`;
     80  },
     81  [SUPPORTED_DATA.EVENT_BREAKPOINTS](id) {
     82    if (typeof id != "string") {
     83      throw new Error(
     84        `Event Breakpoints expect the id to be a string , got ${typeof id} instead.`
     85      );
     86    }
     87    if (!lazy.validateEventBreakpoint(id)) {
     88      throw new Error(
     89        `The id string should be a valid event breakpoint id, ${id} is not.`
     90      );
     91    }
     92    return id;
     93  },
     94 };
     95 // Optional validation method to assert the shape of each session data entry
     96 const DATA_VALIDATION_FUNCTION = {
     97  [SUPPORTED_DATA.BREAKPOINTS]({ location }) {
     98    lazy.validateBreakpointLocation(location);
     99  },
    100  [SUPPORTED_DATA.XHR_BREAKPOINTS]({ path, method }) {
    101    if (typeof path != "string") {
    102      throw new Error(
    103        `XHR Breakpoints expect to have path string, got ${typeof path} instead.`
    104      );
    105    }
    106    if (typeof method != "string") {
    107      throw new Error(
    108        `XHR Breakpoints expect to have method string, got ${typeof method} instead.`
    109      );
    110    }
    111  },
    112  [SUPPORTED_DATA.EVENT_BREAKPOINTS](id) {
    113    if (typeof id != "string") {
    114      throw new Error(
    115        `Event Breakpoints expect the id to be a string , got ${typeof id} instead.`
    116      );
    117    }
    118    if (!lazy.validateEventBreakpoint(id)) {
    119      throw new Error(
    120        `The id string should be a valid event breakpoint id, ${id} is not.`
    121      );
    122    }
    123  },
    124 };
    125 
    126 function idFunction(v) {
    127  if (typeof v != "string") {
    128    throw new Error(
    129      `Expect data entry values to be string, or be using custom data key functions. Got ${typeof v} type instead.`
    130    );
    131  }
    132  return v;
    133 }
    134 
    135 export const SessionDataHelpers = {
    136  SUPPORTED_DATA,
    137 
    138  /**
    139   * Add new values to the shared "sessionData" object.
    140   *
    141   * @param Object sessionData
    142   *               The data object to update.
    143   * @param string type
    144   *               The type of data to be added
    145   * @param Array<Object> entries
    146   *               The values to be added to this type of data
    147   * @param String updateType
    148   *        "add" will only add the new entries in the existing data set.
    149   *        "set" will update the data set with the new entries.
    150   */
    151  addOrSetSessionDataEntry(sessionData, type, entries, updateType) {
    152    const validationFunction = DATA_VALIDATION_FUNCTION[type];
    153    if (validationFunction) {
    154      entries.forEach(validationFunction);
    155    }
    156 
    157    // When we are replacing the whole entries, things are significantly simplier
    158    if (updateType == "set") {
    159      sessionData[type] = entries;
    160      return;
    161    }
    162 
    163    if (!sessionData[type]) {
    164      sessionData[type] = [];
    165    }
    166    const toBeAdded = [];
    167    const keyFunction = DATA_KEY_FUNCTION[type] || idFunction;
    168    for (const entry of entries) {
    169      const existingIndex = sessionData[type].findIndex(existingEntry => {
    170        return keyFunction(existingEntry) === keyFunction(entry);
    171      });
    172      if (existingIndex === -1) {
    173        // New entry.
    174        toBeAdded.push(entry);
    175      } else {
    176        // Existing entry, update the value. This is relevant if the data-entry
    177        // is not a primitive data-type, and the value can change for the same
    178        // key.
    179        sessionData[type][existingIndex] = entry;
    180      }
    181    }
    182    sessionData[type].push(...toBeAdded);
    183  },
    184 
    185  /**
    186   * Remove values from the shared "sessionData" object.
    187   *
    188   * @param Object sessionData
    189   *               The data object to update.
    190   * @param string type
    191   *               The type of data to be remove
    192   * @param Array<Object> entries
    193   *               The values to be removed from this type of data
    194   * @return Boolean
    195   *               True, if at least one entries existed and has been removed.
    196   *               False, if none of the entries existed and none has been removed.
    197   */
    198  removeSessionDataEntry(sessionData, type, entries) {
    199    let includesAtLeastOne = false;
    200    const keyFunction = DATA_KEY_FUNCTION[type] || idFunction;
    201    for (const entry of entries) {
    202      const idx = sessionData[type]
    203        ? sessionData[type].findIndex(existingEntry => {
    204            return keyFunction(existingEntry) === keyFunction(entry);
    205          })
    206        : -1;
    207      if (idx !== -1) {
    208        sessionData[type].splice(idx, 1);
    209        includesAtLeastOne = true;
    210      }
    211    }
    212    if (!includesAtLeastOne) {
    213      return false;
    214    }
    215 
    216    return true;
    217  },
    218 };