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 );