tor-browser

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

commit cac284bf0e4f2efe788cad7757eeeee4b23c6b8d
parent 023a9d0abdfd0735597d164f8e3cfc9788015efd
Author: hannajones <hjones@mozilla.com>
Date:   Tue, 28 Oct 2025 15:30:40 +0000

Bug 1948415 - Create a moz-page-header component r=fluent-reviewers,desktop-theme-reviewers,mstriemer,bolsson,dao

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

Diffstat:
Mbrowser/components/preferences/preferences.js | 2+-
Mbrowser/components/preferences/widgets/setting-pane/setting-pane.mjs | 21+++++----------------
Mbrowser/locales/en-US/browser/preferences/preferences.ftl | 2++
Mbrowser/themes/shared/preferences/preferences.css | 19-------------------
Apython/l10n/fluent_migrations/bug_1948415_containers_header_migration.py | 23+++++++++++++++++++++++
Mtoolkit/content/customElements.js | 6+++++-
Mtoolkit/content/jar.mn | 2++
Mtoolkit/content/tests/widgets/chrome.toml | 2++
Atoolkit/content/tests/widgets/test_moz_page_header.html | 185+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtoolkit/content/widgets/moz-breadcrumb-group/moz-breadcrumb-group.stories.mjs | 4++--
Atoolkit/content/widgets/moz-page-header/moz-page-header.css | 42++++++++++++++++++++++++++++++++++++++++++
Atoolkit/content/widgets/moz-page-header/moz-page-header.mjs | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atoolkit/content/widgets/moz-page-header/moz-page-header.stories.mjs | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atoolkit/locales/en-US/toolkit/global/mozPageHeader.ftl | 6++++++
14 files changed, 486 insertions(+), 39 deletions(-)

diff --git a/browser/components/preferences/preferences.js b/browser/components/preferences/preferences.js @@ -168,7 +168,7 @@ function srdSectionEnabled(section) { const CONFIG_PANES = { containers2: { parent: "general", - l10nId: "containers-header", + l10nId: "containers-section-header", groupIds: ["containers"], }, }; diff --git a/browser/components/preferences/widgets/setting-pane/setting-pane.mjs b/browser/components/preferences/widgets/setting-pane/setting-pane.mjs @@ -20,18 +20,6 @@ export class SettingPane extends MozLitElement { window.gotoPref(this.config.parent); } - backButtonTemplate() { - if (!this.isSubPane) { - return ""; - } - return html`<moz-button - type="icon" - title="Go back" - iconsrc="chrome://global/skin/icons/arrow-left.svg" - @click=${this.goBack} - ></moz-button>`; - } - connectedCallback() { super.connectedCallback(); this.setAttribute("data-category", this.name); @@ -68,10 +56,11 @@ export class SettingPane extends MozLitElement { render() { return html` - <div class="page-header"> - ${this.backButtonTemplate()} - <h2 data-l10n-id=${this.config.l10nId}></h2> - </div> + <moz-page-header + data-l10n-id=${this.config.l10nId} + .backButton=${this.isSubPane} + @navigate-back=${this.goBack} + ></moz-page-header> ${this.config.groupIds.map(groupId => this.groupTemplate(groupId))} `; } diff --git a/browser/locales/en-US/browser/preferences/preferences.ftl b/browser/locales/en-US/browser/preferences/preferences.ftl @@ -896,6 +896,8 @@ remove-addon-engine-alert = To remove this search engine, remove the associated containers-back-button2 = .aria-label = Back to Settings containers-header = Container Tabs +containers-section-header = + .heading = Container Tabs containers-add-button = .label = Add New Container .accesskey = A diff --git a/browser/themes/shared/preferences/preferences.css b/browser/themes/shared/preferences/preferences.css @@ -1479,22 +1479,3 @@ richlistitem .text-link:hover { font-size: var(--font-size-small); } } - -/* START SRD temp page header styles (bug 1948415) */ -.page-header { - gap: var(--space-small); - align-items: center; - - &:not([hidden]) { - display: flex; - } - - > h2 { - margin: 0; - } - - > moz-button:first-of-type:dir(rtl) { - scale: -1 1; - } -} -/* END SRD temp page header styles (bug 1948415) */ diff --git a/python/l10n/fluent_migrations/bug_1948415_containers_header_migration.py b/python/l10n/fluent_migrations/bug_1948415_containers_header_migration.py @@ -0,0 +1,23 @@ +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +import fluent.syntax.ast as FTL +from fluent.migrate.helpers import transforms_from + + +def migrate(ctx): + """Bug 1948415 - Migrate containers-header to containers-section-header with .heading attribute, part {index}""" + source = "browser/browser/preferences/preferences.ftl" + target = source + + ctx.add_transforms( + target, + source, + transforms_from( + """ +containers-section-header = + .heading = {COPY_PATTERN(from_path, "containers-header")} +""", + from_path=source, + ), + ) diff --git a/toolkit/content/customElements.js b/toolkit/content/customElements.js @@ -851,9 +851,13 @@ "moz-message-bar", "chrome://global/content/elements/moz-message-bar.mjs", ], - ["moz-promo", "chrome://global/content/elements/moz-promo.mjs"], ["moz-option", "chrome://global/content/elements/moz-select.mjs"], + [ + "moz-page-header", + "chrome://global/content/elements/moz-page-header.mjs", + ], ["moz-page-nav", "chrome://global/content/elements/moz-page-nav.mjs"], + ["moz-promo", "chrome://global/content/elements/moz-promo.mjs"], ["moz-radio", "chrome://global/content/elements/moz-radio-group.mjs"], [ "moz-radio-group", diff --git a/toolkit/content/jar.mn b/toolkit/content/jar.mn @@ -129,6 +129,8 @@ toolkit.jar: content/global/elements/moz-input-search.mjs (widgets/moz-input-search/moz-input-search.mjs) content/global/elements/moz-input-text.css (widgets/moz-input-text/moz-input-text.css) content/global/elements/moz-input-text.mjs (widgets/moz-input-text/moz-input-text.mjs) + content/global/elements/moz-page-header.css (widgets/moz-page-header/moz-page-header.css) + content/global/elements/moz-page-header.mjs (widgets/moz-page-header/moz-page-header.mjs) content/global/elements/moz-page-nav.css (widgets/moz-page-nav/moz-page-nav.css) content/global/elements/moz-page-nav-button.css (widgets/moz-page-nav/moz-page-nav-button.css) content/global/elements/moz-page-nav.mjs (widgets/moz-page-nav/moz-page-nav.mjs) diff --git a/toolkit/content/tests/widgets/chrome.toml b/toolkit/content/tests/widgets/chrome.toml @@ -64,6 +64,8 @@ skip-if = ["os == 'mac' && os_version == '14.70' && processor == 'x86_64'"] # Bu ["test_moz_message_bar.html"] +["test_moz_page_header.html"] + ["test_moz_page_nav.html"] ["test_moz_promo.html"] diff --git a/toolkit/content/tests/widgets/test_moz_page_header.html b/toolkit/content/tests/widgets/test_moz_page_header.html @@ -0,0 +1,185 @@ +<!doctype html> +<html> + <head> + <meta charset="utf-8" /> + <title>MozPageHeader Tests</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link + rel="stylesheet" + href="chrome://mochikit/content/tests/SimpleTest/test.css" + /> + <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script + type="module" + src="chrome://global/content/elements/moz-page-header.mjs" + ></script> + <script src="lit-test-helpers.js"></script> + <script> + let html; + let testHelpers = new LitTestHelpers(); + + add_setup(async function setup() { + ({ html } = await testHelpers.setupLit()); + const templateFn = attrs => html` + <moz-page-header ${attrs}></moz-page-header> + `; + testHelpers.setupTests({ templateFn }); + }); + + add_task(async function testMozPageHeaderProperties() { + const HEADING = "Page Heading"; + const DESCRIPTION = "Some description of this page."; + const ICON_SRC = "chrome://global/skin/icons/info.svg"; + + let template = testHelpers.templateFn({ + heading: HEADING, + description: DESCRIPTION, + iconsrc: ICON_SRC, + }); + let renderTarget = await testHelpers.renderTemplate(template); + let header = renderTarget.firstElementChild; + + ok(header, "The page header renders."); + + let headingEl = header.shadowRoot.querySelector("h2"); + is( + headingEl?.textContent.trim(), + HEADING, + "Heading element renders with the expected text." + ); + + let descriptionEl = header.shadowRoot.querySelector(".description"); + is( + descriptionEl?.textContent.trim(), + DESCRIPTION, + "Description element renders with the expected text." + ); + + let iconEl = header.shadowRoot.querySelector(".icon"); + ok(iconEl, "Icon element is rendered when iconSrc is set."); + is(iconEl.getAttribute("src"), ICON_SRC, "Icon has the expected src."); + + let backButton = header.shadowRoot.querySelector(".back-button"); + ok(!backButton, "Back button is not rendered by default."); + + header.backButton = true; + await header.updateComplete; + + backButton = header.shadowRoot.querySelector(".back-button"); + ok(backButton, "Back button appears when backButton is true."); + + header.backButton = false; + await header.updateComplete; + + backButton = header.shadowRoot.querySelector(".back-button"); + ok( + !backButton, + "Back button is removed when backButton is set back to false." + ); + }); + + add_task(async function testMozPageHeaderBackButtonEvent() { + let template = testHelpers.templateFn({ + heading: "With back button", + backbutton: "", + }); + let renderTarget = await testHelpers.renderTemplate(template); + let header = renderTarget.firstElementChild; + + let backButton = header.shadowRoot.querySelector(".back-button"); + ok(backButton, "Back button is present for click test."); + + let eventPromise = new Promise(resolve => { + header.addEventListener("navigate-back", resolve, { once: true }); + }); + + backButton.click(); + let event = await eventPromise; + + ok(event instanceof Event, "Clicking back button dispatches an Event."); + is( + event.type, + "navigate-back", + "The event type is a 'navigate-back' event." + ); + }); + + add_task(async function testMozPageHeaderBreadcrumbSlot() { + const template = html` + <moz-page-header + heading="With Breadcrumbs" + icon-src="chrome://global/skin/icons/info.svg" + > + <moz-breadcrumb-group slot="breadcrumbs"> + <moz-breadcrumb href="about#first" label="First"></moz-breadcrumb> + <moz-breadcrumb + href="about#current" + label="Current" + ></moz-breadcrumb> + </moz-breadcrumb-group> + </moz-page-header> + `; + const renderTarget = await testHelpers.renderTemplate(template); + const header = renderTarget.firstElementChild; + + const slot = header.shadowRoot.querySelector( + 'slot[name="breadcrumbs"]' + ); + ok(slot, "Page header renders a slot for breadcrumbs."); + + const assigned = slot.assignedElements(); + is( + assigned.length, + 1, + "One element is assigned into the breadcrumbs slot." + ); + const group = assigned[0]; + is( + group.localName, + "moz-breadcrumb-group", + "moz-breadcrumb-group is slotted in the breadcrumbs slot." + ); + }); + + add_task(async function testMozPageHeaderSupportPage() { + let supportPageTemplate = testHelpers.templateFn({ + label: "Testing support-page", + "support-page": "test", + }); + let renderTarget = + await testHelpers.renderTemplate(supportPageTemplate); + let header = renderTarget.firstElementChild; + + let supportLink = header.shadowRoot.querySelector(".support-link"); + ok( + supportLink, + "moz-page-header renders a support link when supportPage is provided." + ); + + let adjacentEl = supportLink.previousElementSibling; + is( + adjacentEl.id, + "heading", + "support link is placed next to the heading when no description is provided." + ); + + header.description = "now we have a description."; + await header.updateComplete; + + supportLink = header.shadowRoot.querySelector(".support-link"); + adjacentEl = supportLink.previousElementSibling; + is( + adjacentEl.id, + "description", + "support link is placed next to the description when provided." + ); + }); + </script> + </head> + <body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + </body> +</html> diff --git a/toolkit/content/widgets/moz-breadcrumb-group/moz-breadcrumb-group.stories.mjs b/toolkit/content/widgets/moz-breadcrumb-group/moz-breadcrumb-group.stories.mjs @@ -21,7 +21,7 @@ moz-breadcrumb-third = }, }; -const Template = ({ l10nId, width }) => { +const Template = ({ l10nId, width, slot }) => { return html` <style> ${width @@ -31,7 +31,7 @@ const Template = ({ l10nId, width }) => { }` : ""} </style> - <moz-breadcrumb-group> + <moz-breadcrumb-group slot=${ifDefined(slot)}> <moz-breadcrumb href="about#firstpage" data-l10n-id=${ifDefined(l10nId + "-first")} diff --git a/toolkit/content/widgets/moz-page-header/moz-page-header.css b/toolkit/content/widgets/moz-page-header/moz-page-header.css @@ -0,0 +1,42 @@ +/* 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/. */ + +::slotted(moz-breadcrumb-group) { + margin-block-end: var(--space-large); +} + +.heading { + display: flex; + align-items: center; + gap: var(--space-small); + min-height: var(--button-min-height); +} + +.back-button:dir(rtl) { + transform: scaleX(-1); +} + +h1 { + margin: 0; +} + +.icon { + width: var(--icon-size); + height: var(--icon-size); + -moz-context-properties: fill, stroke; + fill: currentColor; + stroke: currentColor; +} + +.description { + color: var(--text-color-deemphasized); + + &:has(+ .support-link) { + margin-inline-end: var(--space-xxsmall); + } +} + +.support-link { + display: inline-block; +} diff --git a/toolkit/content/widgets/moz-page-header/moz-page-header.mjs b/toolkit/content/widgets/moz-page-header/moz-page-header.mjs @@ -0,0 +1,110 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { html } from "../vendor/lit.all.mjs"; +import { MozLitElement } from "../lit-utils.mjs"; + +window.MozXULElement?.insertFTLIfNeeded("toolkit/global/mozPageHeader.ftl"); + +/** + * A header component for providing context about a specific page. + * + * @tagname moz-page-header + * @property {string} heading - The page title text. + * @property {string} description - Secondary text shown under the heading. + * @property {string} iconSrc - The src for an optional icon. + * @property {string} supportPage - Optional URL for a related support article. + * @property {boolean} backButton - Whether or not the header should include a back button. + * @slot breadcrumbs - Container for a <moz-breadcrumb-group, shown above the heading. + * @fires navigate-back + * Event indicating the backwards navigation should occur. + */ +export default class MozPageHeader extends MozLitElement { + static properties = { + heading: { type: String, fluent: true }, + description: { type: String, fluent: true }, + iconSrc: { type: String }, + supportPage: { type: String, attribute: "support-page" }, + backButton: { type: Boolean }, + }; + + constructor() { + super(); + this.heading = ""; + this.description = ""; + this.iconSrc = ""; + this.supportPage = ""; + this.backButton = false; + } + + backButtonTemplate() { + if (!this.backButton) { + return ""; + } + return html`<moz-button + type="ghost" + data-l10n-id="back-nav-button-title" + iconsrc="chrome://global/skin/icons/arrow-left.svg" + class="back-button" + @click=${this.handleBack} + ></moz-button>`; + } + + iconTemplate() { + if (!this.iconSrc) { + return ""; + } + return html`<img src=${this.iconSrc} role="presentation" class="icon" />`; + } + + descriptionTemplate() { + if (!this.description) { + return ""; + } + return html`<span class="description" id="description"> + ${this.description} + </span> + ${this.supportLinkTemplate()}`; + } + + supportLinkTemplate() { + if (!this.supportPage) { + return ""; + } + return html`<a + is="moz-support-link" + support-page=${this.supportPage} + part="support-link" + class="support-link" + aria-describedby=${this.description ? "description" : "heading"} + ></a>`; + } + + handleBack() { + this.dispatchEvent(new Event("navigate-back")); + } + + render() { + return html` + <link + rel="stylesheet" + href="chrome://global/content/elements/moz-page-header.css" + /> + <link + rel="stylesheet" + href="chrome://global/skin/design-system/text-and-typography.css" + /> + <div class="page-header-container"> + <slot name="breadcrumbs"></slot> + <div class="heading"> + ${this.backButtonTemplate()}${this.iconTemplate()} + <h1 id="heading">${this.heading}</h1> + ${!this.description ? this.supportLinkTemplate() : ""} + </div> + ${this.descriptionTemplate()} + </div> + `; + } +} +customElements.define("moz-page-header", MozPageHeader); diff --git a/toolkit/content/widgets/moz-page-header/moz-page-header.stories.mjs b/toolkit/content/widgets/moz-page-header/moz-page-header.stories.mjs @@ -0,0 +1,101 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { html, ifDefined } from "../vendor/lit.all.mjs"; +import "./moz-page-header.mjs"; +import { Default as Breadcrumbs } from "../moz-breadcrumb-group/moz-breadcrumb-group.stories.mjs"; + +export default { + title: "UI Widgets/Page Header", + component: "moz-page-header", + argTypes: { + l10nId: { + options: [ + "moz-page-header-heading", + "moz-page-header-description", + "moz-page-header-long", + ], + control: { type: "select" }, + }, + }, + parameters: { + status: "in-development", + fluent: ` +moz-page-header-heading = + .heading = This is the page heading +moz-page-header-description = + .heading = This is the page heading + .description = This is a description of the page +moz-page-header-long = + .heading = Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum libero enim, luctus eu ante a, maximus imperdiet mi. Suspendisse sodales, nisi et commodo malesuada, lectus. + .description = Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum libero enim, luctus eu ante a, maximus imperdiet mi. Suspendisse sodales, nisi et commodo malesuada, lectus. +moz-breadcrumb-first = + .label = First page +moz-breadcrumb-second = + .label = Previous page +moz-breadcrumb-third = + .label = Current page + `, + }, +}; + +const Template = ({ + l10nId, + backButton, + iconSrc, + supportPage, + showBreadcrumbs, +}) => html` + <moz-page-header + data-l10n-id=${l10nId} + iconsrc=${ifDefined(iconSrc)} + support-page=${ifDefined(supportPage)} + ?backbutton=${backButton} + > + ${showBreadcrumbs + ? Breadcrumbs({ + slot: "breadcrumbs", + ...Breadcrumbs.args, + }) + : ""} + </moz-page-header> +`; + +export const Default = Template.bind({}); +Default.args = { + l10nId: "moz-page-header-heading", + iconSrc: "", + backButton: false, + showBreadcrumbs: false, +}; + +export const WithBackButton = Template.bind({}); +WithBackButton.args = { + ...Default.args, + backButton: true, +}; + +export const WithIcon = Template.bind({}); +WithIcon.args = { + ...WithBackButton.args, + iconSrc: "chrome://global/skin/icons/highlights.svg", +}; + +export const WithDescription = Template.bind({}); +WithDescription.args = { + ...WithBackButton.args, + l10nId: "moz-page-header-description", +}; + +export const WithSupportPage = Template.bind({}); +WithSupportPage.args = { + ...WithDescription.args, + supportPage: "test", +}; + +export const WithBreadcrumbs = Template.bind({}); +WithBreadcrumbs.args = { + ...WithBackButton.args, + showBreadcrumbs: true, +}; diff --git a/toolkit/locales/en-US/toolkit/global/mozPageHeader.ftl b/toolkit/locales/en-US/toolkit/global/mozPageHeader.ftl @@ -0,0 +1,6 @@ +# 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/. + +back-nav-button-title = + .title = Go back