diff options
Diffstat (limited to 'browser/components/privatebrowsing/ResetPBMPanel.sys.mjs')
-rw-r--r-- | browser/components/privatebrowsing/ResetPBMPanel.sys.mjs | 247 |
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 +); |