forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			247 lines
		
	
	
	
		
			8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			247 lines
		
	
	
	
		
			8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* -*- 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
 | |
| );
 | 
