summaryrefslogtreecommitdiffstats
path: root/browser/components/privatebrowsing/ResetPBMPanel.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/privatebrowsing/ResetPBMPanel.sys.mjs')
-rw-r--r--browser/components/privatebrowsing/ResetPBMPanel.sys.mjs247
1 files changed, 247 insertions, 0 deletions
diff --git a/browser/components/privatebrowsing/ResetPBMPanel.sys.mjs b/browser/components/privatebrowsing/ResetPBMPanel.sys.mjs
new file mode 100644
index 0000000000..f5e818c2a8
--- /dev/null
+++ b/browser/components/privatebrowsing/ResetPBMPanel.sys.mjs
@@ -0,0 +1,247 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* eslint-env mozilla/browser-window */
+
+/**
+ * ResetPBMPanel contains the logic for the restart private browsing action.
+ * The feature is exposed via a toolbar button in private browsing windows. It
+ * allows users to restart their private browsing session, clearing all site
+ * data and closing all PBM tabs / windows.
+ * The toolbar button for triggering the panel is only shown in private browsing
+ * windows or if permanent private browsing mode is enabled.
+ */
+
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+const ENABLED_PREF = "browser.privatebrowsing.resetPBM.enabled";
+const SHOW_CONFIRM_DIALOG_PREF =
+ "browser.privatebrowsing.resetPBM.showConfirmationDialog";
+
+const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, {
+ CustomizableUI: "resource:///modules/CustomizableUI.sys.mjs",
+ PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
+ SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
+});
+
+export const ResetPBMPanel = {
+ // Button and view config for CustomizableUI.
+ _widgetConfig: null,
+
+ /**
+ * Initialize the widget code depending on pref state.
+ */
+ init() {
+ // Populate _widgetConfig during init to defer (lazy) CustomizableUI import.
+ this._widgetConfig ??= {
+ id: "reset-pbm-toolbar-button",
+ l10nId: "reset-pbm-toolbar-button",
+ type: "view",
+ viewId: "reset-pbm-panel",
+ defaultArea: lazy.CustomizableUI.AREA_NAVBAR,
+ onViewShowing(aEvent) {
+ ResetPBMPanel.onViewShowing(aEvent);
+ },
+ };
+
+ if (this._enabled) {
+ lazy.CustomizableUI.createWidget(this._widgetConfig);
+ } else {
+ lazy.CustomizableUI.destroyWidget(this._widgetConfig.id);
+ }
+ },
+
+ /**
+ * Called when the reset pbm panelview is showing as the result of clicking
+ * the toolbar button.
+ */
+ async onViewShowing(event) {
+ let triggeringWindow = event.target.ownerGlobal;
+
+ // We may skip the confirmation panel if disabled via pref.
+ if (!this._shouldConfirmClear) {
+ // Prevent the panel from showing up.
+ event.preventDefault();
+
+ // If the action is triggered from the overflow menu make sure that the
+ // panel gets hidden.
+ lazy.CustomizableUI.hidePanelForNode(event.target);
+
+ // Trigger the restart action.
+ await this._restartPBM(triggeringWindow);
+
+ Glean.privateBrowsingResetPbm.resetAction.record({ did_confirm: false });
+ return;
+ }
+
+ // Before the panel is shown, update checkbox state based on pref.
+ this._rememberCheck(triggeringWindow).checked = this._shouldConfirmClear;
+
+ Glean.privateBrowsingResetPbm.confirmPanel.record({
+ action: "show",
+ reason: "toolbar-btn",
+ });
+ },
+
+ /**
+ * Handles the confirmation panel cancel button.
+ * @param {MozButton} button - Cancel button that triggered the action.
+ */
+ onCancel(button) {
+ if (!this._enabled) {
+ throw new Error("Not initialized.");
+ }
+ lazy.CustomizableUI.hidePanelForNode(button);
+
+ Glean.privateBrowsingResetPbm.confirmPanel.record({
+ action: "hide",
+ reason: "cancel-btn",
+ });
+ },
+
+ /**
+ * Handles the confirmation panel confirm button which triggers the clear
+ * action.
+ * @param {MozButton} button - Confirm button that triggered the action.
+ */
+ async onConfirm(button) {
+ if (!this._enabled) {
+ throw new Error("Not initialized.");
+ }
+ let triggeringWindow = button.ownerGlobal;
+
+ // Write the checkbox state to pref. Only do this when the user
+ // confirms.
+ // Setting this pref to true means there is no way to see the panel
+ // again other than flipping the pref back via about:config or resetting
+ // the profile. This is by design.
+ Services.prefs.setBoolPref(
+ SHOW_CONFIRM_DIALOG_PREF,
+ this._rememberCheck(triggeringWindow).checked
+ );
+
+ lazy.CustomizableUI.hidePanelForNode(button);
+
+ Glean.privateBrowsingResetPbm.confirmPanel.record({
+ action: "hide",
+ reason: "confirm-btn",
+ });
+
+ // Clear the private browsing session.
+ await this._restartPBM(triggeringWindow);
+
+ Glean.privateBrowsingResetPbm.resetAction.record({ did_confirm: true });
+ },
+
+ /**
+ * Restart the private browsing session. This is achieved by closing all other
+ * PBM windows, closing all tabs in the current window but
+ * about:privatebrowsing and triggering PBM data clearing.
+ *
+ * @param {ChromeWindow} triggeringWindow - The (private browsing) chrome window which
+ * triggered the restart action.
+ */
+ async _restartPBM(triggeringWindow) {
+ if (
+ !triggeringWindow ||
+ !lazy.PrivateBrowsingUtils.isWindowPrivate(triggeringWindow)
+ ) {
+ throw new Error("Invalid triggering window.");
+ }
+
+ // 1. Close all PBM windows but the current one.
+ for (let w of Services.ww.getWindowEnumerator()) {
+ if (
+ w != triggeringWindow &&
+ lazy.PrivateBrowsingUtils.isWindowPrivate(w)
+ ) {
+ // This suppresses confirmation dialogs like the beforeunload
+ // handler and the tab close warning.
+ // Skip over windows that don't have the closeWindow method.
+ w.closeWindow?.(true, null, "restart-pbm");
+ }
+ }
+
+ // 2. For the current PBM window create a new tab which will be used for
+ // the initial newtab page.
+ let newTab = triggeringWindow.gBrowser.addTab(
+ triggeringWindow.BROWSER_NEW_TAB_URL,
+ {
+ triggeringPrincipal:
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ }
+ );
+ if (!newTab) {
+ throw new Error("Could not open new tab.");
+ }
+
+ // 3. Close all other tabs.
+ triggeringWindow.gBrowser.removeAllTabsBut(newTab, {
+ skipPermitUnload: true,
+ // Instruct the SessionStore to not save closed tab data for these tabs.
+ // We don't want to leak them into the next private browsing session.
+ skipSessionStore: true,
+ animate: false,
+ skipWarnAboutClosingTabs: true,
+ skipPinnedOrSelectedTabs: false,
+ });
+
+ // In the remaining PBM window: If the sidebar is open close it.
+ triggeringWindow.SidebarUI?.hide();
+
+ // Clear session store data for the remaining PBM window.
+ lazy.SessionStore.purgeDataForPrivateWindow(triggeringWindow);
+
+ // 4. Clear private browsing data.
+ // TODO: this doesn't wait for data to be cleared. This is probably
+ // fine since PBM data is stored in memory and can be cleared quick
+ // enough. The mechanism is brittle though, some callers still
+ // perform clearing async. Bug 1846494 will address this.
+ Services.obs.notifyObservers(null, "last-pb-context-exited");
+
+ // Once clearing is complete show a toast message.
+
+ let toolbarButton = this._toolbarButton(triggeringWindow);
+
+ // Find the anchor used for the confirmation hint panel. If the toolbar
+ // button is in the overflow menu we can't use it as an anchor. Instead we
+ // anchor off the overflow button as indicated by cui-anchorid.
+ let anchor;
+ let anchorID = toolbarButton.getAttribute("cui-anchorid");
+ if (anchorID) {
+ anchor = triggeringWindow.document.getElementById(anchorID);
+ }
+ triggeringWindow.ConfirmationHint.show(
+ anchor ?? toolbarButton,
+ "reset-pbm-panel-complete",
+ { position: "bottomright topright" }
+ );
+ },
+
+ _toolbarButton(win) {
+ return lazy.CustomizableUI.getWidget(this._widgetConfig.id).forWindow(win)
+ .node;
+ },
+
+ _rememberCheck(win) {
+ return win.document.getElementById("reset-pbm-panel-checkbox");
+ },
+};
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ ResetPBMPanel,
+ "_enabled",
+ ENABLED_PREF,
+ false,
+ // On pref change update the init state.
+ ResetPBMPanel.init.bind(ResetPBMPanel)
+);
+XPCOMUtils.defineLazyPreferenceGetter(
+ ResetPBMPanel,
+ "_shouldConfirmClear",
+ SHOW_CONFIRM_DIALOG_PREF,
+ true
+);