tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 1958418815e0d75db116bb8db85bfad3d2d0cef4
parent 8debb303c7a418f40bb1a3474f12cd1032988967
Author: Narcis Beleuzu <nbeleuzu@mozilla.com>
Date:   Sat, 15 Nov 2025 01:58:12 +0200

Revert "Bug 1997411 — add a new site settings button in status card r=ip-protection-reviewers,fluent-reviewers,rking,bolsson" for causing bc failure on browser_all_files_referenced.js

This reverts commit 870365b83c1953a2e3fdab88423b2826fb58e6b7.

This reverts commit 861a4f1e9468f8a99ad7e4ff2c08cacbec3371e4.

Diffstat:
Mbrowser/components/ipprotection/IPProtectionPanel.sys.mjs | 7-------
Mbrowser/components/ipprotection/content/ipprotection-content.mjs | 39++++++++++++++++++---------------------
Abrowser/components/ipprotection/content/ipprotection-flag.css | 17+++++++++++++++++
Abrowser/components/ipprotection/content/ipprotection-flag.mjs | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dbrowser/components/ipprotection/content/ipprotection-site-settings-control.css | 18------------------
Dbrowser/components/ipprotection/content/ipprotection-site-settings-control.mjs | 90-------------------------------------------------------------------------------
Mbrowser/components/ipprotection/content/ipprotection-status-card.css | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mbrowser/components/ipprotection/content/ipprotection-status-card.mjs | 180+++++++++++++++++++++++++++++++++----------------------------------------------
Mbrowser/components/ipprotection/jar.mn | 4++--
Mbrowser/components/ipprotection/tests/browser/browser.toml | 4++--
Abrowser/components/ipprotection/tests/browser/browser_ipprotection_flag.js | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Dbrowser/components/ipprotection/tests/browser/browser_ipprotection_site_settings_control.js | 184-------------------------------------------------------------------------------
Mbrowser/components/ipprotection/tests/browser/browser_ipprotection_status_card.js | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mbrowser/locales-preview/ipProtection.ftl | 56++++++++++++++++++++------------------------------------
14 files changed, 382 insertions(+), 493 deletions(-)

diff --git a/browser/components/ipprotection/IPProtectionPanel.sys.mjs b/browser/components/ipprotection/IPProtectionPanel.sys.mjs @@ -321,7 +321,6 @@ export class IPProtectionPanel { doc.addEventListener("IPProtection:UserDisable", this.handleEvent); doc.addEventListener("IPProtection:ShowHelpPage", this.handleEvent); doc.addEventListener("IPProtection:SignIn", this.handleEvent); - doc.addEventListener("IPProtection:UserShowSiteSettings", this.handleEvent); } #removePanelListeners(doc) { @@ -332,10 +331,6 @@ export class IPProtectionPanel { doc.removeEventListener("IPProtection:UserDisable", this.handleEvent); doc.removeEventListener("IPProtection:ShowHelpPage", this.handleEvent); doc.removeEventListener("IPProtection:SignIn", this.handleEvent); - doc.removeEventListener( - "IPProtection:UserShowSiteSettings", - this.handleEvent - ); } #addProxyListeners() { @@ -404,8 +399,6 @@ export class IPProtectionPanel { hasUpgraded: lazy.IPPEnrollAndEntitleManager.hasUpgraded, error: hasError ? ERRORS.GENERIC : "", }); - } else if (event.type == "IPProtection:UserShowSiteSettings") { - // TODO: show subview for site settings (Bug 1997413) } } } diff --git a/browser/components/ipprotection/content/ipprotection-content.mjs b/browser/components/ipprotection/content/ipprotection-content.mjs @@ -12,6 +12,8 @@ import { // eslint-disable-next-line import/no-unassigned-import import "chrome://browser/content/ipprotection/ipprotection-header.mjs"; // eslint-disable-next-line import/no-unassigned-import +import "chrome://browser/content/ipprotection/ipprotection-flag.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-signedout.mjs"; @@ -47,7 +49,7 @@ export default class IPProtectionContentElement extends MozLitElement { this.keyListener = this.#keyListener.bind(this); this.messageBarListener = this.#messageBarListener.bind(this); - this.statusCardListener = this.#statusCardListener.bind(this); + this.toggleListener = this.#toggleEventListener.bind(this); this._showMessageBar = false; this._messageDismissed = false; } @@ -58,15 +60,11 @@ export default class IPProtectionContentElement extends MozLitElement { this.addEventListener("keydown", this.keyListener, { capture: true }); this.addEventListener( "ipprotection-status-card:user-toggled-on", - this.#statusCardListener + this.#toggleEventListener ); this.addEventListener( "ipprotection-status-card:user-toggled-off", - this.#statusCardListener - ); - this.addEventListener( - "ipprotection-site-settings-control:click", - this.#statusCardListener + this.#toggleEventListener ); this.addEventListener( "ipprotection-message-bar:user-dismissed", @@ -80,15 +78,11 @@ export default class IPProtectionContentElement extends MozLitElement { this.removeEventListener("keydown", this.keyListener, { capture: true }); this.removeEventListener( "ipprotection-status-card:user-toggled-on", - this.#statusCardListener + this.#toggleEventListener ); this.removeEventListener( "ipprotection-status-card:user-toggled-off", - this.#statusCardListener - ); - this.removeEventListener( - "ipprotection-site-settings-control:click", - this.#statusCardListener + this.#toggleEventListener ); this.removeEventListener( "ipprotection-message-bar:user-dismissed", @@ -168,7 +162,7 @@ export default class IPProtectionContentElement extends MozLitElement { } } - #statusCardListener(event) { + #toggleEventListener(event) { if (event.type === "ipprotection-status-card:user-toggled-on") { this.dispatchEvent( new CustomEvent("IPProtection:UserEnable", { bubbles: true }) @@ -177,10 +171,6 @@ export default class IPProtectionContentElement extends MozLitElement { this.dispatchEvent( new CustomEvent("IPProtection:UserDisable", { bubbles: true }) ); - } else if (event.type === "ipprotection-site-settings-control:click") { - this.dispatchEvent( - new CustomEvent("IPProtection:UserShowSiteSettings", { bubbles: true }) - ); } } @@ -236,16 +226,23 @@ export default class IPProtectionContentElement extends MozLitElement { `; } + descriptionTemplate() { + return this.state.location + ? html` + <ipprotection-flag + .location=${this.state.location} + ></ipprotection-flag> + ` + : null; + } + statusCardTemplate() { - // TODO: Pass site information to status-card to conditionally - // render the site settings control. (Bug 1997412) return html` <ipprotection-status-card .protectionEnabled=${this.canEnableConnection} .canShowTime=${this.canShowConnectionTime} .enabledSince=${this.state.protectionEnabledSince} .location=${this.state.location} - .siteData=${ifDefined(this.state.siteData)} ></ipprotection-status-card> `; } diff --git a/browser/components/ipprotection/content/ipprotection-flag.css b/browser/components/ipprotection/content/ipprotection-flag.css @@ -0,0 +1,17 @@ +/* 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 "chrome://global/skin/global.css"; + +#flag-wrapper { + display: flex; + align-items: center; + gap: var(--space-small); +} + +#location-icon { + height: var(--icon-size); + width: var(--icon-size); + pointer-events: none; +} diff --git a/browser/components/ipprotection/content/ipprotection-flag.mjs b/browser/components/ipprotection/content/ipprotection-flag.mjs @@ -0,0 +1,66 @@ +/* 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 } from "chrome://global/content/vendor/lit.all.mjs"; +import { FLAGS } from "chrome://browser/content/ipprotection/ipprotection-constants.mjs"; + +/** + * A custom element that handles the display of flag icons. + */ +export default class IPProtectionFlagElement extends MozLitElement { + static properties = { + location: { type: Object }, + _iconSrc: { type: String, state: true }, + _name: { type: String, state: true }, + }; + + constructor() { + super(); + } + + get #hasValidLocation() { + return this.location && this.location.name && this.location.code; + } + + #getFlagIcon() { + const iconName = this.location.code; + + if (!Object.hasOwn(FLAGS, iconName)) { + return null; + } + + return FLAGS[iconName]; + } + + locationDescriptionTemplate() { + return html` + <img id="location-icon" src=${this._iconSrc} /> + <span id="location-name">${this._name}</span> + `; + } + + render() { + if (!this.#hasValidLocation) { + return null; + } + + this._name = this.location.name; + this._iconSrc = this.#getFlagIcon(); + + let locationDescription = this._iconSrc + ? this.locationDescriptionTemplate() + : null; + + return html` + <link + rel="stylesheet" + href="chrome://browser/content/ipprotection/ipprotection-flag.css" + /> + <div id="flag-wrapper">${locationDescription}</div> + `; + } +} + +customElements.define("ipprotection-flag", IPProtectionFlagElement); diff --git a/browser/components/ipprotection/content/ipprotection-site-settings-control.css b/browser/components/ipprotection/content/ipprotection-site-settings-control.css @@ -1,18 +0,0 @@ -/* 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 "chrome://global/skin/global.css"; - -#site-settings-label { - --box-border-radius-start: initial; -} - -.site-settings { - --box-group-border: none; - background-color: var(--background-color-box); -} - -.exception-enabled { - --box-icon-fill: var(--icon-color-success); -} diff --git a/browser/components/ipprotection/content/ipprotection-site-settings-control.mjs b/browser/components/ipprotection/content/ipprotection-site-settings-control.mjs @@ -1,90 +0,0 @@ -/* 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, classMap } from "chrome://global/content/vendor/lit.all.mjs"; - -/** - * Custom element that implements a button for site settings in the panel. - */ -export default class IPPSiteSettingsControl extends MozLitElement { - CLICK_EVENT = "ipprotection-site-settings-control:click"; - - static properties = { - site: { type: String }, - exceptionEnabled: { type: Boolean }, - }; - - constructor() { - super(); - - this.site = null; - this.exceptionEnabled = false; - } - - get iconsrc() { - if (!this.exceptionEnabled) { - return "chrome://global/skin/icons/close-fill.svg"; - } - return "chrome://global/skin/icons/check-filled.svg"; - } - - get descriptionL10n() { - if (!this.exceptionEnabled) { - return "ipprotection-site-settings-button-vpn-off"; - } - return "ipprotection-site-settings-button-vpn-on"; - } - - handleClickSettings(event) { - event.preventDefault(); - - this.dispatchEvent( - new CustomEvent(this.CLICK_EVENT, { - bubbles: true, - composed: true, - }) - ); - } - - render() { - if (!this.site) { - return null; - } - - let icon = this.iconsrc; - let descriptionL10n = this.descriptionL10n; - let l10nArgs = JSON.stringify({ sitename: this.site }); - - return html` - <link - rel="stylesheet" - href="chrome://browser/content/ipprotection/ipprotection-site-settings-control.css" - /> - <moz-box-group> - <moz-box-item - slot="header" - id="site-settings-label" - class="site-settings" - data-l10n-id="ipprotection-site-settings-control" - ></moz-box-item> - <moz-box-button - @click=${this.handleClickSettings} - class=${classMap({ - "site-settings": true, - "exception-enabled": this.exceptionEnabled, - })} - data-l10n-id=${descriptionL10n} - data-l10n-args=${l10nArgs} - iconsrc=${icon} - ></moz-box-button> - </moz-box-group> - `; - } -} - -customElements.define( - "ipprotection-site-settings-control", - IPPSiteSettingsControl -); diff --git a/browser/components/ipprotection/content/ipprotection-status-card.css b/browser/components/ipprotection/content/ipprotection-status-card.css @@ -5,7 +5,14 @@ @import "chrome://global/skin/global.css"; :host { - --border-color: var(--border-color-card); + --status-card-connected-background-color: var(--color-violet-90); + --status-card-connected-text-color: var(--color-white); + --connection-globe-icon-size: 48px; + --connection-rings-extra-space-x: 23px; + --connection-rings-extra-space-y: 23px; + --connection-shield-color: var(--panel-background); + --connection-shield-off-background-color: var(--icon-color-critical); + --connection-shield-on-background-color: var(--color-accent-primary); } #ipprotection-content-wrapper { @@ -15,6 +22,11 @@ padding-block: var(--panel-subview-body-padding-block); } +.vpn-top-content { + margin-inline: var(--space-large); + margin-block-start: var(--space-small); +} + .vpn-status-group { display: block; position: relative; @@ -23,18 +35,76 @@ } .is-enabled { - background-color: var(--background-color-success); + color: var(--status-card-connected-text-color); + background-color: var(--status-card-connected-background-color); } #status-card { + --shield-bgcolor: var(--connection-shield-off-background-color); + --box-icon-size: var(--connection-globe-icon-size); + --box-icon-fill: var(--connection-shield-color); + --box-icon-stroke: var(--shield-bgcolor); --box-label-font-weight: var(--font-weight-semibold); -moz-context-properties: fill, stroke; + /** + * moz-box-group changes the border of items based on its position in the group. + * Undo it to preserve the border radius even though the animation rings are + * loaded first in the group. + */ + --box-border-radius-start: initial; + fill: var(--connection-shield-color); + stroke: var(--shield-bgcolor); + + &.is-enabled { + --shield-bgcolor: var(--connection-shield-on-background-color); + } } #connection-toggle { + --toggle-width: var(--size-item-xlarge); + --toggle-height: var(--size-item-medium); + --toggle-dot-margin: 2px; margin-inline-end: var(--space-small); } -#site-settings { - --box-group-border: none; +#status-card-animation { + display: block; + position: absolute; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 1; +} + +#animation-rings { + display: block; + width: 100%; + height: 100%; + contain: strict; + /** + * A hacky attempt at centering the rings with the globe icon. + * It makes some assumptions about the icon size and padding pixels used in moz-box-item. + * The rings are 200px in height and width, so they have a radius of 100px. + * Additional px gap is merely for alignment and not tied to any element. + * TODO: (Bug 1981251) See if we can better calculate the center coords. + */ + background-position-x: calc(-100px + var(--connection-globe-icon-size) + var(--space-large) - var(--connection-rings-extra-space-x)); + background-position-y: calc(-100px + var(--connection-globe-icon-size) + var(--space-large) - var(--connection-rings-extra-space-y)); + background-image: url("chrome://browser/content/ipprotection/assets/rings.svg"); + background-repeat: no-repeat; + + &:dir(rtl) { + --connection-rings-extra-space-x: 4.5px; + /* Shift by approximately 100% of the element's width to move the rings to the opposite side. */ + background-position-x: calc(100% + var(--connection-globe-icon-size) + var(--space-large) + var(--connection-rings-extra-space-x)); + } + + @media (prefers-reduced-motion: reduce) { + display: none; + } +} + +#location-wrapper { + --box-icon-fill: currentColor; + --box-label-font-weight: var(--font-weight-semibold); } diff --git a/browser/components/ipprotection/content/ipprotection-status-card.mjs b/browser/components/ipprotection/content/ipprotection-status-card.mjs @@ -3,11 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; -import { - html, - classMap, - styleMap, -} from "chrome://global/content/vendor/lit.all.mjs"; +import { html, classMap } from "chrome://global/content/vendor/lit.all.mjs"; import { connectionTimer, defaultTimeValue, @@ -16,22 +12,19 @@ import { // eslint-disable-next-line import/no-unassigned-import import "chrome://browser/content/ipprotection/ipprotection-header.mjs"; // eslint-disable-next-line import/no-unassigned-import -import "chrome://global/content/elements/moz-toggle.mjs"; +import "chrome://browser/content/ipprotection/ipprotection-flag.mjs"; // eslint-disable-next-line import/no-unassigned-import -import "chrome://browser/content/ipprotection/ipprotection-site-settings-control.mjs"; +import "chrome://global/content/elements/moz-toggle.mjs"; /** * Custom element that implements a status card for IP protection. */ export default class IPProtectionStatusCard extends MozLitElement { - TOGGLE_ON_EVENT = "ipprotection-status-card:user-toggled-on"; - TOGGLE_OFF_EVENT = "ipprotection-status-card:user-toggled-off"; - static queries = { statusGroupEl: "#status-card", + animationEl: "#status-card-animation", connectionToggleEl: "#connection-toggle", locationEl: "#location-wrapper", - siteSettingsEl: "ipprotection-site-settings-control", }; static shadowRootOptions = { @@ -44,9 +37,9 @@ export default class IPProtectionStatusCard extends MozLitElement { canShowTime: { type: Boolean }, enabledSince: { type: Object }, location: { type: Object }, - siteData: { type: Object }, + _showAnimation: { type: Boolean, state: true }, // Track toggle state separately so that we can tell when the toggle - // is enabled because of the existing protection state or because of user action. + // is enabled because of existing protection state or because of user action. _toggleEnabled: { type: Boolean, state: true }, }; @@ -54,6 +47,7 @@ export default class IPProtectionStatusCard extends MozLitElement { super(); this.keyListener = this.#keyListener.bind(this); + this._showAnimation = false; } connectedCallback() { @@ -73,14 +67,14 @@ export default class IPProtectionStatusCard extends MozLitElement { if (isEnabled) { this.dispatchEvent( - new CustomEvent(this.TOGGLE_ON_EVENT, { + new CustomEvent("ipprotection-status-card:user-toggled-on", { bubbles: true, composed: true, }) ); } else { this.dispatchEvent( - new CustomEvent(this.TOGGLE_OFF_EVENT, { + new CustomEvent("ipprotection-status-card:user-toggled-off", { bubbles: true, composed: true, }) @@ -132,111 +126,89 @@ export default class IPProtectionStatusCard extends MozLitElement { // (eg. error thrown), unset the toggle. this._toggleEnabled = false; } + + /** + * Don't show animations until all elements are connected and layout is fully drawn. + * This will allow us to best position our animation component with the globe icon + * based on the most up to date status card dimensions. + */ + if (this.protectionEnabled) { + this._showAnimation = true; + } else { + this._showAnimation = false; + } } - cardContentTemplate() { + descriptionTemplate() { + return this.location + ? html` + <ipprotection-flag .location=${this.location}></ipprotection-flag> + ` + : null; + } + + animationRingsTemplate() { + return html` <div id="status-card-animation"> + <div id="animation-rings"></div> + </div>`; + } + + alphaCardTemplate() { const statusCardL10nId = this.protectionEnabled ? "ipprotection-connection-status-on" : "ipprotection-connection-status-off"; const toggleL10nId = this.protectionEnabled ? "ipprotection-toggle-active" : "ipprotection-toggle-inactive"; + const statusIcon = this.protectionEnabled + ? "chrome://browser/content/ipprotection/assets/ipprotection-connection-on.svg" + : "chrome://browser/content/ipprotection/assets/ipprotection-connection-off.svg"; - const siteSettingsTemplate = this.protectionEnabled - ? this.siteSettingsTemplate() - : null; - - return html` <link - rel="stylesheet" - href="chrome://browser/content/ipprotection/ipprotection-status-card.css" - /> - <moz-box-group class="vpn-status-group"> - <moz-box-item - id="status-card" - class=${classMap({ - "is-enabled": this.protectionEnabled, - })} - layout="default" - data-l10n-id=${statusCardL10nId} - .description=${this.cardDescriptionTemplate()} - > - <moz-toggle - id="connection-toggle" - data-l10n-id=${toggleL10nId} - @click=${this.handleToggleConnect} - ?pressed=${this._toggleEnabled} - slot="actions" - ></moz-toggle> - </moz-box-item> - ${siteSettingsTemplate} - </moz-box-group>`; - } - - siteSettingsTemplate() { - // TODO: Once we're able to detect the current site and its exception status, show - // ipprotection-site-settings-control (Bug 1997412). - if (!this.siteData?.siteName) { - return null; - } - - return html` <moz-box-item - id="site-settings" - class=${classMap({ - "is-enabled": this.protectionEnabled, - })} - > - <ipprotection-site-settings-control - .site=${this.siteData.siteName} - .exceptionEnabled=${this.siteData.isException} - class="slotted" - ></ipprotection-site-settings-control> - </moz-box-item>`; - } - - cardDescriptionTemplate() { - // The template consists of location name and connection time. let time = this.canShowTime ? connectionTimer(this.enabledSince) : defaultTimeValue; - // To work around mox-box-item description elements being hard to reach because of the shadowDOM, - // let's use a lit stylemap to apply style changes directly. - let labelStyles = styleMap({ - display: "flex", - gap: "var(--space-small)", - }); - let imgStyles = styleMap({ - "-moz-context-properties": "fill", - fill: "currentColor", - }); - - return this.location - ? html` - <div id="vpn-details"> - <div - id="location-label" - data-l10n-id="ipprotection-location-title" - style=${labelStyles} - > - <span>${this.location.name}</span> - <img - src="chrome://global/skin/icons/info.svg" - style=${imgStyles} - /> - </div> - <span - id="time" - data-l10n-id="ipprotection-connection-time" - data-l10n-args=${time} - ></span> - </div> - ` - : null; + return html` <moz-box-group class="vpn-status-group"> + ${this._showAnimation ? this.animationRingsTemplate() : null} + <moz-box-item + id="status-card" + class=${classMap({ + "is-enabled": this.protectionEnabled, + })} + layout="large-icon" + iconsrc=${statusIcon} + data-l10n-id=${statusCardL10nId} + data-l10n-args=${time} + > + <moz-toggle + id="connection-toggle" + data-l10n-id=${toggleL10nId} + @click=${this.handleToggleConnect} + ?pressed=${this._toggleEnabled} + slot="actions" + ></moz-toggle> + </moz-box-item> + <moz-box-item + id="location-wrapper" + class=${classMap({ + "is-enabled": this.protectionEnabled, + })} + iconsrc="chrome://global/skin/icons/info.svg" + data-l10n-id="ipprotection-location-title" + .description=${this.descriptionTemplate()} + > + </moz-box-item> + </moz-box-group>`; } render() { - let content = this.cardContentTemplate(); - return html`${content}`; + return html` + <link + rel="stylesheet" + href="chrome://browser/content/ipprotection/ipprotection-status-card.css" + /> + ${this.alphaCardTemplate()} + `; } } diff --git a/browser/components/ipprotection/jar.mn b/browser/components/ipprotection/jar.mn @@ -11,10 +11,10 @@ browser.jar: content/browser/ipprotection/ipprotection-customelements.js (content/ipprotection-customelements.js) content/browser/ipprotection/ipprotection-header.css (content/ipprotection-header.css) content/browser/ipprotection/ipprotection-header.mjs (content/ipprotection-header.mjs) + content/browser/ipprotection/ipprotection-flag.css (content/ipprotection-flag.css) + content/browser/ipprotection/ipprotection-flag.mjs (content/ipprotection-flag.mjs) content/browser/ipprotection/ipprotection-message-bar.mjs (content/ipprotection-message-bar.mjs) content/browser/ipprotection/ipprotection-signedout.mjs (content/ipprotection-signedout.mjs) - content/browser/ipprotection/ipprotection-site-settings-control.css (content/ipprotection-site-settings-control.css) - content/browser/ipprotection/ipprotection-site-settings-control.mjs (content/ipprotection-site-settings-control.mjs) content/browser/ipprotection/ipprotection-status-card.css (content/ipprotection-status-card.css) content/browser/ipprotection/ipprotection-status-card.mjs (content/ipprotection-status-card.mjs) content/browser/ipprotection/ipprotection-timer.mjs (content/ipprotection-timer.mjs) diff --git a/browser/components/ipprotection/tests/browser/browser.toml b/browser/components/ipprotection/tests/browser/browser.toml @@ -22,6 +22,8 @@ prefs = [ ["browser_ipprotection_content_signedout.js"] +["browser_ipprotection_flag.js"] + ["browser_ipprotection_header.js"] ["browser_ipprotection_keyboard_navigation.js"] @@ -34,8 +36,6 @@ prefs = [ ["browser_ipprotection_proxy_errors.js"] -["browser_ipprotection_site_settings_control.js"] - ["browser_ipprotection_status_card.js"] ["browser_ipprotection_telemetry.js"] diff --git a/browser/components/ipprotection/tests/browser/browser_ipprotection_flag.js b/browser/components/ipprotection/tests/browser/browser_ipprotection_flag.js @@ -0,0 +1,50 @@ +/* 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/. */ + +"use strict"; + +/** + * Tests that ipprotection-flag has the correct content. + */ +add_task(async function test_flags_content() { + const mockLocation = { + name: "United States", + code: "us", + }; + + let content = await openPanel({ isSignedOut: false }); + + let cardLoadedPromise = BrowserTestUtils.waitForMutationCondition( + content.shadowRoot, + { childList: true, subtree: true }, + () => content.statusCardEl + ); + + await setPanelState({ + isSignedOut: false, + location: mockLocation, + }); + await cardLoadedPromise; + + let statusCard = content.statusCardEl; + + Assert.ok(statusCard.locationEl, "Location details should be present"); + + let flag = + statusCard.locationEl?.shadowRoot.querySelector("ipprotection-flag"); + + Assert.ok(flag, "Flag component should be present"); + + let icon = flag.shadowRoot.getElementById("location-icon"); + let name = flag.shadowRoot.getElementById("location-name"); + + Assert.ok(icon, "Location flag icon should be present"); + Assert.equal( + name.textContent, + mockLocation.name, + "Location name should be correct" + ); + + await closePanel(); +}); diff --git a/browser/components/ipprotection/tests/browser/browser_ipprotection_site_settings_control.js b/browser/components/ipprotection/tests/browser/browser_ipprotection_site_settings_control.js @@ -1,184 +0,0 @@ -/* 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/. */ - -"use strict"; - -const MOCK_SITE_NAME = "example.com"; - -/** - * Tests that we can show ipprotection-site-settings-control when VPN is on. - */ -add_task( - async function test_site_settings_control_visible_with_VPN_on_and_site_data() { - let content = await openPanel({ - isSignedOut: false, - isProtectionEnabled: false, - siteData: { - siteName: MOCK_SITE_NAME, - isException: false, - }, - }); - - Assert.ok( - BrowserTestUtils.isVisible(content), - "ipprotection content component should be present" - ); - - let statusCard = content.statusCardEl; - Assert.ok( - content.statusCardEl, - "ipprotection-status-card should be present" - ); - - // VPN is off, no site settings - Assert.ok( - !statusCard.siteSettingsEl, - "ipprotection-site-settings-control should not be present with VPN off" - ); - - let siteSettingsVisiblePromise = BrowserTestUtils.waitForMutationCondition( - statusCard.shadowRoot, - { childList: true, subtree: true }, - () => statusCard.siteSettingsEl - ); - - // Turn VPN on - await setPanelState({ - isSignedOut: false, - isProtectionEnabled: true, - siteData: { - siteName: MOCK_SITE_NAME, - isException: false, - }, - }); - - await Promise.all([statusCard.updateComplete, siteSettingsVisiblePromise]); - - Assert.ok( - statusCard.siteSettingsEl, - "Now ipprotection-site-settings-control should be present with VPN on" - ); - - await closePanel(); - } -); - -/** - * Tests that we don't show ipprotection-site-settings-control when there's no siteData. - */ -add_task( - async function test_site_settings_control_hidden_with_VPN_on_and_no_site_data() { - let content = await openPanel({ - isSignedOut: false, - isProtectionEnabled: true, - siteData: undefined, - }); - - Assert.ok( - BrowserTestUtils.isVisible(content), - "ipprotection content component should be present" - ); - - let statusCard = content.statusCardEl; - Assert.ok( - content.statusCardEl, - "ipprotection-status-card should be present" - ); - Assert.ok( - !statusCard.siteSettingsEl, - "ipprotection-site-settings-control should not be present because there's no site data" - ); - - await closePanel(); - } -); - -/** - * Tests that we don't show ipprotection-site-settings-control when an error occurs. - */ -add_task(async function test_site_settings_control_hidden_with_VPN_error() { - let content = await openPanel({ - isSignedOut: false, - isProtectionEnabled: true, - siteData: { - siteName: MOCK_SITE_NAME, - isException: false, - }, - }); - - Assert.ok( - BrowserTestUtils.isVisible(content), - "ipprotection content component should be present" - ); - - let statusCard = content.statusCardEl; - Assert.ok(content.statusCardEl, "ipprotection-status-card should be present"); - Assert.ok( - statusCard.siteSettingsEl, - "ipprotection-site-settings-control should be present" - ); - - let siteSettingsNotVisiblePromise = BrowserTestUtils.waitForMutationCondition( - statusCard.shadowRoot, - { childList: true, subtree: true }, - () => !statusCard.siteSettingsEl - ); - - // Turn VPN on - await setPanelState({ - isSignedOut: false, - isProtectionEnabled: true, - error: "generic-error", - }); - - await Promise.all([statusCard.updateComplete, siteSettingsNotVisiblePromise]); - - Assert.ok( - !statusCard.siteSettingsEl, - "Now ipprotection-site-settings-control should not be present due to an error" - ); - - await closePanel(); -}); - -/** - * Tests that we dispatch expected events when we select the button - * in ipprotection-site-settings-control. - */ -add_task(async function test_site_settings_control_click_event() { - let content = await openPanel({ - isSignedOut: false, - isProtectionEnabled: true, - siteData: { - siteName: MOCK_SITE_NAME, - isException: false, - }, - }); - - Assert.ok( - BrowserTestUtils.isVisible(content), - "ipprotection content component should be present" - ); - - let statusCard = content.statusCardEl; - Assert.ok(content.statusCardEl, "ipprotection-status-card should be present"); - Assert.ok( - statusCard.siteSettingsEl, - "ipprotection-site-settings-control should be present" - ); - - let button = - statusCard.siteSettingsEl.shadowRoot.querySelector("moz-box-button"); - let eventPromise = BrowserTestUtils.waitForEvent( - window, - "IPProtection:UserShowSiteSettings" - ); - - button.click(); - - await eventPromise; - Assert.ok("Event was dispatched after clicking site settings button"); - - await closePanel(); -}); diff --git a/browser/components/ipprotection/tests/browser/browser_ipprotection_status_card.js b/browser/components/ipprotection/tests/browser/browser_ipprotection_status_card.js @@ -16,7 +16,7 @@ ChromeUtils.defineESModuleGetters(lazy, { }); /** - * Tests UI updates to the status card in the panel after enable/disables. + * Tests UI updates to the status card in the panel after enable/disable. */ add_task(async function test_status_card_in_panel() { const l10nIdOn = "ipprotection-connection-status-on"; @@ -47,27 +47,27 @@ add_task(async function test_status_card_in_panel() { l10nIdOff, "Status card connection toggle data-l10n-id should be correct by default" ); - - let descriptionMetadata = statusCard?.statusGroupEl.description; - - Assert.ok( - descriptionMetadata.values.length, - "Ensure there are elements loaded in the description slot" + Assert.equal( + statusCard?.statusGroupEl.description, + "", + "Time string should be empty" ); + Assert.ok(statusCard.locationEl, "Location details should be present"); - let locationNameFilter = descriptionMetadata.values.filter( - locationName => locationName === mockLocation.name - ); - Assert.ok(locationNameFilter.length, "Found location in status card"); + let flag = + statusCard.locationEl?.shadowRoot.querySelector("ipprotection-flag"); + + Assert.ok(flag, "Flag component should be present"); - // We can't check the time value directly, so instead see if the lit timerDirective is loaded in the component. - // Assert that there's no timerDirective, so that we know the timer is not running. - let timerDirectiveFilter = descriptionMetadata.values.filter( - value => value._$litDirective$?.name == "TimerDirective" + let animationLoadedPromise = BrowserTestUtils.waitForMutationCondition( + statusCard.shadowRoot, + { childList: true, subtree: true }, + () => statusCard.animationEl ); - Assert.ok( - !timerDirectiveFilter.length, - "Timer should not be loaded in description meta data" + let timerUpdatedPromise = BrowserTestUtils.waitForMutationCondition( + statusCard.shadowRoot, + { childList: true, subtree: true }, + () => JSON.parse(statusCard.statusGroupEl.dataset.l10nArgs).time != "" ); // Set state as if protection is enabled @@ -80,22 +80,54 @@ add_task(async function test_status_card_in_panel() { content.requestUpdate(); - await content.updateComplete; + await Promise.all([ + content.updateComplete, + timerUpdatedPromise, + animationLoadedPromise, + ]); Assert.equal( statusCard?.statusGroupEl.getAttribute("data-l10n-id"), l10nIdOn, "Status card connection toggle data-l10n-id should be correct when protection is enabled" ); + Assert.ok(statusCard.animationEl, "Status card animation should be present"); + + let animationUnloadedPromise = BrowserTestUtils.waitForMutationCondition( + statusCard.shadowRoot, + { childList: true, subtree: true }, + () => !statusCard.animationEl + ); + let timerStoppedPromise = BrowserTestUtils.waitForMutationCondition( + statusCard.shadowRoot, + { childList: true, subtree: true }, + () => JSON.parse(statusCard?.statusGroupEl.dataset.l10nArgs).time === "" + ); + + // // Set state as if protection is disabled + await setPanelState({ + isSignedOut: false, + protectionEnabledSince: enabledSince, + location: mockLocation, + isProtectionEnabled: false, + }); - // Now check the timerDirective again and see if it's loaded. If found, then the timer is running. - descriptionMetadata = statusCard?.statusGroupEl.description; - timerDirectiveFilter = descriptionMetadata.values.filter( - value => value._$litDirective$?.name == "TimerDirective" + content.requestUpdate(); + + await Promise.all([ + content.updateComplete, + animationUnloadedPromise, + timerStoppedPromise, + ]); + + Assert.equal( + statusCard?.statusGroupEl.getAttribute("data-l10n-id"), + l10nIdOff, + "Status card connection toggle data-l10n-id should be correct when protection is disabled" ); Assert.ok( - timerDirectiveFilter.length, - "Timer should be loaded now in description meta data" + !statusCard.animationEl, + "Status card animation should not be present" ); await closePanel(); diff --git a/browser/locales-preview/ipProtection.ftl b/browser/locales-preview/ipProtection.ftl @@ -36,46 +36,17 @@ ipprotection-feature-introduction-button-primary = Next ipprotection-feature-introduction-button-secondary-not-now = Not now ipprotection-feature-introduction-button-secondary-no-thanks = No thanks -## Panel - -upgrade-vpn-title = Get peace of mind with full-device protection -upgrade-vpn-paragraph = Protect yourself beyond the browser with <a data-l10n-name="learn-more-vpn">{ -mozilla-vpn-brand-name }</a>. Customize your VPN location, set site-specific locations, and enjoy enhanced security whether you’re at home or on public Wi-Fi. -upgrade-vpn-button = Upgrade - -signed-out-vpn-title = Sign in to boost your browser’s privacy with free { -firefox-vpn-brand-name } -signed-out-vpn-message = You’ve been selected for early access to our new, <a data-l10n-name="learn-more-vpn-signed-out">built-in VPN</a>. Enhance your browser’s protection by hiding your location and encrypting your traffic. -sign-in-vpn = Next - -## Status card - -ipprotection-connection-status-on = - .label = VPN is on -ipprotection-connection-status-off = - .label = VPN is off +## -# The panel status card has a header, as well as VPN server location name and connection time displayed under it when the VPN is on. +# The panel status card has a header and a connection time displayed under it when the VPN is on. # Variables: # $time (String) - The amount of time connected to the proxy as HH:MM:SS (hours, minutes, seconds). -ipprotection-connection-time = { $time } - -# Location refers to the VPN server geographical position. -ipprotection-location-title = - .title = Location selected based on fastest server - -ipprotection-site-settings-control = - .label = Website settings - -# Variables: -# $sitename (String) - The name of the site that we're currently on (eg. example.com) -ipprotection-site-settings-button-vpn-off = - .label = { $sitename } - .description = VPN is off +ipprotection-connection-status-on = + .label = VPN on + .description = { $time } -# Variables: -# $sitename (String) - The name of the site that we're currently on (eg. example.com) -ipprotection-site-settings-button-vpn-on = - .label = { $sitename } - .description = VPN is on +ipprotection-connection-status-off = + .label = VPN off # When VPN is toggled on ipprotection-toggle-active = @@ -84,6 +55,19 @@ ipprotection-toggle-active = ipprotection-toggle-inactive = .aria-label = Turn VPN on +# Location refers to the VPN server geographical position. +ipprotection-location-title = + .label = Location + .title = Location selected based on fastest server + +upgrade-vpn-title = Get peace of mind with full-device protection +upgrade-vpn-paragraph = Protect yourself beyond the browser with <a data-l10n-name="learn-more-vpn">{ -mozilla-vpn-brand-name }</a>. Customize your VPN location, set site-specific locations, and enjoy enhanced security whether you’re at home or on public Wi-Fi. +upgrade-vpn-button = Upgrade + +signed-out-vpn-title = Sign in to boost your browser’s privacy with free { -firefox-vpn-brand-name } +signed-out-vpn-message = You’ve been selected for early access to our new, <a data-l10n-name="learn-more-vpn-signed-out">built-in VPN</a>. Enhance your browser’s protection by hiding your location and encrypting your traffic. +sign-in-vpn = Next + ## Messages and errors ipprotection-message-generic-error =