tor-browser

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

TabState.sys.mjs (7773B)


      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 file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 const lazy = {};
      6 
      7 ChromeUtils.defineESModuleGetters(lazy, {
      8  PrivacyFilter: "resource://gre/modules/sessionstore/PrivacyFilter.sys.mjs",
      9  TabAttributes: "resource:///modules/sessionstore/TabAttributes.sys.mjs",
     10  TabStateCache: "resource:///modules/sessionstore/TabStateCache.sys.mjs",
     11  sessionStoreLogger: "resource:///modules/sessionstore/SessionLogger.sys.mjs",
     12 });
     13 
     14 /**
     15 * Module that contains tab state collection methods.
     16 */
     17 class _TabState {
     18  /**
     19   * Processes a data update sent by the content script.
     20   */
     21  update(permanentKey, { data }) {
     22    lazy.TabStateCache.update(permanentKey, data);
     23  }
     24 
     25  /**
     26   * Collect data related to a single tab, synchronously.
     27   *
     28   * @param tab
     29   *        tabbrowser tab
     30   * @param [extData]
     31   *        optional dictionary object, containing custom tab values.
     32   *
     33   * @returns {TabStateData} An object with the data for this tab.  If the
     34   * tab has not been invalidated since the last call to
     35   * collect(aTab), the same object is returned.
     36   */
     37  collect(tab, extData) {
     38    return this.#collectBaseTabData(tab, { extData });
     39  }
     40 
     41  /**
     42   * Collect data related to a single tab, including private data.
     43   * Use with caution.
     44   *
     45   * @param tab
     46   *        tabbrowser tab
     47   * @param [extData]
     48   *        optional dictionary object, containing custom tab values.
     49   *
     50   * @returns {object} An object with the data for this tab. This data is never
     51   *                   cached, it will always be read from the tab and thus be
     52   *                   up-to-date.
     53   */
     54  clone(tab, extData) {
     55    return this.#collectBaseTabData(tab, { extData, includePrivateData: true });
     56  }
     57 
     58  /**
     59   * Collects basic tab data for a given tab.
     60   *
     61   * @param tab
     62   *        tabbrowser tab
     63   * @param options (object)
     64   *        {extData: object} optional dictionary object, containing custom tab values
     65   *        {includePrivateData: true} to always include private data
     66   *
     67   * @returns {TabStateData} An object with the basic data for this tab.
     68   */
     69  #collectBaseTabData(tab, options) {
     70    let tabData = { entries: [], lastAccessed: tab.lastAccessed };
     71    let browser = tab.linkedBrowser;
     72 
     73    if (tab.pinned) {
     74      tabData.pinned = true;
     75    }
     76 
     77    tabData.hidden = tab.hidden;
     78 
     79    if (browser.audioMuted) {
     80      tabData.muted = true;
     81      tabData.muteReason = tab.muteReason;
     82    }
     83 
     84    if (tab.group) {
     85      tabData.groupId = tab.group.id;
     86    }
     87 
     88    tabData.searchMode = tab.ownerGlobal.gURLBar.getSearchMode(browser, true);
     89 
     90    tabData.userContextId = tab.userContextId || 0;
     91 
     92    // Save tab attributes.
     93    tabData.attributes = lazy.TabAttributes.get(tab);
     94 
     95    if (options.extData) {
     96      tabData.extData = options.extData;
     97    }
     98 
     99    // Copy data from the tab state cache only if the tab has fully finished
    100    // restoring. We don't want to overwrite data contained in __SS_data.
    101    this.copyFromCache(browser.permanentKey, tabData, options);
    102 
    103    // After copyFromCache() was called we check for properties that are kept
    104    // in the cache only while the tab is pending or restoring. Once that
    105    // happened those properties will be removed from the cache and will
    106    // be read from the tab/browser every time we collect data.
    107 
    108    // Store the tab icon.
    109    if (!("image" in tabData)) {
    110      let tabbrowser = tab.ownerGlobal.gBrowser;
    111      tabData.image = tabbrowser.getIcon(tab);
    112    }
    113 
    114    // If there is a userTypedValue set, then either the user has typed something
    115    // in the URL bar, or a new tab was opened with a URI to load.
    116    // If so, we also track whether we were still in the process of loading something.
    117    if (!("userTypedValue" in tabData) && browser.userTypedValue) {
    118      tabData.userTypedValue = browser.userTypedValue;
    119      // We always used to keep track of the loading state as an integer, where
    120      // '0' indicated the user had typed since the last load (or no load was
    121      // ongoing), and any positive value indicated we had started a load since
    122      // the last time the user typed in the URL bar. Mimic this to keep the
    123      // session store representation in sync, even though we now represent this
    124      // more explicitly:
    125      tabData.userTypedClear = browser.didStartLoadSinceLastUserTyping()
    126        ? 1
    127        : 0;
    128    }
    129 
    130    return tabData;
    131  }
    132 
    133  processAboutRestartrequiredEnties(aEntries) {
    134    // Find if there are some entries that matches (contains) the
    135    // about:restartrequired page. It can be plain about:restartrequired
    136    // or something more complicated like about:restartrequired?e=restartrequired&u=about%3Ablank&c=UTF-8&d=%20
    137    if (
    138      !aEntries.some(e => e.url && e.url.startsWith("about:restartrequired"))
    139    ) {
    140      return aEntries;
    141    }
    142 
    143    // now we need a deep copy
    144    let newEntries = structuredClone(aEntries);
    145    newEntries.forEach((item, index, object) => {
    146      if (item.url === "about:restartrequired") {
    147        object.splice(index, 1);
    148      } else if (item.url.startsWith("about:restartrequired")) {
    149        try {
    150          const parsedURL = new URL(item.url);
    151          if (parsedURL && parsedURL.searchParams.has("u")) {
    152            const previousURL = parsedURL.searchParams.get("u");
    153            object[index].url = previousURL;
    154          }
    155        } catch (ex) {
    156          lazy.sessionStoreLogger.error(
    157            `Exception when parsing "${item.url}"`,
    158            ex
    159          );
    160        }
    161      }
    162    });
    163 
    164    return newEntries;
    165  }
    166 
    167  /**
    168   * Copy data for the given |browser| from the cache to |tabData|.
    169   *
    170   * @param permanentKey (object)
    171   *        The browser belonging to the given |tabData| object.
    172   * @param tabData (object)
    173   *        The tab data belonging to the given |tab|.
    174   * @param options (object)
    175   *        {includePrivateData: true} to always include private data
    176   */
    177  copyFromCache(permanentKey, tabData, options = {}) {
    178    let data = lazy.TabStateCache.get(permanentKey);
    179    if (!data) {
    180      return;
    181    }
    182 
    183    // The caller may explicitly request to omit privacy checks.
    184    let includePrivateData = options && options.includePrivateData;
    185 
    186    for (let key of Object.keys(data)) {
    187      let value = data[key];
    188 
    189      // Filter sensitive data according to the current privacy level.
    190      if (!includePrivateData) {
    191        if (key === "storage") {
    192          value = lazy.PrivacyFilter.filterSessionStorageData(value);
    193        } else if (key === "formdata") {
    194          value = lazy.PrivacyFilter.filterFormData(value);
    195        }
    196      }
    197 
    198      if (key === "history") {
    199        // Make a shallow copy of the entries array. We (currently) don't update
    200        // entries in place, so we don't have to worry about performing a deep
    201        // copy.
    202        tabData.entries = [...value.entries];
    203 
    204        if (value.hasOwnProperty("index")) {
    205          tabData.index = value.index;
    206        }
    207 
    208        if (value.hasOwnProperty("requestedIndex")) {
    209          tabData.requestedIndex = value.requestedIndex;
    210        }
    211 
    212        tabData.entries = this.processAboutRestartrequiredEnties(value.entries);
    213      } else if (!value && (key == "scroll" || key == "formdata")) {
    214        // [Bug 1554512]
    215 
    216        // If scroll or formdata null it indicates that the update to
    217        // be performed is to remove them, and not copy a null
    218        // value. Scroll will be null when the position is at the top
    219        // of the document, formdata will be null when there is only
    220        // default data.
    221        delete tabData[key];
    222      } else {
    223        tabData[key] = value;
    224      }
    225    }
    226  }
    227 }
    228 
    229 export const TabState = new _TabState();