tor-browser

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

Sanitizer.sys.mjs (50104B)


      1 // -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
      7 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
      8 
      9 const lazy = {};
     10 
     11 ChromeUtils.defineESModuleGetters(lazy, {
     12  ContextualIdentityService:
     13    "resource://gre/modules/ContextualIdentityService.sys.mjs",
     14  FormHistory: "resource://gre/modules/FormHistory.sys.mjs",
     15  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
     16  PrincipalsCollector: "resource://gre/modules/PrincipalsCollector.sys.mjs",
     17 });
     18 
     19 XPCOMUtils.defineLazyPreferenceGetter(
     20  lazy,
     21  "useOldClearHistoryDialog",
     22  "privacy.sanitize.useOldClearHistoryDialog",
     23  false
     24 );
     25 
     26 var logConsole;
     27 function log(...msgs) {
     28  if (!logConsole) {
     29    logConsole = console.createInstance({
     30      prefix: "Sanitizer",
     31      maxLogLevelPref: "browser.sanitizer.loglevel",
     32    });
     33  }
     34 
     35  logConsole.log(...msgs);
     36 }
     37 
     38 // Used as unique id for pending sanitizations.
     39 var gPendingSanitizationSerial = 0;
     40 
     41 var gPrincipalsCollector = null;
     42 
     43 export var Sanitizer = {
     44  /**
     45   * Whether we should sanitize on shutdown.
     46   */
     47  PREF_SANITIZE_ON_SHUTDOWN: "privacy.sanitize.sanitizeOnShutdown",
     48 
     49  /**
     50   * During a sanitization this is set to a JSON containing an array of the
     51   * pending sanitizations. This allows to retry sanitizations on startup in
     52   * case they dind't run or were interrupted by a crash.
     53   * Use addPendingSanitization and removePendingSanitization to manage it.
     54   */
     55  PREF_PENDING_SANITIZATIONS: "privacy.sanitize.pending",
     56 
     57  /**
     58   * Pref branches to fetch sanitization options from.
     59   */
     60  PREF_CPD_BRANCH: "privacy.cpd.",
     61  /*
     62   * We need to choose between two branches for shutdown since there are separate prefs for the new
     63   * clear history dialog
     64   */
     65  get PREF_SHUTDOWN_BRANCH() {
     66    return lazy.useOldClearHistoryDialog
     67      ? "privacy.clearOnShutdown."
     68      : "privacy.clearOnShutdown_v2.";
     69  },
     70 
     71  /**
     72   * The fallback timestamp used when no argument is given to
     73   * Sanitizer.getClearRange.
     74   */
     75  PREF_TIMESPAN: "privacy.sanitize.timeSpan",
     76 
     77  /**
     78   * Pref to newTab segregation. If true, on shutdown, the private container
     79   * used in about:newtab is cleaned up.  Exposed because used in tests.
     80   */
     81  PREF_NEWTAB_SEGREGATION:
     82    "privacy.usercontext.about_newtab_segregation.enabled",
     83 
     84  /**
     85   * Time span constants corresponding to values of the privacy.sanitize.timeSpan
     86   * pref.  Used to determine how much history to clear, for various items
     87   */
     88  TIMESPAN_EVERYTHING: 0,
     89  TIMESPAN_HOUR: 1,
     90  TIMESPAN_2HOURS: 2,
     91  TIMESPAN_4HOURS: 3,
     92  TIMESPAN_TODAY: 4,
     93  TIMESPAN_5MIN: 5,
     94  TIMESPAN_24HOURS: 6,
     95 
     96  /**
     97   * Mapping time span constants to get total time in ms from the selected
     98   * time spans
     99   */
    100  timeSpanMsMap: {
    101    TIMESPAN_5MIN: 300000, // 5*60*1000
    102    TIMESPAN_HOUR: 3600000, // 60*60*1000
    103    TIMESPAN_2HOURS: 7200000, // 2*60*60*1000
    104    TIMESPAN_4HOURS: 14400000, // 4*60*60*1000
    105    TIMESPAN_24HOURS: 86400000, // 24*60*60*1000
    106    get TIMESPAN_TODAY() {
    107      return Date.now() - new Date().setHours(0, 0, 0, 0);
    108    }, // time spent today
    109  },
    110 
    111  /**
    112   * Whether we should sanitize on shutdown.
    113   * When this is set, a pending sanitization should also be added and removed
    114   * when shutdown sanitization is complete. This allows to retry incomplete
    115   * sanitizations on startup.
    116   */
    117  shouldSanitizeOnShutdown: false,
    118 
    119  /**
    120   * Whether we should sanitize the private container for about:newtab.
    121   */
    122  shouldSanitizeNewTabContainer: false,
    123 
    124  /**
    125   * Shows a sanitization dialog to the user. Returns after the dialog box has
    126   * closed.
    127   *
    128   * @param parentWindow the browser window to use as parent for the created
    129   *        dialog.
    130   * @param {string} mode - flag to let the dialog know if it is opened
    131   *        using the clear on shutdown (clearOnShutdown) settings option
    132   *        in about:preferences or in a clear site data context (clearSiteData)
    133   *
    134   * @throws if parentWindow is undefined or doesn't have a gDialogBox.
    135   */
    136  showUI(parentWindow, mode) {
    137    // Treat the hidden window as not being a parent window:
    138    if (
    139      parentWindow?.document.documentURI ==
    140      "chrome://browser/content/hiddenWindowMac.xhtml"
    141    ) {
    142      parentWindow = null;
    143    }
    144 
    145    let dialogFile = lazy.useOldClearHistoryDialog
    146      ? "sanitize.xhtml"
    147      : "sanitize_v2.xhtml";
    148 
    149    if (parentWindow?.gDialogBox) {
    150      parentWindow.gDialogBox.open(`chrome://browser/content/${dialogFile}`, {
    151        inBrowserWindow: true,
    152        mode,
    153      });
    154    } else {
    155      Services.ww.openWindow(
    156        parentWindow,
    157        `chrome://browser/content/${dialogFile}`,
    158        "Sanitize",
    159        "chrome,titlebar,dialog,centerscreen,modal",
    160        { needNativeUI: true, mode }
    161      );
    162    }
    163  },
    164 
    165  /**
    166   * Performs startup tasks:
    167   *  - Checks if sanitizations were not completed during the last session.
    168   *  - Registers sanitize-on-shutdown.
    169   */
    170  async onStartup() {
    171    // First, collect pending sanitizations from the last session, before we
    172    // add pending sanitizations for this session.
    173    let pendingSanitizations = getAndClearPendingSanitizations();
    174    log("Pending sanitizations:", pendingSanitizations);
    175 
    176    // Check if we should sanitize on shutdown.
    177    this.shouldSanitizeOnShutdown = Services.prefs.getBoolPref(
    178      Sanitizer.PREF_SANITIZE_ON_SHUTDOWN,
    179      false
    180    );
    181    Services.prefs.addObserver(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN, this, true);
    182    // Add a pending shutdown sanitization, if necessary.
    183    if (this.shouldSanitizeOnShutdown) {
    184      let itemsToClear = getItemsToClearFromPrefBranch(
    185        Sanitizer.PREF_SHUTDOWN_BRANCH
    186      );
    187      addPendingSanitization("shutdown", itemsToClear, {});
    188    }
    189    // Shutdown sanitization is always pending, but the user may change the
    190    // sanitize on shutdown prefs during the session. Then the pending
    191    // sanitization would become stale and must be updated.
    192    Services.prefs.addObserver(Sanitizer.PREF_SHUTDOWN_BRANCH, this, true);
    193 
    194    // Make sure that we are triggered during shutdown.
    195    let shutdownClient = lazy.PlacesUtils.history.shutdownClient.jsclient;
    196    // We need to pass to sanitize() (through sanitizeOnShutdown) a state object
    197    // that tracks the status of the shutdown blocker. This `progress` object
    198    // will be updated during sanitization and reported with the crash in case of
    199    // a shutdown timeout.
    200    // We use the `options` argument to pass the `progress` object to sanitize().
    201    let progress = { isShutdown: true, clearHonoringExceptions: true };
    202    shutdownClient.addBlocker(
    203      "sanitize.js: Sanitize on shutdown",
    204      () => sanitizeOnShutdown(progress),
    205      { fetchState: () => ({ progress }) }
    206    );
    207 
    208    this.shouldSanitizeNewTabContainer = Services.prefs.getBoolPref(
    209      this.PREF_NEWTAB_SEGREGATION,
    210      false
    211    );
    212    if (this.shouldSanitizeNewTabContainer) {
    213      addPendingSanitization("newtab-container", [], {});
    214    }
    215 
    216    let i = pendingSanitizations.findIndex(s => s.id == "newtab-container");
    217    if (i != -1) {
    218      pendingSanitizations.splice(i, 1);
    219      sanitizeNewTabSegregation();
    220    }
    221 
    222    // Finally, run the sanitizations that were left pending, because we crashed
    223    // before completing them.
    224    for (let { itemsToClear, options } of pendingSanitizations) {
    225      try {
    226        // We need to set this flag to watch out for the users exceptions like we do on shutdown
    227        options.progress = { clearHonoringExceptions: true };
    228        await this.sanitize(itemsToClear, options);
    229      } catch (ex) {
    230        console.error(
    231          "A previously pending sanitization failed: ",
    232          itemsToClear,
    233          ex
    234        );
    235      }
    236    }
    237    await cleanupAfterSanitization(Ci.nsIClearDataService.CLEAR_ALL);
    238  },
    239 
    240  /**
    241   * Returns a 2 element array representing the start and end times,
    242   * in the uSec-since-epoch format that PRTime likes. If we should
    243   * clear everything, this function returns null.
    244   *
    245   * @param ts [optional] a timespan to convert to start and end time.
    246   *                      Falls back to the privacy.sanitize.timeSpan preference
    247   *                      if this argument is omitted.
    248   *                      If this argument is provided, it has to be one of the
    249   *                      Sanitizer.TIMESPAN_* constants. This function will
    250   *                      throw an error otherwise.
    251   *
    252   * @return {Array} a 2-element Array containing the start and end times.
    253   */
    254  getClearRange(ts) {
    255    if (ts === undefined) {
    256      ts = Services.prefs.getIntPref(Sanitizer.PREF_TIMESPAN);
    257    }
    258    if (ts === Sanitizer.TIMESPAN_EVERYTHING) {
    259      return null;
    260    }
    261 
    262    // PRTime is microseconds while JS time is milliseconds
    263    var endDate = Date.now() * 1000;
    264    switch (ts) {
    265      case Sanitizer.TIMESPAN_5MIN:
    266        var startDate = endDate - 300000000; // 5*60*1000000
    267        break;
    268      case Sanitizer.TIMESPAN_HOUR:
    269        startDate = endDate - 3600000000; // 1*60*60*1000000
    270        break;
    271      case Sanitizer.TIMESPAN_2HOURS:
    272        startDate = endDate - 7200000000; // 2*60*60*1000000
    273        break;
    274      case Sanitizer.TIMESPAN_4HOURS:
    275        startDate = endDate - 14400000000; // 4*60*60*1000000
    276        break;
    277      case Sanitizer.TIMESPAN_TODAY:
    278        var d = new Date(); // Start with today
    279        d.setHours(0); // zero us back to midnight...
    280        d.setMinutes(0);
    281        d.setSeconds(0);
    282        d.setMilliseconds(0);
    283        startDate = d.valueOf() * 1000; // convert to epoch usec
    284        break;
    285      case Sanitizer.TIMESPAN_24HOURS:
    286        startDate = endDate - 86400000000; // 24*60*60*1000000
    287        break;
    288      default:
    289        throw new Error("Invalid time span for clear private data: " + ts);
    290    }
    291    return [startDate, endDate];
    292  },
    293 
    294  /**
    295   * Deletes privacy sensitive data in a batch, according to user preferences.
    296   * Returns a promise which is resolved if no errors occurred.  If an error
    297   * occurs, a message is reported to the console and all other items are still
    298   * cleared before the promise is finally rejected.
    299   *
    300   * @param [optional] itemsToClear
    301   *        Array of items to be cleared. if specified only those
    302   *        items get cleared, irrespectively of the preference settings.
    303   * @param [optional] options
    304   *        Object whose properties are options for this sanitization:
    305   *         - ignoreTimespan (default: true): Time span only makes sense in
    306   *           certain cases.  Consumers who want to only clear some private
    307   *           data can opt in by setting this to false, and can optionally
    308   *           specify a specific range.
    309   *           If timespan is not ignored, and range is not set, sanitize() will
    310   *           use the value of the timespan pref to determine a range.
    311   *         - range (default: null): array-tuple of [from, to] timestamps
    312   *         - privateStateForNewWindow (default: "non-private"): when clearing
    313   *           open windows, defines the private state for the newly opened window.
    314   * @returns {object} An object containing debug information about the
    315   *          sanitization progress. This state object is also used as
    316   *          AsyncShutdown metadata.
    317   */
    318  async sanitize(itemsToClear = null, options = {}) {
    319    let progress = options.progress;
    320    // initialise the principals collector
    321    gPrincipalsCollector = new lazy.PrincipalsCollector();
    322    if (!progress) {
    323      progress = options.progress = {};
    324    }
    325 
    326    if (!itemsToClear) {
    327      itemsToClear = getItemsToClearFromPrefBranch(this.PREF_CPD_BRANCH);
    328    }
    329    let promise = sanitizeInternal(this.items, itemsToClear, options);
    330 
    331    // Depending on preferences, the sanitizer may perform asynchronous
    332    // work before it starts cleaning up the Places database (e.g. closing
    333    // windows). We need to make sure that the connection to that database
    334    // hasn't been closed by the time we use it.
    335    // Though, if this is a sanitize on shutdown, we already have a blocker.
    336    if (!progress.isShutdown) {
    337      let shutdownClient = lazy.PlacesUtils.history.shutdownClient.jsclient;
    338      shutdownClient.addBlocker("sanitize.js: Sanitize", promise, {
    339        fetchState: () => ({ progress }),
    340      });
    341    }
    342 
    343    try {
    344      await promise;
    345    } finally {
    346      Services.obs.notifyObservers(null, "sanitizer-sanitization-complete");
    347    }
    348    return progress;
    349  },
    350 
    351  observe(subject, topic, data) {
    352    if (topic == "nsPref:changed") {
    353      if (
    354        data.startsWith(this.PREF_SHUTDOWN_BRANCH) &&
    355        this.shouldSanitizeOnShutdown
    356      ) {
    357        // Update the pending shutdown sanitization.
    358        removePendingSanitization("shutdown");
    359        let itemsToClear = getItemsToClearFromPrefBranch(
    360          Sanitizer.PREF_SHUTDOWN_BRANCH
    361        );
    362        addPendingSanitization("shutdown", itemsToClear, {});
    363      } else if (data == this.PREF_SANITIZE_ON_SHUTDOWN) {
    364        this.shouldSanitizeOnShutdown = Services.prefs.getBoolPref(
    365          Sanitizer.PREF_SANITIZE_ON_SHUTDOWN,
    366          false
    367        );
    368        removePendingSanitization("shutdown");
    369        if (this.shouldSanitizeOnShutdown) {
    370          let itemsToClear = getItemsToClearFromPrefBranch(
    371            Sanitizer.PREF_SHUTDOWN_BRANCH
    372          );
    373          addPendingSanitization("shutdown", itemsToClear, {});
    374        }
    375      } else if (data == this.PREF_NEWTAB_SEGREGATION) {
    376        this.shouldSanitizeNewTabContainer = Services.prefs.getBoolPref(
    377          this.PREF_NEWTAB_SEGREGATION,
    378          false
    379        );
    380        removePendingSanitization("newtab-container");
    381        if (this.shouldSanitizeNewTabContainer) {
    382          addPendingSanitization("newtab-container", [], {});
    383        }
    384      }
    385    }
    386  },
    387 
    388  QueryInterface: ChromeUtils.generateQI([
    389    "nsIObserver",
    390    "nsISupportsWeakReference",
    391  ]),
    392 
    393  // This method is meant to be used by tests.
    394  async runSanitizeOnShutdown() {
    395    // The collector needs to be reset for each test, as the collection only happens
    396    // once and does not update after that.
    397    // Pretend that it has never been initialized to mimic the actual browser behavior
    398    // by setting it to null.
    399    // The actually initialization will happen either via sanitize() or directly in
    400    // sanitizeOnShutdown.
    401    gPrincipalsCollector = null;
    402    return sanitizeOnShutdown({
    403      isShutdown: true,
    404      clearHonoringExceptions: true,
    405    });
    406  },
    407 
    408  /**
    409   * Migrate old sanitize prefs to the new prefs for the new
    410   * clear history dialog. Does nothing if the migration was completed before
    411   * based on the pref privacy.sanitize.cpd.hasMigratedToNewPrefs3 or
    412   * privacy.sanitize.clearOnShutdown.hasMigratedToNewPrefs3
    413   *
    414   * @param {string} context - one of "clearOnShutdown" or "cpd", which indicates which
    415   *      pref branch to migrate prefs from based on the dialog context
    416   */
    417  maybeMigratePrefs(context) {
    418    // We are going to be migrating once more due to a backout in Bug 1894933
    419    // The new migration prefs have a 2 appended to the context
    420    if (
    421      Services.prefs.getBoolPref(
    422        `privacy.sanitize.${context}.hasMigratedToNewPrefs3`
    423      )
    424    ) {
    425      return;
    426    }
    427 
    428    let newContext =
    429      context == "clearOnShutdown" ? "clearOnShutdown_v2" : "clearHistory";
    430 
    431    let formData = Services.prefs.getBoolPref(`privacy.${context}.formdata`);
    432    // Bug 1888466 lead to splitting the clearhistory v2 history, formdata and downloads pref into history and formdata
    433    // so we have to now check for both the old pref and the new pref
    434    let history = Services.prefs.getBoolPref(`privacy.${context}.history`);
    435    // if the user has migrated to using historyFormDataAndDownloads, then we should follow what
    436    // the new dialog prefs are set to
    437    if (
    438      Services.prefs.getBoolPref(
    439        `privacy.sanitize.${context}.hasMigratedToNewPrefs2`
    440      )
    441    ) {
    442      let formDataContext =
    443        context == "cpd" ? "clearHistory" : "clearOnShutdown_v2";
    444      history = Services.prefs.getBoolPref(
    445        `privacy.${formDataContext}.historyFormDataAndDownloads`
    446      );
    447      formData = history;
    448    } else {
    449      // migrate from v1 (old dialog) to v3 (latest version of new dialog)
    450      // hasMigratedToNewPrefs3 == false and hasMigratedToNewPrefs2 == false
    451 
    452      // Get all the old pref values to migrate to the new ones
    453      let cookies = Services.prefs.getBoolPref(`privacy.${context}.cookies`);
    454      let cache = Services.prefs.getBoolPref(`privacy.${context}.cache`);
    455      let siteSettings = Services.prefs.getBoolPref(
    456        `privacy.${context}.siteSettings`
    457      );
    458 
    459      // We set cookiesAndStorage to true if cookies are enabled for clearing
    460      // regardless of what sessions and offlineApps are set to
    461      // This is because cookie clearing behaviour takes precedence over sessions and offlineApps clearing.
    462      Services.prefs.setBoolPref(
    463        `privacy.${newContext}.cookiesAndStorage`,
    464        cookies
    465      );
    466 
    467      // cache, siteSettings and formdata follow the old dialog prefs
    468      Services.prefs.setBoolPref(`privacy.${newContext}.cache`, cache);
    469 
    470      Services.prefs.setBoolPref(
    471        `privacy.${newContext}.siteSettings`,
    472        siteSettings
    473      );
    474    }
    475 
    476    // we set browsingHistoryAndDownloads to true if history is enabled for clearing, regardless of what downloads is set to.
    477    Services.prefs.setBoolPref(
    478      `privacy.${newContext}.browsingHistoryAndDownloads`,
    479      history
    480    );
    481 
    482    Services.prefs.setBoolPref(`privacy.${newContext}.formdata`, formData);
    483 
    484    // We have to remove the old privacy.sanitize.${context}.hasMigratedToNewPrefs (2) pref
    485    // if the user has them on their system
    486    Services.prefs.clearUserPref(
    487      `privacy.sanitize.${context}.hasMigratedToNewPrefs`
    488    );
    489    Services.prefs.clearUserPref(
    490      `privacy.sanitize.${context}.hasMigratedToNewPrefs2`
    491    );
    492 
    493    Services.prefs.setBoolPref(
    494      `privacy.sanitize.${context}.hasMigratedToNewPrefs3`,
    495      true
    496    );
    497  },
    498 
    499  // When making any changes to the sanitize implementations here,
    500  // please check whether the changes are applicable to Android
    501  // (mobile/shared/modules/geckoview/GeckoViewStorageController.sys.mjs) as well.
    502 
    503  items: {
    504    cache: {
    505      async clear(range) {
    506        let timerId = Glean.browserSanitizer.cache.start();
    507        await clearData(range, Ci.nsIClearDataService.CLEAR_ALL_CACHES);
    508        Glean.browserSanitizer.cache.stopAndAccumulate(timerId);
    509      },
    510    },
    511 
    512    cookies: {
    513      async clear(range, { progress }, clearHonoringExceptions) {
    514        let timerId = Glean.browserSanitizer.cookies.start();
    515        // This is true if called by sanitizeOnShutdown.
    516        // On shutdown we clear by principal to be able to honor the users exceptions
    517        if (clearHonoringExceptions) {
    518          progress.step = "getAllPrincipals";
    519          let principalsForShutdownClearing =
    520            await gPrincipalsCollector.getAllPrincipals(progress);
    521          await maybeSanitizeSessionPrincipals(
    522            progress,
    523            principalsForShutdownClearing,
    524            Ci.nsIClearDataService.CLEAR_COOKIES |
    525              Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD |
    526              Ci.nsIClearDataService.CLEAR_FINGERPRINTING_PROTECTION_STATE |
    527              Ci.nsIClearDataService.CLEAR_BOUNCE_TRACKING_PROTECTION_STATE
    528          );
    529        } else {
    530          // Not on shutdown
    531          await clearData(
    532            range,
    533            Ci.nsIClearDataService.CLEAR_COOKIES |
    534              Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD |
    535              Ci.nsIClearDataService.CLEAR_FINGERPRINTING_PROTECTION_STATE |
    536              Ci.nsIClearDataService.CLEAR_BOUNCE_TRACKING_PROTECTION_STATE
    537          );
    538        }
    539        await clearData(range, Ci.nsIClearDataService.CLEAR_MEDIA_DEVICES);
    540        Glean.browserSanitizer.cookies.stopAndAccumulate(timerId);
    541      },
    542    },
    543 
    544    offlineApps: {
    545      async clear(range, { progress }, clearHonoringExceptions) {
    546        // This is true if called by sanitizeOnShutdown.
    547        // On shutdown we clear by principal to be able to honor the users exceptions
    548        if (clearHonoringExceptions) {
    549          progress.step = "getAllPrincipals";
    550          let principalsForShutdownClearing =
    551            await gPrincipalsCollector.getAllPrincipals(progress);
    552          await maybeSanitizeSessionPrincipals(
    553            progress,
    554            principalsForShutdownClearing,
    555            Ci.nsIClearDataService.CLEAR_DOM_STORAGES |
    556              Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD |
    557              Ci.nsIClearDataService.CLEAR_FINGERPRINTING_PROTECTION_STATE
    558          );
    559        } else {
    560          // Not on shutdown
    561          await clearData(
    562            range,
    563            Ci.nsIClearDataService.CLEAR_DOM_STORAGES |
    564              Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD |
    565              Ci.nsIClearDataService.CLEAR_FINGERPRINTING_PROTECTION_STATE
    566          );
    567        }
    568      },
    569    },
    570 
    571    history: {
    572      async clear(range, { progress }) {
    573        // TODO: This check is needed for the case that this method is invoked directly and not via the sanitizer.sanitize API.
    574        // This can be removed once bug 1803799 has landed.
    575        if (!gPrincipalsCollector) {
    576          gPrincipalsCollector = new lazy.PrincipalsCollector();
    577        }
    578        progress.step = "getAllPrincipals";
    579        let principals = await gPrincipalsCollector.getAllPrincipals(progress);
    580        let timerId = Glean.browserSanitizer.history.start();
    581        progress.step = "clearing browsing history";
    582        await clearData(
    583          range,
    584          Ci.nsIClearDataService.CLEAR_HISTORY |
    585            Ci.nsIClearDataService.CLEAR_CONTENT_BLOCKING_RECORDS
    586        );
    587 
    588        // storageAccessAPI permissions record every site that the user
    589        // interacted with and thus mirror history quite closely. It makes
    590        // sense to clear them when we clear history. However, since their absence
    591        // indicates that we can purge cookies and site data for tracking origins without
    592        // user interaction, we need to ensure that we only delete those permissions that
    593        // do not have any existing storage.
    594        progress.step = "clearing user interaction";
    595        await new Promise(resolve => {
    596          Services.clearData.deleteUserInteractionForClearingHistory(
    597            principals,
    598            range ? range[0] : 0,
    599            resolve
    600          );
    601        });
    602        Glean.browserSanitizer.history.stopAndAccumulate(timerId);
    603      },
    604    },
    605 
    606    formdata: {
    607      async clear(range) {
    608        let seenException;
    609        let timerId = Glean.browserSanitizer.formdata.start();
    610        try {
    611          // Clear undo history of all search bars.
    612          for (let currentWindow of Services.wm.getEnumerator(
    613            "navigator:browser"
    614          )) {
    615            let currentDocument = currentWindow.document;
    616 
    617            // searchBar may not exist if it's in the customize mode.
    618            let searchBar = currentDocument.getElementById("searchbar");
    619            if (searchBar) {
    620              let input = searchBar.textbox;
    621              input.value = "";
    622              input.editor?.clearUndoRedo();
    623            }
    624 
    625            let tabBrowser = currentWindow.gBrowser;
    626            if (!tabBrowser) {
    627              // No tab browser? This means that it's too early during startup (typically,
    628              // Session Restore hasn't completed yet). Since we don't have find
    629              // bars at that stage and since Session Restore will not restore
    630              // find bars further down during startup, we have nothing to clear.
    631              continue;
    632            }
    633            for (let tab of tabBrowser.tabs) {
    634              if (tabBrowser.isFindBarInitialized(tab)) {
    635                tabBrowser.getCachedFindBar(tab).clear();
    636              }
    637            }
    638            // Clear any saved find value
    639            tabBrowser._lastFindValue = "";
    640          }
    641        } catch (ex) {
    642          seenException = ex;
    643        }
    644 
    645        try {
    646          let change = { op: "remove" };
    647          if (range) {
    648            [change.firstUsedStart, change.firstUsedEnd] = range;
    649          }
    650          await lazy.FormHistory.update(change).catch(e => {
    651            seenException = new Error("Error " + e.result + ": " + e.message);
    652          });
    653        } catch (ex) {
    654          seenException = ex;
    655        }
    656 
    657        Glean.browserSanitizer.formdata.stopAndAccumulate(timerId);
    658        if (seenException) {
    659          throw seenException;
    660        }
    661      },
    662    },
    663 
    664    downloads: {
    665      async clear(range) {
    666        let timerId = Glean.browserSanitizer.downloads.start();
    667        await clearData(range, Ci.nsIClearDataService.CLEAR_DOWNLOADS);
    668        Glean.browserSanitizer.downloads.stopAndAccumulate(timerId);
    669      },
    670    },
    671 
    672    sessions: {
    673      async clear(range) {
    674        let timerId = Glean.browserSanitizer.sessions.start();
    675        await clearData(
    676          range,
    677          Ci.nsIClearDataService.CLEAR_AUTH_TOKENS |
    678            Ci.nsIClearDataService.CLEAR_AUTH_CACHE
    679        );
    680        Glean.browserSanitizer.sessions.stopAndAccumulate(timerId);
    681      },
    682    },
    683 
    684    siteSettings: {
    685      async clear(range) {
    686        let timerId = Glean.browserSanitizer.sitesettings.start();
    687        await clearData(
    688          range,
    689          Ci.nsIClearDataService.CLEAR_SITE_PERMISSIONS |
    690            Ci.nsIClearDataService.CLEAR_CONTENT_PREFERENCES |
    691            Ci.nsIClearDataService.CLEAR_DOM_PUSH_NOTIFICATIONS |
    692            Ci.nsIClearDataService.CLEAR_CLIENT_AUTH_REMEMBER_SERVICE |
    693            Ci.nsIClearDataService.CLEAR_CERT_EXCEPTIONS |
    694            Ci.nsIClearDataService.CLEAR_CREDENTIAL_MANAGER_STATE |
    695            Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXCEPTION |
    696            Ci.nsIClearDataService.CLEAR_FINGERPRINTING_PROTECTION_STATE
    697        );
    698        Glean.browserSanitizer.sitesettings.stopAndAccumulate(timerId);
    699      },
    700    },
    701 
    702    openWindows: {
    703      _canCloseWindow(win) {
    704        if (win.CanCloseWindow()) {
    705          // We already showed PermitUnload for the window, so let's
    706          // make sure we don't do it again when we actually close the
    707          // window.
    708          win.skipNextCanClose = true;
    709          return true;
    710        }
    711        return false;
    712      },
    713      _resetAllWindowClosures(windowList) {
    714        for (let win of windowList) {
    715          win.skipNextCanClose = false;
    716        }
    717      },
    718      async clear(range, { privateStateForNewWindow = "non-private" }) {
    719        // NB: this closes all *browser* windows, not other windows like the library, about window,
    720        // browser console, etc.
    721 
    722        // Keep track of the time in case we get stuck in la-la-land because of onbeforeunload
    723        // dialogs
    724        let startDate = Date.now();
    725 
    726        // First check if all these windows are OK with being closed:
    727        let windowList = [];
    728        for (let someWin of Services.wm.getEnumerator("navigator:browser")) {
    729          windowList.push(someWin);
    730          // If someone says "no" to a beforeunload prompt, we abort here:
    731          if (!this._canCloseWindow(someWin)) {
    732            this._resetAllWindowClosures(windowList);
    733            throw new Error(
    734              "Sanitize could not close windows: cancelled by user"
    735            );
    736          }
    737 
    738          // ...however, beforeunload prompts spin the event loop, and so the code here won't get
    739          // hit until the prompt has been dismissed. If more than 1 minute has elapsed since we
    740          // started prompting, stop, because the user might not even remember initiating the
    741          // 'forget', and the timespans will be all wrong by now anyway:
    742          if (Date.now() > startDate + 60 * 1000) {
    743            this._resetAllWindowClosures(windowList);
    744            throw new Error("Sanitize could not close windows: timeout");
    745          }
    746        }
    747 
    748        if (!windowList.length) {
    749          return;
    750        }
    751 
    752        // If/once we get here, we should actually be able to close all windows.
    753 
    754        let timerId = Glean.browserSanitizer.openwindows.start();
    755 
    756        // First create a new window. We do this first so that on non-mac, we don't
    757        // accidentally close the app by closing all the windows.
    758        let handler = Cc["@mozilla.org/browser/clh;1"].getService(
    759          Ci.nsIBrowserHandler
    760        );
    761        let defaultArgs = handler.defaultArgs;
    762        let features = "chrome,all,dialog=no," + privateStateForNewWindow;
    763        let newWindow = windowList[0].openDialog(
    764          AppConstants.BROWSER_CHROME_URL,
    765          "_blank",
    766          features,
    767          defaultArgs
    768        );
    769 
    770        let onFullScreen = null;
    771        if (AppConstants.platform == "macosx") {
    772          onFullScreen = function (e) {
    773            newWindow.removeEventListener("fullscreen", onFullScreen);
    774            let docEl = newWindow.document.documentElement;
    775            let sizemode = docEl.getAttribute("sizemode");
    776            if (!newWindow.fullScreen && sizemode == "fullscreen") {
    777              docEl.setAttribute("sizemode", "normal");
    778              e.preventDefault();
    779              e.stopPropagation();
    780              return false;
    781            }
    782            return undefined;
    783          };
    784          newWindow.addEventListener("fullscreen", onFullScreen);
    785        }
    786 
    787        let promiseReady = new Promise(resolve => {
    788          // Window creation and destruction is asynchronous. We need to wait
    789          // until all existing windows are fully closed, and the new window is
    790          // fully open, before continuing. Otherwise the rest of the sanitizer
    791          // could run too early (and miss new cookies being set when a page
    792          // closes) and/or run too late (and not have a fully-formed window yet
    793          // in existence). See bug 1088137.
    794          let newWindowOpened = false;
    795          let onWindowOpened = function (subject) {
    796            if (subject != newWindow) {
    797              return;
    798            }
    799 
    800            Services.obs.removeObserver(
    801              onWindowOpened,
    802              "browser-delayed-startup-finished"
    803            );
    804            if (AppConstants.platform == "macosx") {
    805              newWindow.removeEventListener("fullscreen", onFullScreen);
    806            }
    807            newWindowOpened = true;
    808            // If we're the last thing to happen, invoke callback.
    809            if (numWindowsClosing == 0) {
    810              Glean.browserSanitizer.openwindows.stopAndAccumulate(timerId);
    811              resolve();
    812            }
    813          };
    814 
    815          let numWindowsClosing = windowList.length;
    816          let onWindowClosed = function () {
    817            numWindowsClosing--;
    818            if (numWindowsClosing == 0) {
    819              Services.obs.removeObserver(
    820                onWindowClosed,
    821                "xul-window-destroyed"
    822              );
    823              // If we're the last thing to happen, invoke callback.
    824              if (newWindowOpened) {
    825                Glean.browserSanitizer.openwindows.stopAndAccumulate(timerId);
    826                resolve();
    827              }
    828            }
    829          };
    830          Services.obs.addObserver(
    831            onWindowOpened,
    832            "browser-delayed-startup-finished"
    833          );
    834          Services.obs.addObserver(onWindowClosed, "xul-window-destroyed");
    835        });
    836 
    837        // Start the process of closing windows
    838        while (windowList.length) {
    839          windowList.pop().close();
    840        }
    841        newWindow.focus();
    842        await promiseReady;
    843      },
    844    },
    845 
    846    pluginData: {
    847      async clear() {},
    848    },
    849 
    850    // Combine History and Form Data clearing for the
    851    // new clear history dialog box.
    852    browsingHistoryAndDownloads: {
    853      async clear(range, { progress }) {
    854        progress.step = "getAllPrincipals";
    855        let principals = await gPrincipalsCollector.getAllPrincipals(progress);
    856        let timerId = Glean.browserSanitizer.history.start();
    857        progress.step = "clearing browsing history";
    858        await clearData(
    859          range,
    860          Ci.nsIClearDataService.CLEAR_HISTORY |
    861            Ci.nsIClearDataService.CLEAR_CONTENT_BLOCKING_RECORDS
    862        );
    863 
    864        // storageAccessAPI permissions record every site that the user
    865        // interacted with and thus mirror history quite closely. It makes
    866        // sense to clear them when we clear history. However, since their absence
    867        // indicates that we can purge cookies and site data for tracking origins without
    868        // user interaction, we need to ensure that we only delete those permissions that
    869        // do not have any existing storage.
    870        progress.step = "clearing user interaction";
    871        await new Promise(resolve => {
    872          Services.clearData.deleteUserInteractionForClearingHistory(
    873            principals,
    874            range ? range[0] : 0,
    875            resolve
    876          );
    877        });
    878        Glean.browserSanitizer.history.stopAndAccumulate(timerId);
    879 
    880        // clear Downloads
    881        timerId = Glean.browserSanitizer.downloads.start();
    882        await clearData(range, Ci.nsIClearDataService.CLEAR_DOWNLOADS);
    883        Glean.browserSanitizer.downloads.stopAndAccumulate(timerId);
    884      },
    885    },
    886 
    887    cookiesAndStorage: {
    888      async clear(range, { progress }, clearHonoringExceptions) {
    889        let timerId = Glean.browserSanitizer.cookies.start();
    890        // This is true if called by sanitizeOnShutdown.
    891        // On shutdown we clear by principal to be able to honor the users exceptions
    892        if (clearHonoringExceptions) {
    893          progress.step = "getAllPrincipals";
    894          let principalsForShutdownClearing =
    895            await gPrincipalsCollector.getAllPrincipals(progress);
    896          await maybeSanitizeSessionPrincipals(
    897            progress,
    898            principalsForShutdownClearing,
    899            Ci.nsIClearDataService.CLEAR_COOKIES_AND_SITE_DATA
    900          );
    901        } else {
    902          // Not on shutdown
    903          await clearData(
    904            range,
    905            Ci.nsIClearDataService.CLEAR_COOKIES_AND_SITE_DATA
    906          );
    907        }
    908        await clearData(range, Ci.nsIClearDataService.CLEAR_MEDIA_DEVICES);
    909        Glean.browserSanitizer.cookies.stopAndAccumulate(timerId);
    910      },
    911    },
    912  },
    913 };
    914 
    915 async function sanitizeInternal(items, aItemsToClear, options) {
    916  let { ignoreTimespan = true, range, progress } = options;
    917  let seenError = false;
    918  // Shallow copy the array, as we are going to modify it in place later.
    919  if (!Array.isArray(aItemsToClear)) {
    920    throw new Error("Must pass an array of items to clear.");
    921  }
    922  let itemsToClear = [...aItemsToClear];
    923 
    924  // Store the list of items to clear, in case we are killed before we
    925  // get a chance to complete.
    926  let uid = gPendingSanitizationSerial++;
    927  // Shutdown sanitization is managed outside.
    928  if (!progress.isShutdown) {
    929    addPendingSanitization(uid, itemsToClear, options);
    930  }
    931 
    932  // Store the list of items to clear, for debugging/forensics purposes
    933  for (let k of itemsToClear) {
    934    progress[k] = "ready";
    935    // Create a progress object specific to each cleaner. We'll pass down this
    936    // to the cleaners instead of the main progress object, so they don't end
    937    // up overriding properties each other.
    938    // This specific progress is deleted if the cleaner completes successfully,
    939    // so the metadata will only contain progress of unresolved cleaners.
    940    progress[k + "Progress"] = {};
    941  }
    942 
    943  // Ensure open windows get cleared first, if they're in our list, so that
    944  // they don't stick around in the recently closed windows list, and so we
    945  // can cancel the whole thing if the user selects to keep a window open
    946  // from a beforeunload prompt.
    947  let openWindowsIndex = itemsToClear.indexOf("openWindows");
    948  if (openWindowsIndex != -1) {
    949    itemsToClear.splice(openWindowsIndex, 1);
    950    await items.openWindows.clear(
    951      null,
    952      Object.assign(options, { progress: progress.openWindowsProgress })
    953    );
    954    progress.openWindows = "cleared";
    955    delete progress.openWindowsProgress;
    956  }
    957 
    958  // If we ignore timespan, clear everything,
    959  // otherwise, pick a range.
    960  if (!ignoreTimespan && !range) {
    961    range = Sanitizer.getClearRange();
    962  }
    963 
    964  // For performance reasons we start all the clear tasks at once, then wait
    965  // for their promises later.
    966  // Some of the clear() calls may raise exceptions (for example bug 265028),
    967  // we catch and store them, but continue to sanitize as much as possible.
    968  // Callers should check returned errors and give user feedback
    969  // about items that could not be sanitized
    970  let timerId = Glean.browserSanitizer.total.start();
    971 
    972  let annotateError = (name, ex) => {
    973    progress[name] = "failed";
    974    seenError = true;
    975    console.error("Error sanitizing " + name, ex);
    976  };
    977 
    978  // Array of objects in form { name, promise }.
    979  // `name` is the item's name and `promise` may be a promise, if the
    980  // sanitization is asynchronous, or the function return value, otherwise.
    981  log("Running sanitization for:", itemsToClear);
    982  let handles = [];
    983  for (let name of itemsToClear) {
    984    progress[name] = "blocking";
    985    let item = items[name];
    986    try {
    987      // Catch errors here, so later we can just loop through these.
    988      handles.push({
    989        name,
    990        promise: item
    991          .clear(
    992            range,
    993            Object.assign(options, { progress: progress[name + "Progress"] }),
    994            progress.clearHonoringExceptions
    995          )
    996          .then(
    997            () => {
    998              progress[name] = "cleared";
    999              delete progress[name + "Progress"];
   1000            },
   1001            ex => annotateError(name, ex)
   1002          ),
   1003      });
   1004    } catch (ex) {
   1005      annotateError(name, ex);
   1006    }
   1007  }
   1008  await Promise.all(handles.map(h => h.promise));
   1009 
   1010  log("All sanitizations are complete");
   1011  Glean.browserSanitizer.total.stopAndAccumulate(timerId);
   1012  if (!progress.isShutdown) {
   1013    removePendingSanitization(uid);
   1014  }
   1015  progress = {};
   1016  if (seenError) {
   1017    throw new Error("Error sanitizing");
   1018  }
   1019 }
   1020 
   1021 async function sanitizeOnShutdown(progress) {
   1022  log("Sanitizing on shutdown");
   1023  if (lazy.useOldClearHistoryDialog) {
   1024    progress.sanitizationPrefs = {
   1025      privacy_sanitize_sanitizeOnShutdown: Services.prefs.getBoolPref(
   1026        "privacy.sanitize.sanitizeOnShutdown"
   1027      ),
   1028      privacy_clearOnShutdown_cookies: Services.prefs.getBoolPref(
   1029        "privacy.clearOnShutdown.cookies"
   1030      ),
   1031      privacy_clearOnShutdown_history: Services.prefs.getBoolPref(
   1032        "privacy.clearOnShutdown.history"
   1033      ),
   1034      privacy_clearOnShutdown_formdata: Services.prefs.getBoolPref(
   1035        "privacy.clearOnShutdown.formdata"
   1036      ),
   1037      privacy_clearOnShutdown_downloads: Services.prefs.getBoolPref(
   1038        "privacy.clearOnShutdown.downloads"
   1039      ),
   1040      privacy_clearOnShutdown_cache: Services.prefs.getBoolPref(
   1041        "privacy.clearOnShutdown.cache"
   1042      ),
   1043      privacy_clearOnShutdown_sessions: Services.prefs.getBoolPref(
   1044        "privacy.clearOnShutdown.sessions"
   1045      ),
   1046      privacy_clearOnShutdown_offlineApps: Services.prefs.getBoolPref(
   1047        "privacy.clearOnShutdown.offlineApps"
   1048      ),
   1049      privacy_clearOnShutdown_siteSettings: Services.prefs.getBoolPref(
   1050        "privacy.clearOnShutdown.siteSettings"
   1051      ),
   1052      privacy_clearOnShutdown_openWindows: Services.prefs.getBoolPref(
   1053        "privacy.clearOnShutdown.openWindows"
   1054      ),
   1055    };
   1056  } else {
   1057    // Perform a migration if this is the first time sanitizeOnShutdown is
   1058    // running for the user with the new dialog
   1059    Sanitizer.maybeMigratePrefs("clearOnShutdown");
   1060 
   1061    progress.sanitizationPrefs = {
   1062      privacy_sanitize_sanitizeOnShutdown: Services.prefs.getBoolPref(
   1063        "privacy.sanitize.sanitizeOnShutdown"
   1064      ),
   1065      privacy_clearOnShutdown_v2_cookiesAndStorage: Services.prefs.getBoolPref(
   1066        "privacy.clearOnShutdown_v2.cookiesAndStorage"
   1067      ),
   1068      privacy_clearOnShutdown_v2_browsingHistoryAndDownloads:
   1069        Services.prefs.getBoolPref(
   1070          "privacy.clearOnShutdown_v2.browsingHistoryAndDownloads"
   1071        ),
   1072      privacy_clearOnShutdown_v2_cache: Services.prefs.getBoolPref(
   1073        "privacy.clearOnShutdown_v2.cache"
   1074      ),
   1075      privacy_clearOnShutdown_v2_formdata: Services.prefs.getBoolPref(
   1076        "privacy.clearOnShutdown_v2.formdata"
   1077      ),
   1078      privacy_clearOnShutdown_v2_siteSettings: Services.prefs.getBoolPref(
   1079        "privacy.clearOnShutdown_v2.siteSettings"
   1080      ),
   1081    };
   1082  }
   1083 
   1084  let needsSyncSavePrefs = false;
   1085  if (Sanitizer.shouldSanitizeOnShutdown) {
   1086    // Need to sanitize upon shutdown
   1087    progress.advancement = "shutdown-cleaner";
   1088 
   1089    let itemsToClear = getItemsToClearFromPrefBranch(
   1090      Sanitizer.PREF_SHUTDOWN_BRANCH
   1091    );
   1092    await Sanitizer.sanitize(itemsToClear, { progress });
   1093 
   1094    // We didn't crash during shutdown sanitization, so annotate it to avoid
   1095    // sanitizing again on startup.
   1096    removePendingSanitization("shutdown");
   1097    needsSyncSavePrefs = true;
   1098  }
   1099 
   1100  if (Sanitizer.shouldSanitizeNewTabContainer) {
   1101    progress.advancement = "newtab-segregation";
   1102    sanitizeNewTabSegregation();
   1103    removePendingSanitization("newtab-container");
   1104    needsSyncSavePrefs = true;
   1105  }
   1106 
   1107  if (needsSyncSavePrefs) {
   1108    Services.prefs.savePrefFile(null);
   1109  }
   1110 
   1111  if (!Sanitizer.shouldSanitizeOnShutdown) {
   1112    // In case the user has not activated sanitizeOnShutdown but has explicitely set exceptions
   1113    // to always clear particular origins, we clear those here
   1114 
   1115    progress.advancement = "session-permission";
   1116 
   1117    let exceptions = 0;
   1118    let selectedPrincipals = [];
   1119    // Let's see if we have to forget some particular site.
   1120    for (let permission of Services.perms.all) {
   1121      if (
   1122        permission.type != "cookie" ||
   1123        permission.capability != Ci.nsICookiePermission.ACCESS_SESSION
   1124      ) {
   1125        continue;
   1126      }
   1127 
   1128      // We consider just permissions set for http, https and file URLs.
   1129      if (!isSupportedPrincipal(permission.principal)) {
   1130        continue;
   1131      }
   1132 
   1133      log(
   1134        "Custom session cookie permission detected for: " +
   1135          permission.principal.asciiSpec
   1136      );
   1137      exceptions++;
   1138 
   1139      // We use just the URI here, because permissions ignore OriginAttributes.
   1140      // The principalsCollector is lazy, this is computed only once
   1141      if (!gPrincipalsCollector) {
   1142        gPrincipalsCollector = new lazy.PrincipalsCollector();
   1143      }
   1144      let principals = await gPrincipalsCollector.getAllPrincipals(progress);
   1145      selectedPrincipals.push(
   1146        ...extractMatchingPrincipals(principals, permission.principal.host)
   1147      );
   1148    }
   1149    await maybeSanitizeSessionPrincipals(
   1150      progress,
   1151      selectedPrincipals,
   1152      Ci.nsIClearDataService.CLEAR_ALL_CACHES |
   1153        Ci.nsIClearDataService.CLEAR_COOKIES |
   1154        Ci.nsIClearDataService.CLEAR_DOM_STORAGES |
   1155        Ci.nsIClearDataService.CLEAR_EME |
   1156        Ci.nsIClearDataService.CLEAR_BOUNCE_TRACKING_PROTECTION_STATE
   1157    );
   1158    progress.sanitizationPrefs.session_permission_exceptions = exceptions;
   1159  }
   1160 
   1161  await cleanupAfterSanitization(Ci.nsIClearDataService.CLEAR_ALL);
   1162 
   1163  progress.advancement = "done";
   1164 }
   1165 
   1166 async function cleanupAfterSanitization(flags) {
   1167  await new Promise(resolve =>
   1168    Services.clearData.cleanupAfterDeletionAtShutdown(flags, resolve)
   1169  );
   1170 }
   1171 
   1172 // Extracts the principals matching matchUri as root domain.
   1173 function extractMatchingPrincipals(principals, matchHost) {
   1174  return principals.filter(principal => {
   1175    return Services.eTLD.hasRootDomain(matchHost, principal.host);
   1176  });
   1177 }
   1178 
   1179 /**
   1180 * This method receives a list of principals and it checks if some of them or
   1181 * some of their sub-domain need to be sanitize.
   1182 *
   1183 * @param {object} progress - Object to keep track of the sanitization progress, prefs and mode
   1184 * @param {nsIPrincipal[]} principals - The principals generated by the PrincipalsCollector
   1185 * @param {int} flags - The cleaning categories that need to be cleaned for the principals.
   1186 * @returns {Promise} - Resolves once the clearing of the principals to be cleared is done
   1187 */
   1188 async function maybeSanitizeSessionPrincipals(progress, principals, flags) {
   1189  log("Sanitizing " + principals.length + " principals");
   1190 
   1191  let promises = [];
   1192  let permissions = new Map();
   1193  Services.perms.getAllWithTypePrefix("cookie").forEach(perm => {
   1194    permissions.set(perm.principal.origin, perm);
   1195  });
   1196 
   1197  principals.forEach(principal => {
   1198    progress.step = "checking-principal";
   1199    let cookieAllowed = cookiesAllowedForDomainOrSubDomain(
   1200      principal,
   1201      permissions
   1202    );
   1203    progress.step = "principal-checked:" + cookieAllowed;
   1204 
   1205    if (!cookieAllowed) {
   1206      promises.push(sanitizeSessionPrincipal(progress, principal, flags));
   1207    }
   1208  });
   1209 
   1210  progress.step = "promises:" + promises.length;
   1211  if (promises.length) {
   1212    await Promise.all(promises);
   1213  }
   1214  progress.step = "promises resolved";
   1215 }
   1216 
   1217 function cookiesAllowedForDomainOrSubDomain(principal, permissions) {
   1218  log("Checking principal: " + principal.asciiSpec);
   1219 
   1220  // If we have the 'cookie' permission for this principal, let's return
   1221  // immediately.
   1222  let cookiePermission = checkIfCookiePermissionIsSet(principal);
   1223  if (cookiePermission != null) {
   1224    return cookiePermission;
   1225  }
   1226 
   1227  for (let perm of permissions.values()) {
   1228    if (perm.type != "cookie") {
   1229      permissions.delete(perm.principal.origin);
   1230      continue;
   1231    }
   1232    // We consider just permissions set for http, https and file URLs.
   1233    if (!isSupportedPrincipal(perm.principal)) {
   1234      permissions.delete(perm.principal.origin);
   1235      continue;
   1236    }
   1237 
   1238    // We don't care about scheme, port, and anything else.
   1239    if (Services.eTLD.hasRootDomain(perm.principal.host, principal.host)) {
   1240      log("Cookie check on principal: " + perm.principal.asciiSpec);
   1241      let rootDomainCookiePermission = checkIfCookiePermissionIsSet(
   1242        perm.principal
   1243      );
   1244      if (rootDomainCookiePermission != null) {
   1245        return rootDomainCookiePermission;
   1246      }
   1247    }
   1248  }
   1249 
   1250  log("Cookie not allowed.");
   1251  return false;
   1252 }
   1253 
   1254 /**
   1255 * Checks if a cookie permission is set for a given principal
   1256 *
   1257 * @returns {boolean} - true: cookie permission "ACCESS_ALLOW", false: cookie permission "ACCESS_DENY"/"ACCESS_SESSION"
   1258 * @returns {null} - No cookie permission is set for this principal
   1259 */
   1260 function checkIfCookiePermissionIsSet(principal) {
   1261  let p = Services.perms.testPermissionFromPrincipal(principal, "cookie");
   1262 
   1263  if (p == Ci.nsICookiePermission.ACCESS_ALLOW) {
   1264    log("Cookie allowed!");
   1265    return true;
   1266  }
   1267 
   1268  if (
   1269    p == Ci.nsICookiePermission.ACCESS_DENY ||
   1270    p == Ci.nsICookiePermission.ACCESS_SESSION
   1271  ) {
   1272    log("Cookie denied or session!");
   1273    return false;
   1274  }
   1275  // This is an old profile with unsupported permission values
   1276  if (p != Ci.nsICookiePermission.ACCESS_DEFAULT) {
   1277    log("Not supported cookie permission: " + p);
   1278    return false;
   1279  }
   1280  return null;
   1281 }
   1282 
   1283 async function sanitizeSessionPrincipal(progress, principal, flags) {
   1284  log("Sanitizing principal: " + principal.asciiSpec);
   1285 
   1286  await new Promise(resolve => {
   1287    progress.sanitizePrincipal = "started";
   1288    Services.clearData.deleteDataFromPrincipal(
   1289      principal,
   1290      true /* user request */,
   1291      flags,
   1292      resolve
   1293    );
   1294  });
   1295  progress.sanitizePrincipal = "completed";
   1296 }
   1297 
   1298 function sanitizeNewTabSegregation() {
   1299  let identity = lazy.ContextualIdentityService.getPrivateIdentity(
   1300    "userContextIdInternal.thumbnail"
   1301  );
   1302  if (identity) {
   1303    Services.clearData.deleteDataFromOriginAttributesPattern({
   1304      userContextId: identity.userContextId,
   1305    });
   1306  }
   1307 }
   1308 
   1309 /**
   1310 * Gets an array of items to clear from the given pref branch.
   1311 *
   1312 * @param branch The pref branch to fetch.
   1313 * @return Array of items to clear
   1314 */
   1315 function getItemsToClearFromPrefBranch(branch) {
   1316  branch = Services.prefs.getBranch(branch);
   1317  return Object.keys(Sanitizer.items).filter(itemName => {
   1318    try {
   1319      return branch.getBoolPref(itemName);
   1320    } catch (ex) {
   1321      return false;
   1322    }
   1323  });
   1324 }
   1325 
   1326 /**
   1327 * These functions are used to track pending sanitization on the next startup
   1328 * in case of a crash before a sanitization could happen.
   1329 *
   1330 * @param id A unique id identifying the sanitization
   1331 * @param itemsToClear The items to clear
   1332 * @param options The Sanitize options
   1333 */
   1334 function addPendingSanitization(id, itemsToClear, options) {
   1335  let pendingSanitizations = safeGetPendingSanitizations();
   1336  pendingSanitizations.push({ id, itemsToClear, options });
   1337  Services.prefs.setStringPref(
   1338    Sanitizer.PREF_PENDING_SANITIZATIONS,
   1339    JSON.stringify(pendingSanitizations)
   1340  );
   1341 }
   1342 
   1343 function removePendingSanitization(id) {
   1344  let pendingSanitizations = safeGetPendingSanitizations();
   1345  let i = pendingSanitizations.findIndex(s => s.id == id);
   1346  let [s] = pendingSanitizations.splice(i, 1);
   1347  Services.prefs.setStringPref(
   1348    Sanitizer.PREF_PENDING_SANITIZATIONS,
   1349    JSON.stringify(pendingSanitizations)
   1350  );
   1351  return s;
   1352 }
   1353 
   1354 function getAndClearPendingSanitizations() {
   1355  let pendingSanitizations = safeGetPendingSanitizations();
   1356  if (pendingSanitizations.length) {
   1357    Services.prefs.clearUserPref(Sanitizer.PREF_PENDING_SANITIZATIONS);
   1358  }
   1359  return pendingSanitizations;
   1360 }
   1361 
   1362 function safeGetPendingSanitizations() {
   1363  try {
   1364    return JSON.parse(
   1365      Services.prefs.getStringPref(Sanitizer.PREF_PENDING_SANITIZATIONS, "[]")
   1366    );
   1367  } catch (ex) {
   1368    console.error("Invalid JSON value for pending sanitizations: ", ex);
   1369    return [];
   1370  }
   1371 }
   1372 
   1373 async function clearData(range, flags) {
   1374  if (range) {
   1375    await new Promise(resolve => {
   1376      Services.clearData.deleteDataInTimeRange(
   1377        range[0],
   1378        range[1],
   1379        true /* user request */,
   1380        flags,
   1381        resolve
   1382      );
   1383    });
   1384  } else {
   1385    await new Promise(resolve => {
   1386      Services.clearData.deleteData(flags, resolve);
   1387    });
   1388  }
   1389 }
   1390 
   1391 function isSupportedPrincipal(principal) {
   1392  return ["http", "https", "file"].some(scheme => principal.schemeIs(scheme));
   1393 }