270 lines
8.5 KiB
JavaScript
270 lines
8.5 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/. */
|
|
|
|
/**
|
|
* 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);
|
|
},
|
|
onViewHiding(aEvent) {
|
|
ResetPBMPanel.onViewHiding(aEvent);
|
|
},
|
|
hideInNonPrivateBrowsing: true,
|
|
};
|
|
|
|
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 panelview = event.target;
|
|
let triggeringWindow = panelview.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(panelview);
|
|
|
|
// Trigger the restart action.
|
|
await this._restartPBM(triggeringWindow);
|
|
|
|
Glean.privateBrowsingResetPbm.resetAction.record({ did_confirm: false });
|
|
return;
|
|
}
|
|
|
|
panelview.addEventListener("command", this);
|
|
|
|
// 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",
|
|
});
|
|
},
|
|
|
|
onViewHiding(event) {
|
|
let panelview = event.target;
|
|
panelview.removeEventListener("command", this);
|
|
},
|
|
|
|
handleEvent(event) {
|
|
let button = event.target;
|
|
switch (button.id) {
|
|
case "reset-pbm-panel-cancel-button":
|
|
this.onCancel(button);
|
|
break;
|
|
case "reset-pbm-panel-confirm-button":
|
|
this.onConfirm(button);
|
|
break;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 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.SidebarController?.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
|
|
);
|