Source code
Revision control
Copy as Markdown
Other Tools
/* 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/. */
import { BANDWIDTH } from "chrome://browser/content/ipprotection/ipprotection-constants.mjs";
const lazy = {};
ChromeUtils.defineLazyGetter(lazy, "ipProtectionLocalization", () => {
return new Localization(["browser/ipProtection.ftl"], true);
});
ChromeUtils.defineESModuleGetters(lazy, {
EveryWindow: "resource:///modules/EveryWindow.sys.mjs",
IPPEnrollAndEntitleManager:
"moz-src:///browser/components/ipprotection/IPPEnrollAndEntitleManager.sys.mjs",
IPPProxyManager:
"moz-src:///browser/components/ipprotection/IPPProxyManager.sys.mjs",
IPPProxyStates:
"moz-src:///browser/components/ipprotection/IPPProxyManager.sys.mjs",
});
/**
* Manages showing alerts for different VPN states
*/
class IPProtectionAlertManagerClass {
#localizationMessages = null;
#promptsOpen = false;
#initialized = false;
get initialized() {
return this.#initialized;
}
init() {
if (this.#initialized) {
return;
}
lazy.IPPProxyManager.addEventListener("IPPProxyManager:StateChanged", this);
this.#initialized = true;
}
uninit() {
if (!this.#initialized) {
return;
}
lazy.IPPProxyManager.removeEventListener(
"IPPProxyManager:StateChanged",
this
);
this.#closeAllPrompts();
this.#initialized = false;
}
get localizationMessages() {
if (!this.#localizationMessages) {
const [
pausedTitle,
pausedBody,
closeTabsButton,
continueButton,
errorTitle,
errorBody,
] = lazy.ipProtectionLocalization.formatMessagesSync([
{ id: "vpn-paused-alert-title" },
{
id: "vpn-paused-alert-body",
args: { maxUsage: this.#getMaxBandwidthUsage() },
},
{ id: "vpn-paused-alert-close-tabs-button" },
{ id: "vpn-paused-alert-continue-wo-vpn-button" },
{ id: "vpn-error-alert-title" },
{ id: "vpn-error-alert-body" },
]);
this.#localizationMessages = {
pausedTitle: pausedTitle.value,
pausedBody: pausedBody.value,
closeTabsButton: closeTabsButton.value,
continueButton: continueButton.value,
errorTitle: errorTitle.value,
errorBody: errorBody.value,
};
}
return this.#localizationMessages;
}
/**
* Check usage info for the max usage, then entitlement, then default to
* BANDWIDTH.MAX_IN_GB.
*
* @returns {object} An object with max and remaining as numbers
*/
#getMaxBandwidthUsage() {
if (lazy.IPPProxyManager.usageInfo?.max != null) {
return Number(lazy.IPPProxyManager.usageInfo.max) / BANDWIDTH.BYTES_IN_GB;
} else if (lazy.IPPEnrollAndEntitleManager.entitlement?.maxBytes != null) {
// Usage info doesn't exist yet. Check the entitlement
return (
Number(lazy.IPPEnrollAndEntitleManager.entitlement?.maxBytes) /
BANDWIDTH.BYTES_IN_GB
);
}
return BANDWIDTH.MAX_IN_GB;
}
handleEvent(event) {
if (event.type !== "IPPProxyManager:StateChanged") {
return;
}
switch (event.detail.state) {
case lazy.IPPProxyStates.ACTIVE:
case lazy.IPPProxyStates.NOT_READY:
case lazy.IPPProxyStates.READY: {
this.#closeAllPrompts();
break;
}
case lazy.IPPProxyStates.PAUSED: {
this.showPausedPrompts();
break;
}
case lazy.IPPProxyStates.ERROR: {
this.showErrorPrompts();
break;
}
}
}
/**
* A helper to create a blocking prompt in all existing windows.
*
* @param {string} title The title of the prompt
* @param {string} body The body text of the prompt
* @param {string} button0 The text of the primary button
* @param {string} button1 The text of the secondary button
* @returns {Array} An array of promies. A promise will resole once one of
* buttons in the prompt are clicked.
*/
#createAllPrompts(title, body, button0, button1) {
if (this.#promptsOpen) {
return [];
}
const promises = [];
for (let window of lazy.EveryWindow.readyWindows) {
promises.push(
Services.prompt.asyncConfirmEx(
window.browsingContext,
Services.prompt.MODAL_TYPE_INTERNAL_WINDOW,
title,
body,
Ci.nsIPromptService.BUTTON_POS_0_DEFAULT |
(Ci.nsIPromptService.BUTTON_TITLE_IS_STRING *
Ci.nsIPromptService.BUTTON_POS_0) |
(Ci.nsIPromptService.BUTTON_TITLE_IS_STRING *
Ci.nsIPromptService.BUTTON_POS_1),
button0,
button1,
null,
null,
false,
{ useTitle: true }
)
);
}
this.#promptsOpen = true;
return promises;
}
/**
* Close all of the open prompts
*/
#closeAllPrompts() {
if (!this.#promptsOpen) {
return;
}
for (let window of lazy.EveryWindow.readyWindows) {
window.gDialogBox.dialog?.close();
}
this.#promptsOpen = false;
}
/**
* Show the VPN paused alert. This notifies the user that they hit the max
* bandwidth.
*
* @returns {number} The button clicked. 0 meaning continue without vpn and
* 1 meaning close all tabs.
*/
async showPausedPrompts() {
if (!lazy.IPPProxyManager.active) {
// If the proxy isn't already active, it will fail to start.
return;
}
let { pausedTitle, pausedBody, continueButton, closeTabsButton } =
this.localizationMessages;
const promises = this.#createAllPrompts(
pausedTitle,
pausedBody,
continueButton,
closeTabsButton
);
if (promises.length === 0) {
return;
}
let result = await Promise.any(promises);
let buttonClicked = result.getProperty("buttonNumClicked");
this.#handlePromptAction(buttonClicked, "paused");
}
/**
* Show the VPN error alert. This notifies the user that the VPN isn't
* working.
*
* @returns {number} The button clicked. 0 meaning continue without vpn and
* 1 meaning close all tabs.
*/
async showErrorPrompts() {
if (!lazy.IPPProxyManager.active) {
// If the proxy isn't active, no need to alert the user.
return;
}
let { errorTitle, errorBody, continueButton, closeTabsButton } =
this.localizationMessages;
const promises = this.#createAllPrompts(
errorTitle,
errorBody,
continueButton,
closeTabsButton
);
if (promises.length === 0) {
return;
}
let result = await Promise.any(promises);
let buttonClicked = result.getProperty("buttonNumClicked");
this.#handlePromptAction(buttonClicked, "error");
}
/**
* Handles the action taken by the user from the prompt.
*
* @param {number} buttonClicked Either 0 or 1.
* 0 means continue without vpn
* 1 means close all tabs
* @param {"paused"|"error"} reason Reason why the alert was triggered.
*/
#handlePromptAction(buttonClicked, reason) {
this.#closeAllPrompts();
if (buttonClicked === 0) {
lazy.IPPProxyManager.stop(true);
} else if (buttonClicked === 1) {
this.#closeAllTabs();
}
Glean.ipprotection.alertButtonClicked.record({
buttonType: buttonClicked,
reason,
});
}
async #closeAllTabs() {
const mostRecentWindow = Services.wm.getMostRecentBrowserWindow();
const tabs = mostRecentWindow.gBrowser.tabs;
mostRecentWindow.openTrustedLinkIn("about:home", "tab");
mostRecentWindow.gBrowser.removeTabs(tabs);
for (let window of lazy.EveryWindow.readyWindows) {
if (window === mostRecentWindow) {
continue;
}
window.close();
}
// Stop the VPN after closing everything to prevent any requests from
// completing.
lazy.IPPProxyManager.stop(true);
}
}
const IPProtectionAlertManager = new IPProtectionAlertManagerClass();
export { IPProtectionAlertManager };