tor-browser

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

commit 6948c472ebaa49506d5c6dacdb3f81fec217ce96
parent 7dd63d21285a8aaa3bdb13704a03ee1561c6cfc6
Author: Fred Chasen <fchasen@mozilla.com>
Date:   Fri, 21 Nov 2025 22:41:47 +0000

Bug 1997231 - Update IP Protection panel headers. r=ip-protection-reviewers,desktop-theme-reviewers,kpatenio,fluent-reviewers,bolsson,dao

Removes the header component and moves the header into the panelview so that we can have it hide when subviews are shown.

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

Diffstat:
Mbrowser/base/content/appmenu-viewcache.inc.xhtml | 13+++++++++++++
Mbrowser/components/ipprotection/IPProtectionPanel.sys.mjs | 52++++++++++++++++++++++++++++++++++++++++++++--------
Mbrowser/components/ipprotection/content/ipprotection-content.mjs | 15++++++---------
Dbrowser/components/ipprotection/content/ipprotection-header.css | 29-----------------------------
Dbrowser/components/ipprotection/content/ipprotection-header.mjs | 81-------------------------------------------------------------------------------
Mbrowser/components/ipprotection/content/ipprotection-message-bar.mjs | 8+-------
Mbrowser/components/ipprotection/content/ipprotection-status-card.mjs | 2--
Mbrowser/components/ipprotection/jar.mn | 2--
Mbrowser/components/ipprotection/tests/browser/browser_ipprotection_header.js | 40++++++++++++++++++++--------------------
Mbrowser/components/ipprotection/tests/browser/browser_ipprotection_keyboard_navigation.js | 29++++++++++++++++++++++++++---
Mbrowser/components/ipprotection/tests/browser/browser_ipprotection_panel.js | 8++++----
Mbrowser/locales-preview/ipProtection.ftl | 2+-
Mbrowser/themes/shared/customizableui/panelUI-shared.css | 10+++++-----
13 files changed, 120 insertions(+), 171 deletions(-)

diff --git a/browser/base/content/appmenu-viewcache.inc.xhtml b/browser/base/content/appmenu-viewcache.inc.xhtml @@ -822,6 +822,19 @@ class="PanelUI-subView" mainview-with-header="true" has-custom-header="true"> + <hbox id="PanelUI-ipprotection-header" + class="panel-header panel-header-with-info-button"> + <html:h1> + <html:span id="ipprotection-header-content" + data-l10n-id="ipprotection-title"></html:span> + <html:moz-badge + id="ipprotection-experiment-badge" + data-l10n-id="ipprotection-experiment-badge" + ></html:moz-badge> + </html:h1> + </hbox> + <toolbarseparator/> + <vbox id="PanelUI-ipprotection-content" class="panel-subview-body"/> </panelview> <panelview id="PanelUI-ipprotection-site-settings" diff --git a/browser/components/ipprotection/IPProtectionPanel.sys.mjs b/browser/components/ipprotection/IPProtectionPanel.sys.mjs @@ -34,6 +34,9 @@ export class IPProtectionPanel { static WIDGET_ID = "ipprotection-button"; static PANEL_ID = "PanelUI-ipprotection"; static TITLE_L10N_ID = "ipprotection-title"; + static HEADER_AREA_ID = "PanelUI-ipprotection-header"; + static CONTENT_AREA_ID = "PanelUI-ipprotection-content"; + static HEADER_BUTTON_ID = "ipprotection-header-button"; /** * Loads the ipprotection custom element script @@ -182,11 +185,20 @@ export class IPProtectionPanel { lazy.IPPProxyManager.stop(); } - showHelpPage() { - let win = this.panel.ownerGlobal; + /** + * Opens the help page in a new tab and closes the panel. + * + * @param {Event} e + */ + static showHelpPage(e) { + let win = e.target?.ownerGlobal; if (win && !Cu.isInAutomation) { win.openWebLinkIn(LINKS.SUPPORT_URL, "tab"); - this.close(); + } + + let panelParent = e.target?.closest("panel"); + if (panelParent) { + panelParent.hidePopup(); } } @@ -240,6 +252,19 @@ export class IPProtectionPanel { #createPanel(panelView) { let { ownerDocument } = panelView; + let headerArea = panelView.querySelector( + `#${IPProtectionPanel.HEADER_AREA_ID}` + ); + let headerButton = headerArea.querySelector( + `#${IPProtectionPanel.HEADER_BUTTON_ID}` + ); + if (!headerButton) { + headerButton = this.#createHeaderButton(ownerDocument); + headerArea.appendChild(headerButton); + } + // Reset the tab index to ensure it is focusable. + headerButton.setAttribute("tabindex", "0"); + let contentEl = ownerDocument.createElement( IPProtectionPanel.CONTENT_TAGNAME ); @@ -249,7 +274,22 @@ export class IPProtectionPanel { this.#addPanelListeners(ownerDocument); - panelView.appendChild(contentEl); + let contentArea = panelView.querySelector( + `#${IPProtectionPanel.CONTENT_AREA_ID}` + ); + contentArea.appendChild(contentEl); + } + + #createHeaderButton(ownerDocument) { + const headerButton = ownerDocument.createXULElement("toolbarbutton"); + + headerButton.id = IPProtectionPanel.HEADER_BUTTON_ID; + headerButton.className = "panel-info-button"; + headerButton.dataset.capturesFocus = "true"; + + ownerDocument.l10n.setAttributes(headerButton, "ipprotection-help-button"); + headerButton.addEventListener("click", IPProtectionPanel.showHelpPage); + return headerButton; } /** @@ -319,7 +359,6 @@ export class IPProtectionPanel { doc.addEventListener("IPProtection:Close", this.handleEvent); doc.addEventListener("IPProtection:UserEnable", this.handleEvent); doc.addEventListener("IPProtection:UserDisable", this.handleEvent); - doc.addEventListener("IPProtection:ShowHelpPage", this.handleEvent); doc.addEventListener("IPProtection:SignIn", this.handleEvent); doc.addEventListener("IPProtection:UserShowSiteSettings", this.handleEvent); } @@ -330,7 +369,6 @@ export class IPProtectionPanel { doc.removeEventListener("IPProtection:Close", this.handleEvent); doc.removeEventListener("IPProtection:UserEnable", this.handleEvent); doc.removeEventListener("IPProtection:UserDisable", this.handleEvent); - doc.removeEventListener("IPProtection:ShowHelpPage", this.handleEvent); doc.removeEventListener("IPProtection:SignIn", this.handleEvent); doc.removeEventListener( "IPProtection:UserShowSiteSettings", @@ -379,8 +417,6 @@ export class IPProtectionPanel { } else if (event.type == "IPProtection:UserDisable") { this.#stopProxy(); Services.prefs.setBoolPref("browser.ipProtection.userEnabled", false); - } else if (event.type == "IPProtection:ShowHelpPage") { - this.showHelpPage(); } else if (event.type == "IPProtection:ClickUpgrade") { // Let the service know that we tried upgrading at least once this.initiatedUpgrade = true; diff --git a/browser/components/ipprotection/content/ipprotection-content.mjs b/browser/components/ipprotection/content/ipprotection-content.mjs @@ -10,8 +10,6 @@ import { } from "chrome://browser/content/ipprotection/ipprotection-constants.mjs"; // 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-message-bar.mjs"; // eslint-disable-next-line import/no-unassigned-import import "chrome://browser/content/ipprotection/ipprotection-signedout.mjs"; @@ -25,7 +23,6 @@ import "chrome://global/content/elements/moz-toggle.mjs"; */ export default class IPProtectionContentElement extends MozLitElement { static queries = { - headerEl: "ipprotection-header", signedOutEl: "ipprotection-signedout", messagebarEl: "ipprotection-message-bar", statusCardEl: "ipprotection-status-card", @@ -147,16 +144,18 @@ export default class IPProtectionContentElement extends MozLitElement { #keyListener(event) { let keyCode = event.code; switch (keyCode) { + case "Tab": case "ArrowUp": // Intentional fall-through case "ArrowDown": { event.stopPropagation(); event.preventDefault(); - let direction = - keyCode == "ArrowDown" - ? Services.focus.MOVEFOCUS_FORWARD - : Services.focus.MOVEFOCUS_BACKWARD; + let isForward = + (keyCode == "Tab" && !event.shiftKey) || keyCode == "ArrowDown"; + let direction = isForward + ? Services.focus.MOVEFOCUS_FORWARD + : Services.focus.MOVEFOCUS_BACKWARD; Services.focus.moveFocus( window, null, @@ -309,8 +308,6 @@ export default class IPProtectionContentElement extends MozLitElement { rel="stylesheet" href="chrome://browser/content/ipprotection/ipprotection-content.css" /> - <ipprotection-header titleId="ipprotection-title"></ipprotection-header> - <hr /> <div id="ipprotection-content-wrapper">${content}</div> `; } diff --git a/browser/components/ipprotection/content/ipprotection-header.css b/browser/components/ipprotection/content/ipprotection-header.css @@ -1,29 +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"; - -#ipprotection-header-wrapper { - display: flex; - align-items: center; - justify-content: center; - padding: var(--arrowpanel-menuitem-margin-inline); -} - -#ipprotection-header-title-and-badge { - display: flex; - align-items: center; - justify-content: center; - gap: var(--space-small); - - h1 { - margin: 0; - } -} - -#ipprotection-help-button { - position: absolute; - inset-inline-end: 0; - margin-inline-end: var(--space-medium); -} diff --git a/browser/components/ipprotection/content/ipprotection-header.mjs b/browser/components/ipprotection/content/ipprotection-header.mjs @@ -1,81 +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"; - -// eslint-disable-next-line import/no-unassigned-import -import "chrome://global/content/elements/moz-badge.mjs"; -// eslint-disable-next-line import/no-unassigned-import -import "chrome://global/content/elements/moz-button.mjs"; - -/** - * A custom elements that implements display of the IP Protection header. - */ -export default class IPProtectionHeaderElement extends MozLitElement { - static queries = { - experimentBadgeEl: "#ipprotection-experiment-badge", - helpButtonEl: "#ipprotection-help-button", - titleEl: "#ipprotection-header-title", - }; - - static properties = { - titleId: { type: String }, - }; - - constructor() { - super(); - this.titleId = ""; - } - - connectedCallback() { - super.connectedCallback(); - } - - disconnectedCallback() { - super.disconnectedCallback(); - } - - handleClickHelpButton() { - this.dispatchEvent( - new CustomEvent("IPProtection:ShowHelpPage", { - bubbles: true, - composed: true, - }) - ); - } - - render() { - return html` - <link - rel="stylesheet" - href="chrome://browser/content/ipprotection/ipprotection-header.css" - /> - <div id="ipprotection-header-wrapper"> - <span id="ipprotection-header-title-and-badge"> - <h1> - <span - id="ipprotection-header-title" - data-l10n-id=${this.titleId} - ></span> - </h1> - <moz-badge - id="ipprotection-experiment-badge" - data-l10n-id="ipprotection-experiment-badge" - ></moz-badge> - </span> - <moz-button - id="ipprotection-help-button" - type="icon ghost" - data-l10n-id="ipprotection-help-button" - iconSrc="chrome://global/skin/icons/help.svg" - @click=${this.handleClickHelpButton} - tabindex="0" - ></moz-button> - </div> - `; - } -} - -customElements.define("ipprotection-header", IPProtectionHeaderElement); diff --git a/browser/components/ipprotection/content/ipprotection-message-bar.mjs b/browser/components/ipprotection/content/ipprotection-message-bar.mjs @@ -115,13 +115,7 @@ export default class IPProtectionMessageBarElement extends MozLitElement { return null; } - return html` - <link - rel="stylesheet" - href="chrome://browser/content/ipprotection/ipprotection-header.css" - /> - ${messageBarTemplate} - `; + return html` ${messageBarTemplate} `; } } diff --git a/browser/components/ipprotection/content/ipprotection-status-card.mjs b/browser/components/ipprotection/content/ipprotection-status-card.mjs @@ -14,8 +14,6 @@ import { } from "chrome://browser/content/ipprotection/ipprotection-timer.mjs"; // 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"; // eslint-disable-next-line import/no-unassigned-import import "chrome://browser/content/ipprotection/ipprotection-site-settings-control.mjs"; diff --git a/browser/components/ipprotection/jar.mn b/browser/components/ipprotection/jar.mn @@ -8,8 +8,6 @@ browser.jar: 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-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) diff --git a/browser/components/ipprotection/tests/browser/browser_ipprotection_header.js b/browser/components/ipprotection/tests/browser/browser_ipprotection_header.js @@ -28,21 +28,24 @@ add_task(async function test_header_content() { await panelShownPromise; let header = panelView.querySelector( - lazy.IPProtectionPanel.CONTENT_TAGNAME - ).headerEl; + `#${lazy.IPProtectionPanel.HEADER_AREA_ID}` + ); Assert.ok( BrowserTestUtils.isVisible(header), - "ipprotection-header component should be present" + "ipprotection-header should be present" ); Assert.ok( - header.experimentBadgeEl, + header.querySelector("moz-badge"), "ipprotection-header experiment badge should be present" ); Assert.ok( - header.helpButtonEl, + header.querySelector(`#${IPProtectionPanel.HEADER_BUTTON_ID}`), "ipprotection-header help button should be present" ); - Assert.ok(header.titleEl, "ipprotection-header title should be present"); + Assert.ok( + header.querySelector("h1"), + "ipprotection-header title should be present" + ); // Close the panel let panelHiddenPromise = waitForPanelEvent(document, "popuphidden"); @@ -66,28 +69,25 @@ add_task(async function test_help_button() { await panelShownPromise; let header = panelView.querySelector( - lazy.IPProtectionPanel.CONTENT_TAGNAME - ).headerEl; + `#${lazy.IPProtectionPanel.HEADER_AREA_ID}` + ); Assert.ok( BrowserTestUtils.isVisible(header), - "ipprotection-header component should be present" + "ipprotection-header should be present" ); - let helpButton = header.helpButtonEl; + let helpButton = header.querySelector( + `#${IPProtectionPanel.HEADER_BUTTON_ID}` + ); Assert.ok(helpButton, "ipprotection-header help button should be present"); - let helpPageEventPromise = BrowserTestUtils.waitForEvent( - document, - "IPProtection:ShowHelpPage" - ); + let panelHiddenPromise = waitForPanelEvent(document, "popuphidden"); helpButton.click(); - await helpPageEventPromise; - Assert.ok(true, "Got IPProtection:ShowHelpPage event"); - - // Close the panel - let panelHiddenPromise = waitForPanelEvent(document, "popuphidden"); - EventUtils.synthesizeKey("KEY_Escape"); await panelHiddenPromise; + Assert.ok( + !BrowserTestUtils.isVisible(helpButton), + "ipprotection-header help button should have closed the panel" + ); }); diff --git a/browser/components/ipprotection/tests/browser/browser_ipprotection_keyboard_navigation.js b/browser/components/ipprotection/tests/browser/browser_ipprotection_keyboard_navigation.js @@ -34,6 +34,12 @@ add_task(async function test_keyboard_navigation_in_panel() { "ipprotection-content component should be present" ); + await expectFocusAfterKey( + "Tab", + content.ownerDocument.querySelector( + `#${IPProtectionPanel.HEADER_BUTTON_ID}` + ) + ); let statusCard = content.statusCardEl; await expectFocusAfterKey("Tab", statusCard.connectionToggleEl); @@ -42,8 +48,14 @@ add_task(async function test_keyboard_navigation_in_panel() { "Tab", content.upgradeEl.querySelector("#upgrade-vpn-button") ); - await expectFocusAfterKey("Tab", content.headerEl.helpButtonEl); + // Loop back around + await expectFocusAfterKey( + "Tab", + content.ownerDocument.querySelector( + `#${IPProtectionPanel.HEADER_BUTTON_ID}` + ) + ); await expectFocusAfterKey("Tab", statusCard.connectionToggleEl); await expectFocusAfterKey("ArrowDown", content.upgradeEl.querySelector("a")); @@ -51,12 +63,23 @@ add_task(async function test_keyboard_navigation_in_panel() { "ArrowDown", content.upgradeEl.querySelector("#upgrade-vpn-button") ); - await expectFocusAfterKey("ArrowDown", content.headerEl.helpButtonEl); + // Loop back around + await expectFocusAfterKey( + "ArrowDown", + content.ownerDocument.querySelector( + `#${IPProtectionPanel.HEADER_BUTTON_ID}` + ) + ); await expectFocusAfterKey("ArrowDown", statusCard.connectionToggleEl); // Loop backwards - await expectFocusAfterKey("Shift+Tab", content.headerEl.helpButtonEl); + await expectFocusAfterKey( + "Shift+Tab", + content.ownerDocument.querySelector( + `#${IPProtectionPanel.HEADER_BUTTON_ID}` + ) + ); await closePanel(); }); diff --git a/browser/components/ipprotection/tests/browser/browser_ipprotection_panel.js b/browser/components/ipprotection/tests/browser/browser_ipprotection_panel.js @@ -53,8 +53,8 @@ add_task(async function click_toolbar_button() { ); let header = panelView.querySelector( - lazy.IPProtectionPanel.CONTENT_TAGNAME - ).headerEl; + `#${lazy.IPProtectionPanel.HEADER_AREA_ID}` + ); Assert.ok( BrowserTestUtils.isVisible(header), "ipprotection-header component should be present" @@ -99,8 +99,8 @@ add_task(async function test_panel_in_new_window() { ); let header = panelView.querySelector( - lazy.IPProtectionPanel.CONTENT_TAGNAME - ).headerEl; + `#${lazy.IPProtectionPanel.HEADER_AREA_ID}` + ); Assert.ok( BrowserTestUtils.isVisible(header), "ipprotection-header component should be present" diff --git a/browser/locales-preview/ipProtection.ftl b/browser/locales-preview/ipProtection.ftl @@ -24,7 +24,7 @@ ipprotection-experiment-badge = .label = BETA ipprotection-help-button = - .title = Open { -firefox-vpn-brand-name } support page + .tooltiptext = Open { -firefox-vpn-brand-name } support page ipprotection-title = { -firefox-vpn-brand-name } diff --git a/browser/themes/shared/customizableui/panelUI-shared.css b/browser/themes/shared/customizableui/panelUI-shared.css @@ -2325,11 +2325,11 @@ radiogroup:focus-visible > .subviewradio[focused="true"] { width: 400px; } -#PanelUI-ipprotection-header { - width: 100%; +#ipprotection-experiment-badge { + margin-inline-start: var(--space-xsmall); + display: inline-block; } -#PanelUI-ipprotection .panel-header, -#PanelUI-ipprotection .panel-header + toolbarseparator { - display: none; +#ipprotection-header-button { + list-style-image: url(chrome://global/skin/icons/help.svg); }