tor-browser

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

commit af31d5856c0d6cadb8cc8ee90752b1abb5cba767
parent 8c2a1ee1f707dd80aeb1f6c12e56c84263f6b9bd
Author: kpatenio <kpatenio@mozilla.com>
Date:   Mon, 17 Nov 2025 19:54:31 +0000

Bug 1997406 — implement new status card designs for the ip protection panel r=fluent-reviewers,ip-protection-reviewers,bolsson,rking

Differential Revision: https://phabricator.services.mozilla.com/D271156

Diffstat:
Dbrowser/components/ipprotection/assets/flags/us.png | 0
Dbrowser/components/ipprotection/assets/ipprotection-connection-off.svg | 17-----------------
Dbrowser/components/ipprotection/assets/ipprotection-connection-on.svg | 5-----
Dbrowser/components/ipprotection/assets/rings.svg | 35-----------------------------------
Mbrowser/components/ipprotection/content/ipprotection-constants.mjs | 4----
Mbrowser/components/ipprotection/content/ipprotection-content.mjs | 12------------
Dbrowser/components/ipprotection/content/ipprotection-flag.css | 17-----------------
Dbrowser/components/ipprotection/content/ipprotection-flag.mjs | 66------------------------------------------------------------------
Mbrowser/components/ipprotection/content/ipprotection-status-card.css | 78++----------------------------------------------------------------------------
Mbrowser/components/ipprotection/content/ipprotection-status-card.mjs | 152++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mbrowser/components/ipprotection/jar.mn | 3---
Mbrowser/components/ipprotection/tests/browser/browser.toml | 6+++---
Dbrowser/components/ipprotection/tests/browser/browser_ipprotection_flag.js | 50--------------------------------------------------
Mbrowser/components/ipprotection/tests/browser/browser_ipprotection_status_card.js | 82++++++++++++++++++++++++-------------------------------------------------------
Mbrowser/locales-preview/ipProtection.ftl | 41+++++++++++++++++++++--------------------
15 files changed, 127 insertions(+), 441 deletions(-)

diff --git a/browser/components/ipprotection/assets/flags/us.png b/browser/components/ipprotection/assets/flags/us.png Binary files differ. diff --git a/browser/components/ipprotection/assets/ipprotection-connection-off.svg b/browser/components/ipprotection/assets/ipprotection-connection-off.svg @@ -1,16 +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/. --> -<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none"> -<style> - path.globe { - fill: #5b5b66; - } - - @media (prefers-color-scheme: dark) { - path.globe { - fill: #8f8f9d; - } - } -</style> -<rect width="19.2" height="19.2" x="25.8" y="3" rx="9.6" fill="context-stroke"/><path class="globe" d="M24 1.2c1.69.002 3.36.19 4.984.556a12.654 12.654 0 0 0-4.823 5.15c-.054-.003-.108-.006-.162-.006-3.063 0-5.804 3.42-7.315 8.43h6.413c.24 1.09.625 2.126 1.128 3.09h-8.28a31.589 31.589 0 0 0 0 11.16h16.11c.278-1.555.438-3.128.482-4.707.913.212 1.864.325 2.841.326-.05 1.467-.19 2.93-.422 4.381h5.189c.628-1.793.951-3.68.955-5.58 0-.054-.003-.108-.004-.162a12.651 12.651 0 0 0 5.147-4.821c.365 1.623.555 3.293.557 4.983a22.801 22.801 0 1 1-43.865-8.725A22.802 22.802 0 0 1 23.999 1.2Zm-7.316 31.47c1.51 5.01 4.252 8.43 7.316 8.43 3.063 0 5.805-3.42 7.319-8.43H16.684Zm-7.398 0a17.185 17.185 0 0 0 7.418 6.771 21.944 21.944 0 0 1-2.995-6.771H9.286Zm25.014 0a21.945 21.945 0 0 1-2.993 6.765 17.184 17.184 0 0 0 7.416-6.765H34.3ZM7.855 18.42A16.995 16.995 0 0 0 6.9 24c.004 1.9.327 3.787.955 5.58h5.19a35.228 35.228 0 0 1 0-11.16h-5.19Zm8.849-9.861a17.186 17.186 0 0 0-7.418 6.771h4.423c.607-2.41 1.62-4.7 2.995-6.771Z"/><path fill="context-fill" fill-rule="evenodd" d="M39.432 8.585a.6.6 0 0 0-.849 0l-.322.322a1.084 1.084 0 0 0-.171-.046L35.407 8.4l-2.683.46c-.531.094-.918.555-.917 1.094 0 .615 0 1.72.048 2.223a6.12 6.12 0 0 0 .648 2.488l-1.12 1.12a.6.6 0 1 0 .849.848l7.2-7.2a.6.6 0 0 0 0-.848Zm-2.517 6.053a2.533 2.533 0 0 1-1.508.963 2.56 2.56 0 0 1-.846-.347l-.861.862a3.732 3.732 0 0 0 1.64.69l.067.006.068-.007a3.746 3.746 0 0 0 2.407-1.456 5.557 5.557 0 0 0 1.08-3.171c.026-.311.038-.853.045-1.37l-1.2 1.2a4.544 4.544 0 0 1-.892 2.63ZM33 11.01c-.006.46-.01.799.008 1 .036.604.167 1.199.39 1.761l3.84-3.84-1.83-.313-2.4.412c0 .37-.004.698-.008.98Z" clip-rule="evenodd"/></svg> -\ No newline at end of file diff --git a/browser/components/ipprotection/assets/ipprotection-connection-on.svg b/browser/components/ipprotection/assets/ipprotection-connection-on.svg @@ -1,4 +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/. --> -<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none"><rect width="19.2" height="19.2" x="25.8" y="3" fill="context-stroke" rx="9.6"/><path fill="url(#a)" fill-rule="evenodd" d="M23.097 15.33h-6.413C18.194 10.32 20.936 6.9 24 6.9l.159.003a12.658 12.658 0 0 1 4.822-5.147A22.82 22.82 0 0 0 24 1.2 22.8 22.8 0 1 0 46.8 24a22.82 22.82 0 0 0-.556-4.98 12.657 12.657 0 0 1-5.146 4.82l.001.16a16.992 16.992 0 0 1-.955 5.58h-5.19a35.14 35.14 0 0 0 .425-4.38 12.635 12.635 0 0 1-2.842-.326 31.67 31.67 0 0 1-.484 4.706H15.946a31.589 31.589 0 0 1 0-11.16h8.276c-.503-.964-.885-2-1.125-3.09Zm-6.413 17.34c1.51 5.01 4.252 8.43 7.316 8.43s5.806-3.42 7.319-8.43H16.684Zm-8.83-14.25A16.995 16.995 0 0 0 6.9 24c.004 1.9.327 3.787.955 5.58h5.19a35.23 35.23 0 0 1 0-11.16h-5.19Zm5.855-3.09c.607-2.41 1.62-4.7 2.995-6.771a17.186 17.186 0 0 0-7.418 6.771h4.423Zm0 17.34H9.286a17.185 17.185 0 0 0 7.418 6.771 21.942 21.942 0 0 1-2.995-6.771Zm17.6 6.765a17.186 17.186 0 0 0 7.414-6.765H34.3a21.944 21.944 0 0 1-2.991 6.765Z" clip-rule="evenodd"/><path fill="context-fill" fill-rule="evenodd" d="m35.4 16.813-.067-.008a3.746 3.746 0 0 1-2.408-1.456 5.556 5.556 0 0 1-1.08-3.171c-.045-.504-.045-1.609-.045-2.224-.002-.539.385-1 .916-1.093L35.4 8.4l2.682.46c.532.093.92.555.918 1.095 0 .613 0 1.72-.048 2.223a5.556 5.556 0 0 1-1.08 3.171 3.746 3.746 0 0 1-2.407 1.456l-.065.008ZM33 10.029c0 .37-.004.698-.008.98-.006.461-.01.8.008 1 .016.948.328 1.867.892 2.63.369.493.905.835 1.508.962a2.532 2.532 0 0 0 1.507-.963 4.543 4.543 0 0 0 .893-2.629c.018-.2.014-.539.008-1-.004-.282-.008-.61-.008-.98l-2.4-.411-2.4.411Zm.9.759 1.5-.257v4.14h-.005a1.611 1.611 0 0 1-.78-.565 3.459 3.459 0 0 1-.678-2.118c-.02-.207-.032-.635-.037-1.2Z" clip-rule="evenodd"/><defs><radialGradient id="a" cx="0" cy="0" r="1" gradientTransform="rotate(-45 24.849 -55.893) scale(64.4881)" gradientUnits="userSpaceOnUse"><stop stop-color="#B833E1"/><stop offset=".371" stop-color="#9059FF"/><stop offset=".614" stop-color="#5B6DF8"/><stop offset="1" stop-color="#0090ED"/></radialGradient></defs></svg> -\ No newline at end of file diff --git a/browser/components/ipprotection/assets/rings.svg b/browser/components/ipprotection/assets/rings.svg @@ -1,35 +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/. --> -<svg xmlns="http://www.w3.org/2000/svg" - width="200" - height="200" - viewbox="0 0 200 200" - stroke="#fff" - fill="none" - stroke-opacity="0" - stroke-width="4" - r="20"> - <circle id="a" cx="100" cy="100"> - <animate id="a_r" attributeName="r" from="20" to="100" dur="6s" begin="0s; a_o_finish.end" /> - <animate id="a_o_start" attributeName="stroke-opacity" from="0" to=".3" dur="4s" begin="a_r.begin"/> - <animate id="a_o_finish" attributeName="stroke-opacity" from=".3" to="0" dur="2s" begin="a_o_start.end"/> - <animate attributeName="stroke-width" from="2" to="4" dur="4s" begin="a_r.begin"/> - <animate attributeName="stroke-width" from="4" to="0.01" dur="2s" begin="a_o_start.end"/> - </circle> - <circle id="b" cx="100" cy="100"> - <animate id="b_r" attributeName="r" from="20" to="100" dur="6s" begin="a_r.begin+3" /> - <animate id="b_o_start" attributeName="stroke-opacity" from="0" to=".3" dur="4s" begin="b_r.begin"/> - <animate id="b_o_finish" attributeName="stroke-opacity" from=".3" to="0" dur="2s" begin="b_o_start.end"/> - <animate attributeName="stroke-width" from="2" to="4" dur="4s" begin="b_r.begin"/> - <animate attributeName="stroke-width" from="4" to="0.01" dur="2s" begin="b_o_start.end"/> - </circle> - <circle id="c" cx="100" cy="100"> - <animate id="c_r" attributeName="r" from="20" to="100" dur="6s" begin="b_r.begin+3" /> - <animate id="c_o_start" attributeName="stroke-opacity" from="0" to=".3" dur="4s" begin="c_r.begin"/> - <animate id="c_o_finish" attributeName="stroke-opacity" from=".3" to="0" dur="2s" begin="c_o_start.end"/> - <animate attributeName="stroke-width" from="2" to="4" dur="4s" begin="c_r.begin"/> - <animate attributeName="stroke-width" from="4" to="0.01" dur="2s" begin="c_o_start.end"/> - </circle> -</svg> - diff --git a/browser/components/ipprotection/content/ipprotection-constants.mjs b/browser/components/ipprotection/content/ipprotection-constants.mjs @@ -17,10 +17,6 @@ export const LINKS = Object.freeze({ SUPPORT_URL: "https://support.mozilla.org/kb/use-ip-concealment-in-firefox", }); -export const FLAGS = Object.freeze({ - us: "chrome://browser/content/ipprotection/assets/flags/us.png", -}); - export const ERRORS = Object.freeze({ GENERIC: "generic-error", }); diff --git a/browser/components/ipprotection/content/ipprotection-content.mjs b/browser/components/ipprotection/content/ipprotection-content.mjs @@ -12,8 +12,6 @@ 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"; @@ -226,16 +224,6 @@ export default class IPProtectionContentElement extends MozLitElement { `; } - descriptionTemplate() { - return this.state.location - ? html` - <ipprotection-flag - .location=${this.state.location} - ></ipprotection-flag> - ` - : null; - } - statusCardTemplate() { return html` <ipprotection-status-card diff --git a/browser/components/ipprotection/content/ipprotection-flag.css b/browser/components/ipprotection/content/ipprotection-flag.css @@ -1,17 +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"; - -#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 @@ -1,66 +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 } 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-status-card.css b/browser/components/ipprotection/content/ipprotection-status-card.css @@ -5,14 +5,7 @@ @import "chrome://global/skin/global.css"; :host { - --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); + --border-color: var(--border-color-card); } #ipprotection-content-wrapper { @@ -22,11 +15,6 @@ 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; @@ -35,76 +23,14 @@ } .is-enabled { - color: var(--status-card-connected-text-color); - background-color: var(--status-card-connected-background-color); + background-color: var(--background-color-success); } #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); } - -#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,7 +3,11 @@ * 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"; +import { + html, + classMap, + styleMap, +} from "chrome://global/content/vendor/lit.all.mjs"; import { connectionTimer, defaultTimeValue, @@ -12,17 +16,17 @@ 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://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", }; @@ -37,9 +41,8 @@ export default class IPProtectionStatusCard extends MozLitElement { canShowTime: { type: Boolean }, enabledSince: { type: Object }, location: { type: Object }, - _showAnimation: { type: Boolean, state: true }, // Track toggle state separately so that we can tell when the toggle - // is enabled because of existing protection state or because of user action. + // is enabled because of the existing protection state or because of user action. _toggleEnabled: { type: Boolean, state: true }, }; @@ -47,7 +50,6 @@ export default class IPProtectionStatusCard extends MozLitElement { super(); this.keyListener = this.#keyListener.bind(this); - this._showAnimation = false; } connectedCallback() { @@ -67,14 +69,14 @@ export default class IPProtectionStatusCard extends MozLitElement { if (isEnabled) { this.dispatchEvent( - new CustomEvent("ipprotection-status-card:user-toggled-on", { + new CustomEvent(this.TOGGLE_ON_EVENT, { bubbles: true, composed: true, }) ); } else { this.dispatchEvent( - new CustomEvent("ipprotection-status-card:user-toggled-off", { + new CustomEvent(this.TOGGLE_OFF_EVENT, { bubbles: true, composed: true, }) @@ -126,89 +128,87 @@ 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; - } - } - - 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() { + cardContentTemplate() { 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"; + // TODO: add site settings button as a slotted element (class="slotted") in a moz-boz-item (Bug 1997411) + + 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> + </moz-box-group>`; + } + + cardDescriptionTemplate() { + // The template consists of location name and connection time. let time = this.canShowTime ? connectionTimer(this.enabledSince) : defaultTimeValue; - 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>`; + // 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; } render() { - return html` - <link - rel="stylesheet" - href="chrome://browser/content/ipprotection/ipprotection-status-card.css" - /> - ${this.alphaCardTemplate()} - `; + let content = this.cardContentTemplate(); + return html`${content}`; } } diff --git a/browser/components/ipprotection/jar.mn b/browser/components/ipprotection/jar.mn @@ -4,15 +4,12 @@ browser.jar: content/browser/ipprotection/assets (assets/**/*.svg) - content/browser/ipprotection/assets/flags (assets/flags/*.png) content/browser/ipprotection/ipprotection-content.css (content/ipprotection-content.css) content/browser/ipprotection/ipprotection-content.mjs (content/ipprotection-content.mjs) content/browser/ipprotection/ipprotection-constants.mjs (content/ipprotection-constants.mjs) 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-status-card.css (content/ipprotection-status-card.css) diff --git a/browser/components/ipprotection/tests/browser/browser.toml b/browser/components/ipprotection/tests/browser/browser.toml @@ -22,8 +22,6 @@ prefs = [ ["browser_ipprotection_content_signedout.js"] -["browser_ipprotection_flag.js"] - ["browser_ipprotection_header.js"] ["browser_ipprotection_keyboard_navigation.js"] @@ -41,7 +39,9 @@ prefs = [ ["browser_ipprotection_telemetry.js"] ["browser_ipprotection_toolbar.js"] -skip-if = ["devedition"] +skip-if = [ + "devedition", +] ["browser_ipprotection_upgrade.js"] diff --git a/browser/components/ipprotection/tests/browser/browser_ipprotection_flag.js b/browser/components/ipprotection/tests/browser/browser_ipprotection_flag.js @@ -1,50 +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"; - -/** - * 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_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/disable. + * Tests UI updates to the status card in the panel after enable/disables. */ 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" ); - Assert.equal( - statusCard?.statusGroupEl.description, - "", - "Time string should be empty" - ); - Assert.ok(statusCard.locationEl, "Location details should be present"); - let flag = - statusCard.locationEl?.shadowRoot.querySelector("ipprotection-flag"); + let descriptionMetadata = statusCard?.statusGroupEl.description; - Assert.ok(flag, "Flag component should be present"); + Assert.ok( + descriptionMetadata.values.length, + "Ensure there are elements loaded in the description slot" + ); - let animationLoadedPromise = BrowserTestUtils.waitForMutationCondition( - statusCard.shadowRoot, - { childList: true, subtree: true }, - () => statusCard.animationEl + let locationNameFilter = descriptionMetadata.values.filter( + locationName => locationName === mockLocation.name ); - let timerUpdatedPromise = BrowserTestUtils.waitForMutationCondition( - statusCard.shadowRoot, - { childList: true, subtree: true }, - () => JSON.parse(statusCard.statusGroupEl.dataset.l10nArgs).time != "" + Assert.ok(locationNameFilter.length, "Found location in status card"); + + // 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" + ); + Assert.ok( + !timerDirectiveFilter.length, + "Timer should not be loaded in description meta data" ); // Set state as if protection is enabled @@ -80,54 +80,22 @@ add_task(async function test_status_card_in_panel() { content.requestUpdate(); - await Promise.all([ - content.updateComplete, - timerUpdatedPromise, - animationLoadedPromise, - ]); + await content.updateComplete; 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, - }); - 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" + // 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" ); Assert.ok( - !statusCard.animationEl, - "Status card animation should not be present" + timerDirectiveFilter.length, + "Timer should be loaded now in description meta data" ); await closePanel(); diff --git a/browser/locales-preview/ipProtection.ftl b/browser/locales-preview/ipProtection.ftl @@ -36,17 +36,31 @@ 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 -# The panel status card has a header and a connection time displayed under it when the VPN is on. +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. # Variables: # $time (String) - The amount of time connected to the proxy as HH:MM:SS (hours, minutes, seconds). -ipprotection-connection-status-on = - .label = VPN on - .description = { $time } +ipprotection-connection-time = { $time } -ipprotection-connection-status-off = - .label = VPN off +# Location refers to the VPN server geographical position. +ipprotection-location-title = + .title = Location selected based on fastest server # When VPN is toggled on ipprotection-toggle-active = @@ -55,19 +69,6 @@ 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 =