/* 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 https://mozilla.org/MPL/2.0/. */ import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; import { html, ifDefined } from "chrome://global/content/vendor/lit.all.mjs"; import { BANDWIDTH, LINKS, } from "chrome://browser/content/ipprotection/ipprotection-constants.mjs"; const { ERRORS } = ChromeUtils.importESModule( "moz-src:///toolkit/components/ipprotection/IPPProxyManager.sys.mjs" ); // eslint-disable-next-line import/no-unassigned-import import "chrome://browser/content/ipprotection/ipprotection-message-bar.mjs"; // eslint-disable-next-line import/no-unassigned-import import "chrome://browser/content/ipprotection/ipprotection-unauthenticated.mjs"; // eslint-disable-next-line import/no-unassigned-import import "chrome://browser/content/ipprotection/ipprotection-status-card.mjs"; // eslint-disable-next-line import/no-unassigned-import import "chrome://browser/content/ipprotection/ipprotection-status-box.mjs"; // eslint-disable-next-line import/no-unassigned-import import "chrome://global/content/elements/moz-toggle.mjs"; /** * Custom element that implements a message bar and status card for IP protection. */ export default class IPProtectionContentElement extends MozLitElement { static queries = { unauthenticatedEl: "ipprotection-unauthenticated", messagebarEl: "ipprotection-message-bar", statusCardEl: "ipprotection-status-card", upgradeEl: "#upgrade-vpn-content", activeSubscriptionEl: "#active-subscription-vpn-content", supportLinkEl: "#vpn-support-link", statusBoxEl: "ipprotection-status-box", siteExclusionControlEl: "#site-exclusion-control", siteExclusionToggleEl: "#site-exclusion-toggle", settingsButtonEl: "#vpn-settings-button", }; static properties = { state: { type: Object, attribute: false }, _showMessageBar: { type: Boolean, state: true }, _messageDismissed: { type: Boolean, state: true }, }; constructor() { super(); this.state = {}; this.messageBarListener = this.#messageBarListener.bind(this); this.statusCardListener = this.#statusCardListener.bind(this); this._showMessageBar = false; this._messageDismissed = false; } connectedCallback() { super.connectedCallback(); this.dispatchEvent(new CustomEvent("IPProtection:Init", { bubbles: true })); this.addEventListener( "ipprotection-status-card:user-toggled-on", this.#statusCardListener ); this.addEventListener( "ipprotection-status-card:user-toggled-off", this.#statusCardListener ); this.addEventListener( "ipprotection-message-bar:user-dismissed", this.#messageBarListener ); } disconnectedCallback() { super.disconnectedCallback(); this.removeEventListener( "ipprotection-status-card:user-toggled-on", this.#statusCardListener ); this.removeEventListener( "ipprotection-status-card:user-toggled-off", this.#statusCardListener ); this.removeEventListener( "ipprotection-message-bar:user-dismissed", this.#messageBarListener ); } get canEnableConnection() { return this.state && this.state.isProtectionEnabled && !this.state.error; } get hasSiteExclusion() { return this.state?.siteData?.isExclusion ?? false; } get #hasErrors() { return !this.state || !!this.state.error; } handleClickSupportLink(event) { const win = event.target.ownerGlobal; if (event.target === this.supportLinkEl) { event.preventDefault(); win.openWebLinkIn(LINKS.PRODUCT_URL, "tab"); this.dispatchEvent( new CustomEvent("IPProtection:Close", { bubbles: true }) ); } } handleUpgrade(event) { const win = event.target.ownerGlobal; win.openWebLinkIn(LINKS.PRODUCT_URL + "#pricing", "tab"); // Close the panel this.dispatchEvent( new CustomEvent("IPProtection:ClickUpgrade", { bubbles: true }) ); Glean.ipprotection.clickUpgradeButton.record(); } focus() { if (this.state.unauthenticated) { this.unauthenticatedEl?.focus(); } else { this.statusCardEl?.focus(); } } #statusCardListener(event) { if (event.type === "ipprotection-status-card:user-toggled-on") { this.dispatchEvent( new CustomEvent("IPProtection:UserEnable", { bubbles: true }) ); } else if (event.type === "ipprotection-status-card:user-toggled-off") { this.dispatchEvent( new CustomEvent("IPProtection:UserDisable", { bubbles: true }) ); } } #messageBarListener(event) { if (event.type === "ipprotection-message-bar:user-dismissed") { this._showMessageBar = false; this._messageDismissed = true; this.state.error = ""; if (this.state.bandwidthWarning) { const threshold = Services.prefs.getIntPref( "browser.ipProtection.bandwidthThreshold", 0 ); this.dispatchEvent( new CustomEvent("IPProtection:DismissBandwidthWarning", { bubbles: true, composed: true, detail: { threshold }, }) ); } } } handleToggleUseVPN(event) { let isEnabled = event.target.pressed; if (isEnabled) { this.dispatchEvent( new CustomEvent("IPProtection:UserEnableVPNForSite", { bubbles: true, }) ); } else { this.dispatchEvent( new CustomEvent("IPProtection:UserDisableVPNForSite", { bubbles: true, composed: true, }) ); } } handleClickSettingsButton(event) { event.preventDefault(); const win = event.target.ownerGlobal; win.openPreferences("privacy-vpn"); this.dispatchEvent( new CustomEvent("IPProtection:Close", { bubbles: true, composed: true }) ); } updated(changedProperties) { super.updated(changedProperties); // Clear messages when there is an error. if (this.state.error) { this._messageDismissed = false; } } messageBarTemplate() { let messageId; let messageLink; let messageLinkl10nId; let messageLinkL10nArgs; let messageType = "info"; if (this.state.bandwidthWarning && this.state.bandwidthUsage) { messageId = "ipprotection-message-bandwidth-warning"; messageType = "warning"; const bandwidthRemaining = this.state.bandwidthUsage.remaining / BANDWIDTH.BYTES_IN_GB; const maxUsage = this.state.bandwidthUsage.max / BANDWIDTH.BYTES_IN_GB; const pctUsed = (100 * (maxUsage - bandwidthRemaining)) / maxUsage; let usageLeft = Math.round(bandwidthRemaining); if (pctUsed >= 75 && pctUsed < 90) { usageLeft = bandwidthRemaining.toFixed(1); } else if (bandwidthRemaining < 1) { messageId = "ipprotection-message-bandwidth-warning-mb"; usageLeft = Math.floor( this.state.bandwidthUsage.remaining / BANDWIDTH.BYTES_IN_MB ); } messageLinkL10nArgs = JSON.stringify({ usageLeft, maxUsage, }); } else if (this.state.onboardingMessage) { messageId = this.state.onboardingMessage; messageType = "info"; switch (this.state.onboardingMessage) { case "ipprotection-message-continuous-onboarding-intro": break; case "ipprotection-message-continuous-onboarding-autostart": messageLink = "about:settings#privacy"; messageLinkl10nId = "setting-link"; break; case "ipprotection-message-continuous-onboarding-site-settings": messageLink = "about:settings#privacy"; messageLinkl10nId = "setting-link"; break; } } return html` `; } statusCardTemplate() { let hasExclusion = this.hasSiteExclusion; return html` `; } upgradeTemplate() { if (this.state.hasUpgraded) { return null; } return html`

`; } errorTemplate() { const isNetworkError = this.state.error === ERRORS.NETWORK; const headerL10nId = isNetworkError ? "ipprotection-connection-status-network-error-title" : "ipprotection-connection-status-generic-error-title"; const descriptionL10nId = isNetworkError ? "ipprotection-connection-status-network-error-description" : "ipprotection-connection-status-generic-error-description"; const errorType = isNetworkError ? ERRORS.NETWORK : ERRORS.GENERIC; return html` ${isNetworkError ? html` ` : null} `; } pausedTemplate() { return html` ${this.upgradeTemplate()} `; } exclusionToggleTemplate() { if ( !this.state.isSiteExceptionsEnabled || !this.state.siteData || !this.state.isProtectionEnabled || this.#hasErrors ) { return null; } const hasExclusion = this.hasSiteExclusion; const siteExclusionToggleStateL10nId = hasExclusion ? "site-exclusion-toggle-disabled-1" : "site-exclusion-toggle-enabled-1"; return html`
`; } footerTemplate() { return html`
>
`; } enrollingTemplate() { return html`
`; } mainContentTemplate() { if (this.state.isCheckingEntitlement) { return html`${this.enrollingTemplate()} ${this.footerTemplate()}`; } if (this.state.unauthenticated) { return html` `; } if (this.#hasErrors) { return html` ${this.errorTemplate()}${this.footerTemplate()}`; } if (this.state.paused) { return html` ${this.pausedTemplate()} ${this.footerTemplate()}`; } return html` ${this.statusCardTemplate()} ${this.exclusionToggleTemplate()} ${this.footerTemplate()} `; } render() { if ( (this.state.onboardingMessage || this.state.bandwidthWarning) && !this._messageDismissed && !this.state.unauthenticated ) { this._showMessageBar = true; } else if (!this.state.onboardingMessage && !this.state.bandwidthWarning) { // Remove the message bar if we can no longer render messages before they were dismissed this._showMessageBar = false; } const messageBar = this._showMessageBar ? this.messageBarTemplate() : null; let content = html`${messageBar}${this.mainContentTemplate()}`; // TODO: Conditionally render post-upgrade subview within #ipprotection-content-wrapper - Bug 1973813 return html`
${content}
`; } } customElements.define("ipprotection-content", IPProtectionContentElement);