tor-browser

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

ResetPBMPanel.sys.mjs (8750B)


      1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
      2 /* vim: set ts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 /**
      8 * ResetPBMPanel contains the logic for the restart private browsing action.
      9 * The feature is exposed via a toolbar button in private browsing windows. It
     10 * allows users to restart their private browsing session, clearing all site
     11 * data and closing all PBM tabs / windows.
     12 * The toolbar button for triggering the panel is only shown in private browsing
     13 * windows or if permanent private browsing mode is enabled.
     14 */
     15 
     16 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
     17 
     18 const ENABLED_PREF = "browser.privatebrowsing.resetPBM.enabled";
     19 const SHOW_CONFIRM_DIALOG_PREF =
     20  "browser.privatebrowsing.resetPBM.showConfirmationDialog";
     21 
     22 const lazy = {};
     23 ChromeUtils.defineESModuleGetters(lazy, {
     24  CustomizableUI:
     25    "moz-src:///browser/components/customizableui/CustomizableUI.sys.mjs",
     26  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
     27  SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
     28 });
     29 
     30 export const ResetPBMPanel = {
     31  // Button and view config for CustomizableUI.
     32  _widgetConfig: null,
     33 
     34  /**
     35   * Initialize the widget code depending on pref state.
     36   */
     37  init() {
     38    // Populate _widgetConfig during init to defer (lazy) CustomizableUI import.
     39    this._widgetConfig ??= {
     40      id: "reset-pbm-toolbar-button",
     41      l10nId: "reset-pbm-toolbar-button",
     42      type: "view",
     43      viewId: "reset-pbm-panel",
     44      defaultArea: lazy.CustomizableUI.AREA_NAVBAR,
     45      onViewShowing(aEvent) {
     46        ResetPBMPanel.onViewShowing(aEvent);
     47      },
     48      onViewHiding(aEvent) {
     49        ResetPBMPanel.onViewHiding(aEvent);
     50      },
     51      hideInNonPrivateBrowsing: true,
     52    };
     53 
     54    if (this._enabled) {
     55      lazy.CustomizableUI.createWidget(this._widgetConfig);
     56    } else {
     57      lazy.CustomizableUI.destroyWidget(this._widgetConfig.id);
     58    }
     59  },
     60 
     61  /**
     62   * Called when the reset pbm panelview is showing as the result of clicking
     63   * the toolbar button.
     64   */
     65  async onViewShowing(event) {
     66    let panelview = event.target;
     67    let triggeringWindow = panelview.ownerGlobal;
     68 
     69    // We may skip the confirmation panel if disabled via pref.
     70    if (!this._shouldConfirmClear) {
     71      // Prevent the panel from showing up.
     72      event.preventDefault();
     73 
     74      // If the action is triggered from the overflow menu make sure that the
     75      // panel gets hidden.
     76      lazy.CustomizableUI.hidePanelForNode(panelview);
     77 
     78      // Trigger the restart action.
     79      await this._restartPBM(triggeringWindow);
     80 
     81      Glean.privateBrowsingResetPbm.resetAction.record({ did_confirm: false });
     82      return;
     83    }
     84 
     85    panelview.addEventListener("command", this);
     86 
     87    // Before the panel is shown, update checkbox state based on pref.
     88    this._rememberCheck(triggeringWindow).checked = this._shouldConfirmClear;
     89 
     90    Glean.privateBrowsingResetPbm.confirmPanel.record({
     91      action: "show",
     92      reason: "toolbar-btn",
     93    });
     94  },
     95 
     96  onViewHiding(event) {
     97    let panelview = event.target;
     98    panelview.removeEventListener("command", this);
     99  },
    100 
    101  handleEvent(event) {
    102    let button = event.target;
    103    switch (button.id) {
    104      case "reset-pbm-panel-cancel-button":
    105        this.onCancel(button);
    106        break;
    107      case "reset-pbm-panel-confirm-button":
    108        this.onConfirm(button);
    109        break;
    110    }
    111  },
    112 
    113  /**
    114   * Handles the confirmation panel cancel button.
    115   *
    116   * @param {MozButton} button - Cancel button that triggered the action.
    117   */
    118  onCancel(button) {
    119    if (!this._enabled) {
    120      throw new Error("Not initialized.");
    121    }
    122    lazy.CustomizableUI.hidePanelForNode(button);
    123 
    124    Glean.privateBrowsingResetPbm.confirmPanel.record({
    125      action: "hide",
    126      reason: "cancel-btn",
    127    });
    128  },
    129 
    130  /**
    131   * Handles the confirmation panel confirm button which triggers the clear
    132   * action.
    133   *
    134   * @param {MozButton} button - Confirm button that triggered the action.
    135   */
    136  async onConfirm(button) {
    137    if (!this._enabled) {
    138      throw new Error("Not initialized.");
    139    }
    140    let triggeringWindow = button.ownerGlobal;
    141 
    142    // Write the checkbox state to pref. Only do this when the user
    143    // confirms.
    144    // Setting this pref to true means there is no way to see the panel
    145    // again other than flipping the pref back via about:config or resetting
    146    // the profile. This is by design.
    147    Services.prefs.setBoolPref(
    148      SHOW_CONFIRM_DIALOG_PREF,
    149      this._rememberCheck(triggeringWindow).checked
    150    );
    151 
    152    lazy.CustomizableUI.hidePanelForNode(button);
    153 
    154    Glean.privateBrowsingResetPbm.confirmPanel.record({
    155      action: "hide",
    156      reason: "confirm-btn",
    157    });
    158 
    159    // Clear the private browsing session.
    160    await this._restartPBM(triggeringWindow);
    161 
    162    Glean.privateBrowsingResetPbm.resetAction.record({ did_confirm: true });
    163  },
    164 
    165  /**
    166   * Restart the private browsing session. This is achieved by closing all other
    167   * PBM windows, closing all tabs in the current window but
    168   * about:privatebrowsing and triggering PBM data clearing.
    169   *
    170   * @param {ChromeWindow} triggeringWindow - The (private browsing) chrome window which
    171   * triggered the restart action.
    172   */
    173  async _restartPBM(triggeringWindow) {
    174    if (
    175      !triggeringWindow ||
    176      !lazy.PrivateBrowsingUtils.isWindowPrivate(triggeringWindow)
    177    ) {
    178      throw new Error("Invalid triggering window.");
    179    }
    180 
    181    // 1. Close all PBM windows but the current one.
    182    for (let w of Services.ww.getWindowEnumerator()) {
    183      if (
    184        w != triggeringWindow &&
    185        lazy.PrivateBrowsingUtils.isWindowPrivate(w)
    186      ) {
    187        // This suppresses confirmation dialogs like the beforeunload
    188        // handler and the tab close warning.
    189        // Skip over windows that don't have the closeWindow method.
    190        w.closeWindow?.(true, null, "restart-pbm");
    191      }
    192    }
    193 
    194    // 2. For the current PBM window create a new tab which will be used for
    195    //    the initial newtab page.
    196    let newTab = triggeringWindow.gBrowser.addTab(
    197      triggeringWindow.BROWSER_NEW_TAB_URL,
    198      {
    199        triggeringPrincipal:
    200          Services.scriptSecurityManager.getSystemPrincipal(),
    201      }
    202    );
    203    if (!newTab) {
    204      throw new Error("Could not open new tab.");
    205    }
    206 
    207    // 3. Close all other tabs.
    208    triggeringWindow.gBrowser.removeAllTabsBut(newTab, {
    209      skipPermitUnload: true,
    210      // Instruct the SessionStore to not save closed tab data for these tabs.
    211      // We don't want to leak them into the next private browsing session.
    212      skipSessionStore: true,
    213      animate: false,
    214      skipWarnAboutClosingTabs: true,
    215      skipPinnedOrSelectedTabs: false,
    216    });
    217 
    218    // In the remaining PBM window: If the sidebar is open close it.
    219    triggeringWindow.SidebarController?.hide();
    220 
    221    // Clear session store data for the remaining PBM window.
    222    lazy.SessionStore.purgeDataForPrivateWindow(triggeringWindow);
    223 
    224    // 4. Clear private browsing data.
    225    //    TODO: this doesn't wait for data to be cleared. This is probably
    226    //    fine since PBM data is stored in memory and can be cleared quick
    227    //    enough. The mechanism is brittle though, some callers still
    228    //    perform clearing async. Bug 1846494 will address this.
    229    Services.obs.notifyObservers(null, "last-pb-context-exited");
    230 
    231    // Once clearing is complete show a toast message.
    232 
    233    let toolbarButton = this._toolbarButton(triggeringWindow);
    234 
    235    // Find the anchor used for the confirmation hint panel. If the toolbar
    236    // button is in the overflow menu we can't use it as an anchor. Instead we
    237    // anchor off the overflow button as indicated by cui-anchorid.
    238    let anchor;
    239    let anchorID = toolbarButton.getAttribute("cui-anchorid");
    240    if (anchorID) {
    241      anchor = triggeringWindow.document.getElementById(anchorID);
    242    }
    243    triggeringWindow.ConfirmationHint.show(
    244      anchor ?? toolbarButton,
    245      "reset-pbm-panel-complete",
    246      { position: "bottomright topright" }
    247    );
    248  },
    249 
    250  _toolbarButton(win) {
    251    return lazy.CustomizableUI.getWidget(this._widgetConfig.id).forWindow(win)
    252      .node;
    253  },
    254 
    255  _rememberCheck(win) {
    256    return win.document.getElementById("reset-pbm-panel-checkbox");
    257  },
    258 };
    259 
    260 XPCOMUtils.defineLazyPreferenceGetter(
    261  ResetPBMPanel,
    262  "_enabled",
    263  ENABLED_PREF,
    264  false,
    265  // On pref change update the init state.
    266  ResetPBMPanel.init.bind(ResetPBMPanel)
    267 );
    268 XPCOMUtils.defineLazyPreferenceGetter(
    269  ResetPBMPanel,
    270  "_shouldConfirmClear",
    271  SHOW_CONFIRM_DIALOG_PREF,
    272  true
    273 );