tor-browser

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

SessionStoreBackupResource.sys.mjs (4998B)


      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 https://mozilla.org/MPL/2.0/. */
      4 
      5 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
      6 
      7 import {
      8  BackupResource,
      9  bytesToFuzzyKilobytes,
     10 } from "resource:///modules/backup/BackupResource.sys.mjs";
     11 
     12 const lazy = {};
     13 
     14 ChromeUtils.defineESModuleGetters(lazy, {
     15  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
     16  SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
     17  setTimeout: "resource://gre/modules/Timer.sys.mjs",
     18  TabStateFlusher: "resource:///modules/sessionstore/TabStateFlusher.sys.mjs",
     19 });
     20 
     21 ChromeUtils.defineLazyGetter(lazy, "logConsole", function () {
     22  return console.createInstance({
     23    prefix: "SessionStoreBackupResource",
     24    maxLogLevel: Services.prefs.getBoolPref("browser.backup.log", false)
     25      ? "Debug"
     26      : "Warn",
     27  });
     28 });
     29 
     30 XPCOMUtils.defineLazyPreferenceGetter(
     31  lazy,
     32  "TAB_FLUSH_TIMEOUT",
     33  "browser.backup.tab-flush-timeout",
     34  5000
     35 );
     36 
     37 /**
     38 * Class representing Session store related files within a user profile.
     39 */
     40 export class SessionStoreBackupResource extends BackupResource {
     41  // Allow creator to provide a "SessionStore" object, so we can use mocks in
     42  // testing.  Passing `null` means use the real service.
     43  constructor(sessionStore = null) {
     44    super();
     45    this._sessionStore = sessionStore;
     46  }
     47 
     48  static get key() {
     49    return "sessionstore";
     50  }
     51 
     52  static get requiresEncryption() {
     53    // Session store data does not require encryption, but if encryption is
     54    // disabled, then session cookies will be cleared from the backup before
     55    // writing it to the disk.
     56    return false;
     57  }
     58 
     59  get #sessionStore() {
     60    return this._sessionStore || lazy.SessionStore;
     61  }
     62 
     63  get filteredSessionStoreState() {
     64    let sessionStoreState = this.#sessionStore.getCurrentState(true);
     65    // Preserving session cookies in a backup used on a different machine
     66    // may break behavior for websites. So we leave them out of the backup.
     67    sessionStoreState.cookies = [];
     68 
     69    // Remove session storage.
     70    if (sessionStoreState.windows) {
     71      // We don't want to backup private windows
     72      sessionStoreState.windows = sessionStoreState.windows.filter(
     73        w => !w?.isPrivate
     74      );
     75      sessionStoreState.windows.forEach(win => {
     76        if (win.tabs) {
     77          win.tabs.forEach(tab => delete tab.storage);
     78        }
     79        if (win._closedTabs) {
     80          win._closedTabs.forEach(closedTab => delete closedTab.state.storage);
     81        }
     82      });
     83    }
     84    if (sessionStoreState.savedGroups) {
     85      sessionStoreState.savedGroups.forEach(group => {
     86        if (group.tabs) {
     87          group.tabs.forEach(tab => delete tab.state.storage);
     88        }
     89      });
     90    }
     91 
     92    return sessionStoreState;
     93  }
     94 
     95  async backup(
     96    stagingPath,
     97    profilePath = PathUtils.profileDir,
     98    _isEncrypting = false
     99  ) {
    100    // Flush tab state so backups receive the correct url to restore.
    101    await Promise.race([
    102      Promise.allSettled(
    103        lazy.BrowserWindowTracker.orderedWindows.map(
    104          lazy.TabStateFlusher.flushWindow
    105        )
    106      ),
    107      new Promise((_, reject) =>
    108        lazy.setTimeout(reject, lazy.TAB_FLUSH_TIMEOUT, { timeout: true })
    109      ),
    110    ]).catch(e => {
    111      if (e?.timeout) {
    112        lazy.logConsole.warn("Timed out waiting while flushing tab state.");
    113      } else {
    114        lazy.logConsole.error(
    115          "Unrecognized error while flushing tab state.",
    116          e
    117        );
    118      }
    119    });
    120 
    121    let sessionStorePath = PathUtils.join(stagingPath, "sessionstore.jsonlz4");
    122 
    123    await IOUtils.writeJSON(sessionStorePath, this.filteredSessionStoreState, {
    124      compress: true,
    125    });
    126    await BackupResource.copyFiles(profilePath, stagingPath, [
    127      "sessionstore-backups",
    128    ]);
    129 
    130    return null;
    131  }
    132 
    133  async recover(_manifestEntry, recoveryPath, destProfilePath) {
    134    await BackupResource.copyFiles(recoveryPath, destProfilePath, [
    135      "sessionstore.jsonlz4",
    136      "sessionstore-backups",
    137    ]);
    138 
    139    return null;
    140  }
    141 
    142  async measure(profilePath = PathUtils.profileDir) {
    143    // Get the current state of the session store JSON and
    144    // measure it's uncompressed size.
    145    let sessionStoreJson = this.#sessionStore.getCurrentState(true);
    146    let sessionStoreSize = new TextEncoder().encode(
    147      JSON.stringify(sessionStoreJson)
    148    ).byteLength;
    149    let sessionStoreNearestTenKb = bytesToFuzzyKilobytes(sessionStoreSize);
    150 
    151    Glean.browserBackup.sessionStoreSize.set(sessionStoreNearestTenKb);
    152 
    153    let sessionStoreBackupsDirectoryPath = PathUtils.join(
    154      profilePath,
    155      "sessionstore-backups"
    156    );
    157    let sessionStoreBackupsDirectorySize =
    158      await BackupResource.getDirectorySize(sessionStoreBackupsDirectoryPath);
    159 
    160    Glean.browserBackup.sessionStoreBackupsDirectorySize.set(
    161      sessionStoreBackupsDirectorySize
    162    );
    163  }
    164 }