From fa0e1f01ae9862119db30418876a68ea66b6d995 Mon Sep 17 00:00:00 2001 From: Kelly Cochrane Date: Fri, 9 Feb 2024 22:01:30 +0000 Subject: [PATCH] Bug 1850591 - Convert Firefox View's domain-specific navigation component to a global component r=desktop-theme-reviewers,fxview-reviewers,reusable-components-reviewers,sclements,jules,hjones,fluent-reviewers,flod Differential Revision: https://phabricator.services.mozilla.com/D193593 --- ...72_ensure_Fluent_works_in_customizeMode.js | 43 ++- ...{category-history.svg => view-history.svg} | 0 ...ategory-opentabs.svg => view-opentabs.svg} | 0 ...ntbrowsing.svg => view-recentbrowsing.svg} | 0 ...ntlyclosed.svg => view-recentlyclosed.svg} | 0 ...ory-syncedtabs.svg => view-syncedtabs.svg} | 0 .../components/firefoxview/firefoxview.css | 32 +- .../components/firefoxview/firefoxview.html | 57 ++-- .../components/firefoxview/firefoxview.mjs | 58 ++-- .../fxview-category-navigation.css | 60 ---- .../fxview-category-navigation.mjs | 150 -------- browser/components/firefoxview/jar.mn | 13 +- .../firefoxview/tests/browser/browser.toml | 42 ++- .../tests/browser/browser_firefoxview.js | 5 +- .../browser_firefoxview_general_telemetry.js | 12 +- .../browser/browser_firefoxview_navigation.js | 54 +-- .../browser/browser_firefoxview_paused.js | 4 +- .../browser_firefoxview_search_telemetry.js | 18 +- .../browser_firefoxview_virtual_list.js | 2 +- .../browser/browser_history_firefoxview.js | 16 +- .../tests/browser/browser_opentabs_cards.js | 4 +- .../browser/browser_opentabs_firefoxview.js | 2 +- .../tests/browser/browser_opentabs_recency.js | 3 +- .../browser_opentabs_tab_indicators.js | 10 +- .../browser_recentlyclosed_firefoxview.js | 18 +- .../browser_syncedtabs_errors_firefoxview.js | 4 +- .../browser/browser_syncedtabs_firefoxview.js | 20 +- .../firefoxview/tests/browser/head.js | 30 +- .../firefoxview/tests/chrome/chrome.toml | 2 - .../test_fxview_category_navigation.html | 322 ------------------ .../fxview-category-navigation.stories.mjs | 113 ------ browser/locales/en-US/browser/firefoxView.ftl | 3 + toolkit/content/jar.mn | 3 + toolkit/content/tests/widgets/chrome.toml | 3 + .../tests/widgets/test_moz_page_nav.html | 306 +++++++++++++++++ .../moz-page-nav/moz-page-nav-button.css | 38 +-- .../widgets/moz-page-nav/moz-page-nav.css | 76 +++++ .../widgets/moz-page-nav/moz-page-nav.mjs | 170 +++++++++ .../moz-page-nav/moz-page-nav.stories.mjs | 77 +++++ 39 files changed, 871 insertions(+), 899 deletions(-) rename browser/components/firefoxview/content/{category-history.svg => view-history.svg} (100%) rename browser/components/firefoxview/content/{category-opentabs.svg => view-opentabs.svg} (100%) rename browser/components/firefoxview/content/{category-recentbrowsing.svg => view-recentbrowsing.svg} (100%) rename browser/components/firefoxview/content/{category-recentlyclosed.svg => view-recentlyclosed.svg} (100%) rename browser/components/firefoxview/content/{category-syncedtabs.svg => view-syncedtabs.svg} (100%) delete mode 100644 browser/components/firefoxview/fxview-category-navigation.css delete mode 100644 browser/components/firefoxview/fxview-category-navigation.mjs delete mode 100644 browser/components/firefoxview/tests/chrome/test_fxview_category_navigation.html delete mode 100644 browser/components/storybook/stories/fxview-category-navigation.stories.mjs create mode 100644 toolkit/content/tests/widgets/test_moz_page_nav.html rename browser/components/firefoxview/fxview-category-button.css => toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css (66%) create mode 100644 toolkit/content/widgets/moz-page-nav/moz-page-nav.css create mode 100644 toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs create mode 100644 toolkit/content/widgets/moz-page-nav/moz-page-nav.stories.mjs diff --git a/browser/components/customizableui/test/browser_1856572_ensure_Fluent_works_in_customizeMode.js b/browser/components/customizableui/test/browser_1856572_ensure_Fluent_works_in_customizeMode.js index 0f89bd5d1c74..7187effdb186 100644 --- a/browser/components/customizableui/test/browser_1856572_ensure_Fluent_works_in_customizeMode.js +++ b/browser/components/customizableui/test/browser_1856572_ensure_Fluent_works_in_customizeMode.js @@ -10,12 +10,21 @@ const { "resource://testing-common/FirefoxViewTestUtils.sys.mjs" ); +/** + * Bug 1856572 - This test is to ensure that fluent strings in Firefox View + * can be updated after opening Customize Mode + * **/ add_task(async function test_data_l10n_customize_mode() { FirefoxViewTestUtilsInit(this); await withFirefoxView({ win: window }, async function (browser) { /** * Bug 1856572, Bug 1857622: Without requesting two animation frames * the "missing Fluent strings" issue will not reproduce. + * + * Given the precondition that we open Firefox View, then open Customize Mode, then + * navigate back to Firefox View in a quick succession, as long as Fluent + * controlled strings can be updated by changing their Fluent IDs then this + * test is valid. */ await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)) @@ -23,39 +32,47 @@ add_task(async function test_data_l10n_customize_mode() { await startCustomizing(); await endCustomizing(); await openFirefoxViewTab(window); - const { document } = browser.contentWindow; - let header = document.querySelector("h1"); - document.l10n.setAttributes(header, "firefoxview-overview-header"); + let secondPageNavButton = document.querySelectorAll( + "moz-page-nav-button" + )[0]; + document.l10n.setAttributes( + secondPageNavButton, + "firefoxview-overview-header" + ); let previousText = await document.l10n.formatValue( - "firefoxview-page-title" + "firefoxview-opentabs-nav" + ); + let translatedText = await window.content.document.l10n.formatValue( + "firefoxview-overview-header" ); /** * This should be replaced with - * BrowserTestUtils.waitForMutationCondition(header, {characterData: true}, ...) + * BrowserTestUtils.waitForMutationCondition(secondPageNavButton, {characterData: true}, ...) * but apparently Fluent manipulation of textContent doesn't result * in a characterData mutation occurring. */ await BrowserTestUtils.waitForCondition(() => { - return header.textContent != previousText; + return ( + secondPageNavButton.textContent !== previousText && + secondPageNavButton.textContent === translatedText + ); }, "waiting for text content to change"); Assert.equal( - header.getAttribute("data-l10n-id"), + secondPageNavButton.getAttribute("data-l10n-id"), "firefoxview-overview-header", "data-l10n-id should be updated" ); Assert.notEqual( previousText, - header.textContent, - "The header's text content should be updated" - ); - let translatedText = await window.content.document.l10n.formatValue( - "firefoxview-overview-header" + secondPageNavButton.textContent, + "The second page-nav button text content should be updated" ); + Assert.equal( translatedText, - header.textContent, + secondPageNavButton.textContent, "The changed text should be the translated value of 'firefoxview-overview-header" ); }); diff --git a/browser/components/firefoxview/content/category-history.svg b/browser/components/firefoxview/content/view-history.svg similarity index 100% rename from browser/components/firefoxview/content/category-history.svg rename to browser/components/firefoxview/content/view-history.svg diff --git a/browser/components/firefoxview/content/category-opentabs.svg b/browser/components/firefoxview/content/view-opentabs.svg similarity index 100% rename from browser/components/firefoxview/content/category-opentabs.svg rename to browser/components/firefoxview/content/view-opentabs.svg diff --git a/browser/components/firefoxview/content/category-recentbrowsing.svg b/browser/components/firefoxview/content/view-recentbrowsing.svg similarity index 100% rename from browser/components/firefoxview/content/category-recentbrowsing.svg rename to browser/components/firefoxview/content/view-recentbrowsing.svg diff --git a/browser/components/firefoxview/content/category-recentlyclosed.svg b/browser/components/firefoxview/content/view-recentlyclosed.svg similarity index 100% rename from browser/components/firefoxview/content/category-recentlyclosed.svg rename to browser/components/firefoxview/content/view-recentlyclosed.svg diff --git a/browser/components/firefoxview/content/category-syncedtabs.svg b/browser/components/firefoxview/content/view-syncedtabs.svg similarity index 100% rename from browser/components/firefoxview/content/category-syncedtabs.svg rename to browser/components/firefoxview/content/view-syncedtabs.svg diff --git a/browser/components/firefoxview/firefoxview.css b/browser/components/firefoxview/firefoxview.css index 48cf5a9490c5..737c797b7c4f 100644 --- a/browser/components/firefoxview/firefoxview.css +++ b/browser/components/firefoxview/firefoxview.css @@ -69,6 +69,10 @@ body { grid-template-columns: var(--fxview-sidebar-width) 1fr; background-color: var(--fxview-background-color); color: var(--fxview-text-primary-color); + + @media (max-width: 52rem) { + display: flex; + } } .main-container { @@ -88,26 +92,6 @@ body { margin: 0; } -fxview-category-button:focus-visible { - outline-offset: var(--in-content-focus-outline-inset); -} - -fxview-category-button[name="recentbrowsing"]::part(icon) { - background-image: url("chrome://browser/content/firefoxview/category-recentbrowsing.svg"); -} -fxview-category-button[name="opentabs"]::part(icon) { - background-image: url("chrome://browser/content/firefoxview/category-opentabs.svg"); -} -fxview-category-button[name="recentlyclosed"]::part(icon) { - background-image: url("chrome://browser/content/firefoxview/category-recentlyclosed.svg"); -} -fxview-category-button[name="syncedtabs"]::part(icon) { - background-image: url("chrome://browser/content/firefoxview/category-syncedtabs.svg"); -} -fxview-category-button[name="history"]::part(icon) { - background-image: url("chrome://browser/content/firefoxview/category-history.svg"); -} - fxview-tab-list.with-dismiss-button::part(secondary-button) { background-image: url("chrome://global/skin/icons/close.svg"); } @@ -174,14 +158,6 @@ panel-list { overflow-y: visible; } -fxview-category-navigation { - overflow-y: auto; -} - -fxview-category-navigation h1 { - margin-block: 0; -} - fxview-empty-state:not([isSelectedTab]) button[slot="primary-action"] { margin-inline-start: 0; } diff --git a/browser/components/firefoxview/firefoxview.html b/browser/components/firefoxview/firefoxview.html index 1f53a1d0c91a..2e1dc40b78c2 100644 --- a/browser/components/firefoxview/firefoxview.html +++ b/browser/components/firefoxview/firefoxview.html @@ -41,54 +41,51 @@ > - -

- + - - + - - + - - + - - + - -
+ +
diff --git a/browser/components/firefoxview/firefoxview.mjs b/browser/components/firefoxview/firefoxview.mjs index 962e187559bd..7d6d3a587053 100644 --- a/browser/components/firefoxview/firefoxview.mjs +++ b/browser/components/firefoxview/firefoxview.mjs @@ -3,35 +3,35 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ let pageList = []; -let categoryPagesDeck = null; -let categoryNavigation = null; +let viewsDeck = null; +let pageNav = null; let activeComponent = null; let searchKeyboardShortcut = null; const { topChromeWindow } = window.browsingContext; function onHashChange() { - let page = document.location?.hash.substring(1); - if (!page || !pageList.includes(page)) { - page = "recentbrowsing"; + let view = document.location?.hash.substring(1); + if (!view || !pageList.includes(view)) { + view = "recentbrowsing"; } - changePage(page); + changeView(view); } -function changePage(page) { - categoryPagesDeck.selectedViewName = page; - categoryNavigation.currentCategory = page; - if (categoryNavigation.categoryButtons.includes(document.activeElement)) { - let currentCategoryButton = categoryNavigation.categoryButtons.find( - categoryButton => categoryButton.name === page +function changeView(view) { + viewsDeck.selectedViewName = view; + pageNav.currentPage = view; + if (pageNav.pageNavButtons.includes(document.activeElement)) { + let currentPageButton = pageNav.pageNavButtons.find( + pageButton => pageButton.view === view ); - (currentCategoryButton || categoryNavigation.categoryButtons[0]).focus(); + (currentPageButton || pageNav.pageNavButtons[0]).focus(); } } -function onPagesDeckViewChange() { - for (const child of categoryPagesDeck.children) { - if (child.getAttribute("name") == categoryPagesDeck.selectedViewName) { +function onViewsDeckViewChange() { + for (const child of viewsDeck.children) { + if (child.getAttribute("name") == viewsDeck.selectedViewName) { child.enter(); activeComponent = child; } else { @@ -41,11 +41,11 @@ function onPagesDeckViewChange() { } function recordNavigationTelemetry(source, eventTarget) { - let page = "recentbrowsing"; + let view = "recentbrowsing"; if (source === "category-navigation") { - page = eventTarget.parentNode.currentCategory; + view = eventTarget.parentNode.currentView; } else if (source === "view-all") { - page = eventTarget.shortPageName; + view = eventTarget.shortPageName; } // Record telemetry Services.telemetry.recordEvent( @@ -54,7 +54,7 @@ function recordNavigationTelemetry(source, eventTarget) { "navigation", null, { - page, + page: view, source, } ); @@ -73,7 +73,7 @@ async function updateSearchTextboxSize() { const placeholder = msg.attributes[0].value; maxLength = Math.max(maxLength, placeholder.length); } - for (const child of categoryPagesDeck.children) { + for (const child of viewsDeck.children) { child.searchTextboxSize = maxLength; } } @@ -89,15 +89,15 @@ async function updateSearchKeyboardShortcut() { window.addEventListener("DOMContentLoaded", async () => { recordEnteredTelemetry(); - categoryNavigation = document.querySelector("fxview-category-navigation"); - categoryPagesDeck = document.querySelector("named-deck"); + pageNav = document.querySelector("moz-page-nav"); + viewsDeck = document.querySelector("named-deck"); - for (const item of categoryNavigation.categoryButtons) { - pageList.push(item.getAttribute("name")); + for (const item of pageNav.pageNavButtons) { + pageList.push(item.getAttribute("view")); } window.addEventListener("hashchange", onHashChange); - window.addEventListener("change-category", function (event) { - location.hash = event.target.getAttribute("name"); + window.addEventListener("change-view", function (event) { + location.hash = event.target.getAttribute("view"); window.scrollTo(0, 0); recordNavigationTelemetry("category-navigation", event.target); }); @@ -105,11 +105,11 @@ window.addEventListener("DOMContentLoaded", async () => { recordNavigationTelemetry("view-all", event.originalTarget); }); - categoryPagesDeck.addEventListener("view-changed", onPagesDeckViewChange); + viewsDeck.addEventListener("view-changed", onViewsDeckViewChange); // set the initial state onHashChange(); - onPagesDeckViewChange(); + onViewsDeckViewChange(); await updateSearchTextboxSize(); await updateSearchKeyboardShortcut(); diff --git a/browser/components/firefoxview/fxview-category-navigation.css b/browser/components/firefoxview/fxview-category-navigation.css deleted file mode 100644 index 571059699b53..000000000000 --- a/browser/components/firefoxview/fxview-category-navigation.css +++ /dev/null @@ -1,60 +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/. */ - -:host { - --fxviewcategorynav-button-padding: 8px; - margin-inline-start: 42px; - position: sticky; - top: 0; - height: 100vh; -} - -nav { - display: grid; - grid-template-rows: min-content 1fr auto; - gap: 25px; - margin-block-start: var(--fxview-margin-top); -} - -.category-nav-header { - /* Align the header text/icon with the category button icons */ - margin-inline-start: var(--fxviewcategorynav-button-padding); -} - -.category-nav-buttons, -::slotted(.category-nav-footer) { - display: grid; - grid-template-columns: 1fr; - grid-auto-rows: min-content; - gap: 4px; -} - -@media (prefers-contrast) { - .category-nav-buttons { - gap: 8px; - } -} - -@media (prefers-reduced-motion) { - /* (See Bug 1610081) Setting border-inline-end to add clear differentiation between side navigation and main content area */ - :host { - border-inline-end: 1px solid var(--in-content-border-color); - } -} - -@media (max-width: 52rem) { - :host { - grid-template-rows: 1fr auto; - } - - .category-nav-header { - display: none; - } - - .category-nav-buttons, - ::slotted(.category-nav-footer) { - justify-content: center; - grid-template-columns: min-content; - } -} diff --git a/browser/components/firefoxview/fxview-category-navigation.mjs b/browser/components/firefoxview/fxview-category-navigation.mjs deleted file mode 100644 index abacd17df1ae..000000000000 --- a/browser/components/firefoxview/fxview-category-navigation.mjs +++ /dev/null @@ -1,150 +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/. */ - -import { html } from "chrome://global/content/vendor/lit.all.mjs"; -import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; - -export default class FxviewCategoryNavigation extends MozLitElement { - static properties = { - currentCategory: { type: String }, - }; - - static queries = { - categoryButtonsSlot: "slot[name=category-button]", - }; - - get categoryButtons() { - return this.categoryButtonsSlot - .assignedNodes() - .filter(node => !node.hidden); - } - - onChangeCategory(e) { - this.currentCategory = e.target.name; - } - - handleFocus(e) { - if (e.key == "ArrowDown" || e.key == "ArrowRight") { - e.preventDefault(); - this.focusNextCategory(); - } else if (e.key == "ArrowUp" || e.key == "ArrowLeft") { - e.preventDefault(); - this.focusPreviousCategory(); - } - } - - focusPreviousCategory() { - let categoryButtons = this.categoryButtons; - let currentIndex = categoryButtons.findIndex(b => b.selected); - let prev = categoryButtons[currentIndex - 1]; - if (prev) { - prev.activate(); - prev.focus(); - } - } - - focusNextCategory() { - let categoryButtons = this.categoryButtons; - let currentIndex = categoryButtons.findIndex(b => b.selected); - let next = categoryButtons[currentIndex + 1]; - if (next) { - next.activate(); - next.focus(); - } - } - - render() { - return html` - - - `; - } - - updated() { - let categorySelected = false; - let assignedCategories = this.categoryButtons; - for (let button of assignedCategories) { - button.selected = button.name == this.currentCategory; - categorySelected = categorySelected || button.selected; - } - if (!categorySelected && assignedCategories.length) { - // Current category has no matching category, reset to the first category. - assignedCategories[0].activate(); - } - } -} -customElements.define("fxview-category-navigation", FxviewCategoryNavigation); - -export class FxviewCategoryButton extends MozLitElement { - static properties = { - selected: { type: Boolean }, - }; - - static queries = { - buttonEl: "button", - }; - - connectedCallback() { - super.connectedCallback(); - this.setAttribute("role", "tab"); - } - - get name() { - return this.getAttribute("name"); - } - - activate() { - this.dispatchEvent( - new CustomEvent("change-category", { - bubbles: true, - composed: true, - }) - ); - } - - render() { - return html` - - - `; - } - - updated() { - this.setAttribute("aria-selected", this.selected); - this.setAttribute("tabindex", this.selected ? 0 : -1); - } -} -customElements.define("fxview-category-button", FxviewCategoryButton); diff --git a/browser/components/firefoxview/jar.mn b/browser/components/firefoxview/jar.mn index 27eeaaef800c..1e5cc3e6907b 100644 --- a/browser/components/firefoxview/jar.mn +++ b/browser/components/firefoxview/jar.mn @@ -15,9 +15,6 @@ browser.jar: content/browser/firefoxview/view-syncedtabs.css content/browser/firefoxview/recentbrowsing.mjs content/browser/firefoxview/firefoxview.css - content/browser/firefoxview/fxview-category-button.css - content/browser/firefoxview/fxview-category-navigation.css - content/browser/firefoxview/fxview-category-navigation.mjs content/browser/firefoxview/fxview-empty-state.css content/browser/firefoxview/fxview-empty-state.mjs content/browser/firefoxview/helpers.mjs @@ -29,12 +26,12 @@ browser.jar: content/browser/firefoxview/recentlyclosed.mjs content/browser/firefoxview/viewpage.mjs content/browser/firefoxview/history-empty.svg (content/history-empty.svg) - content/browser/firefoxview/category-history.svg (content/category-history.svg) - content/browser/firefoxview/category-opentabs.svg (content/category-opentabs.svg) - content/browser/firefoxview/category-recentbrowsing.svg (content/category-recentbrowsing.svg) - content/browser/firefoxview/category-recentlyclosed.svg (content/category-recentlyclosed.svg) - content/browser/firefoxview/category-syncedtabs.svg (content/category-syncedtabs.svg) content/browser/firefoxview/recentlyclosed-empty.svg (content/recentlyclosed-empty.svg) content/browser/firefoxview/synced-tabs-error.svg (content/synced-tabs-error.svg) content/browser/callout-tab-pickup.svg (content/callout-tab-pickup.svg) content/browser/callout-tab-pickup-dark.svg (content/callout-tab-pickup-dark.svg) + content/browser/firefoxview/view-history.svg (content/view-history.svg) + content/browser/firefoxview/view-opentabs.svg (content/view-opentabs.svg) + content/browser/firefoxview/view-recentbrowsing.svg (content/view-recentbrowsing.svg) + content/browser/firefoxview/view-recentlyclosed.svg (content/view-recentlyclosed.svg) + content/browser/firefoxview/view-syncedtabs.svg (content/view-syncedtabs.svg) diff --git a/browser/components/firefoxview/tests/browser/browser.toml b/browser/components/firefoxview/tests/browser/browser.toml index 307032ba81ed..69cf701a0fa6 100644 --- a/browser/components/firefoxview/tests/browser/browser.toml +++ b/browser/components/firefoxview/tests/browser/browser.toml @@ -27,37 +27,37 @@ skip-if = ["true"] # Bug 1869605 and # Bug 1870296 ["browser_firefoxview.js"] -["browser_firefoxview_tab.js"] - -["browser_notification_dot.js"] -skip-if = ["true"] # Bug 1851453 - -["browser_opentabs_changes.js"] - -["browser_reload_firefoxview.js"] - -["browser_tab_close_last_tab.js"] - -["browser_tab_on_close_warning.js"] - -["browser_firefoxview_paused.js"] - ["browser_firefoxview_general_telemetry.js"] +fail-if = ["a11y_checks"] # Bug 1850591 clicked moz-page-nav-button button is not focusable ["browser_firefoxview_navigation.js"] +fail-if = ["a11y_checks"] # Bug 1850591 clicked moz-page-nav-button button is not focusable + +["browser_firefoxview_paused.js"] +fail-if = ["a11y_checks"] # Bug 1850591 clicked moz-page-nav-button button is not focusable ["browser_firefoxview_search_telemetry.js"] +fail-if = ["a11y_checks"] # Bug 1850591 clicked moz-page-nav-button button is not focusable + +["browser_firefoxview_tab.js"] ["browser_firefoxview_virtual_list.js"] +fail-if = ["a11y_checks"] # Bug 1850591 clicked moz-page-nav-button button is not focusable ["browser_history_firefoxview.js"] skip-if = ["true"] # Bug 1877594 -["browser_opentabs_firefoxview.js"] +["browser_notification_dot.js"] +skip-if = ["true"] # Bug 1851453 ["browser_opentabs_cards.js"] fail-if = ["a11y_checks"] # Bugs 1858041, 1854625, and 1872174 clicked Show all link is not accessible because it is "hidden" when clicked +["browser_opentabs_changes.js"] + +["browser_opentabs_firefoxview.js"] +fail-if = ["a11y_checks"] # Bug 1850591 clicked moz-page-nav-button button is not focusable + ["browser_opentabs_recency.js"] skip-if = [ "os == 'win'", @@ -65,9 +65,19 @@ skip-if = [ ] # macos times out, see bug 1857293, skipped for windows, see bug 1858460 ["browser_opentabs_tab_indicators.js"] +fail-if = ["a11y_checks"] # Bug 1850591 clicked moz-page-nav-button button is not focusable ["browser_recentlyclosed_firefoxview.js"] +fail-if = ["a11y_checks"] # Bug 1850591 clicked moz-page-nav-button button is not focusable + +["browser_reload_firefoxview.js"] ["browser_syncedtabs_errors_firefoxview.js"] +fail-if = ["a11y_checks"] # Bug 1850591 clicked moz-page-nav-button button is not focusable ["browser_syncedtabs_firefoxview.js"] +fail-if = ["a11y_checks"] # Bug 1850591 clicked moz-page-nav-button button is not focusable + +["browser_tab_close_last_tab.js"] + +["browser_tab_on_close_warning.js"] diff --git a/browser/components/firefoxview/tests/browser/browser_firefoxview.js b/browser/components/firefoxview/tests/browser/browser_firefoxview.js index 33467941a447..1a51d61f42dd 100644 --- a/browser/components/firefoxview/tests/browser/browser_firefoxview.js +++ b/browser/components/firefoxview/tests/browser/browser_firefoxview.js @@ -6,10 +6,7 @@ add_task(async function about_firefoxview_smoke_test() { const { document } = browser.contentWindow; // sanity check the important regions exist on this page - ok( - document.querySelector("fxview-category-navigation"), - "fxview-category-navigation element exists" - ); + ok(document.querySelector("moz-page-nav"), "moz-page-nav element exists"); ok(document.querySelector("named-deck"), "named-deck element exists"); }); }); diff --git a/browser/components/firefoxview/tests/browser/browser_firefoxview_general_telemetry.js b/browser/components/firefoxview/tests/browser/browser_firefoxview_general_telemetry.js index 51d5caa032bb..d4bc5e371d17 100644 --- a/browser/components/firefoxview/tests/browser/browser_firefoxview_general_telemetry.js +++ b/browser/components/firefoxview/tests/browser/browser_firefoxview_general_telemetry.js @@ -39,7 +39,7 @@ add_task(async function firefox_view_entered_telemetry() { enteredTelemetry[4] = { page: "recentlyclosed" }; enteredAndTabSelectedEvents = [tabSelectedTelemetry, enteredTelemetry]; - navigateToCategory(document, "recentlyclosed"); + await navigateToViewAndWait(document, "recentlyclosed"); await clearAllParentTelemetryEvents(); await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots"); is( @@ -107,9 +107,9 @@ add_task(async function test_change_page_telemetry() { ], ]; await clearAllParentTelemetryEvents(); - navigateToCategory(document, "recentlyclosed"); + await navigateToViewAndWait(document, "recentlyclosed"); await telemetryEvent(changePageEvent); - navigateToCategory(document, "recentbrowsing"); + await navigateToViewAndWait(document, "recentbrowsing"); let openTabsComponent = document.querySelector( "view-opentabs[slot=opentabs]" @@ -189,7 +189,7 @@ add_task(async function test_context_menu_new_window_telemetry() { ); // Test history context menu options - await navigateToCategoryAndWait(document, "history"); + await navigateToViewAndWait(document, "history"); let historyComponent = document.querySelector("view-history"); await TestUtils.waitForCondition(() => historyComponent.fullyUpdated); await TestUtils.waitForCondition( @@ -245,7 +245,7 @@ add_task(async function test_context_menu_private_window_telemetry() { ); // Test history context menu options - await navigateToCategoryAndWait(document, "history"); + await navigateToViewAndWait(document, "history"); let historyComponent = document.querySelector("view-history"); await TestUtils.waitForCondition(() => historyComponent.fullyUpdated); await TestUtils.waitForCondition( @@ -314,7 +314,7 @@ add_task(async function test_context_menu_delete_from_history_telemetry() { ); // Test history context menu options - await navigateToCategoryAndWait(document, "history"); + await navigateToViewAndWait(document, "history"); let historyComponent = document.querySelector("view-history"); await TestUtils.waitForCondition(() => historyComponent.fullyUpdated); await TestUtils.waitForCondition( diff --git a/browser/components/firefoxview/tests/browser/browser_firefoxview_navigation.js b/browser/components/firefoxview/tests/browser/browser_firefoxview_navigation.js index 80206dd94524..281d969b3946 100644 --- a/browser/components/firefoxview/tests/browser/browser_firefoxview_navigation.js +++ b/browser/components/firefoxview/tests/browser/browser_firefoxview_navigation.js @@ -3,15 +3,15 @@ const URL_BASE = `${getFirefoxViewURL()}#`; -function assertCorrectPage(document, name, event) { +function assertCorrectPage(document, view, event) { is( document.location.hash, - `#${name}`, - `Navigation button for ${name} navigates to ${URL_BASE + name} on ${event}.` + `#${view}`, + `Navigation button for ${view} navigates to ${URL_BASE + view} on ${event}.` ); is( document.querySelector("named-deck").selectedViewName, - name, + view, "The correct deck child is selected" ); } @@ -22,21 +22,21 @@ add_task(async function test_side_component_navigation_by_click() { const { document } = browser.contentWindow; let win = browser.ownerGlobal; - const categoryButtons = document.querySelectorAll("fxview-category-button"); + const pageNavButtons = document.querySelectorAll("moz-page-nav-button"); - for (let element of categoryButtons) { - const name = element.name; + for (let element of pageNavButtons) { + const view = element.view; let buttonClicked = BrowserTestUtils.waitForEvent( element.buttonEl, "click", win ); - info(`Clicking navigation button for ${name}`); + info(`Clicking navigation button for ${view}`); EventUtils.synthesizeMouseAtCenter(element.buttonEl, {}, content); await buttonClicked; - assertCorrectPage(document, name, "click"); + assertCorrectPage(document, view, "click"); } }); }); @@ -47,49 +47,49 @@ add_task(async function test_side_component_navigation_by_keyboard() { const { document } = browser.contentWindow; let win = browser.ownerGlobal; - const categoryButtons = document.querySelectorAll("fxview-category-button"); - const firstButton = categoryButtons[0]; + const pageNavButtons = document.querySelectorAll("moz-page-nav-button"); + const firstButton = pageNavButtons[0].buttonEl; firstButton.focus(); is( - document.activeElement, + document.activeElement.shadowRoot.activeElement, firstButton, - "The first category button has focus" + "The first page nav button has focus" ); - for (let element of Array.from(categoryButtons).slice(1)) { - const name = element.name; + for (let element of Array.from(pageNavButtons).slice(1)) { + const view = element.view; let buttonFocused = BrowserTestUtils.waitForEvent(element, "focus", win); - info(`Focus is on ${document.activeElement.name}`); - info(`Arrow down on navigation to ${name}`); + info(`Focus is on ${document.activeElement.view}`); + info(`Arrow down on navigation to ${view}`); EventUtils.synthesizeKey("KEY_ArrowDown", {}, win); await buttonFocused; - assertCorrectPage(document, name, "key press"); + assertCorrectPage(document, view, "key press"); } }); }); -add_task(async function test_direct_navigation_to_correct_category() { +add_task(async function test_direct_navigation_to_correct_view() { await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - const categoryButtons = document.querySelectorAll("fxview-category-button"); + const pageNavButtons = document.querySelectorAll("moz-page-nav-button"); const namedDeck = document.querySelector("named-deck"); - for (let element of categoryButtons) { - const name = element.name; + for (let element of pageNavButtons) { + const view = element.view; - info(`Navigating to ${URL_BASE + name}`); - document.location.assign(URL_BASE + name); + info(`Navigating to ${URL_BASE + view}`); + document.location.assign(URL_BASE + view); await BrowserTestUtils.waitForCondition(() => { - return namedDeck.selectedViewName === name; + return namedDeck.selectedViewName === view; }, "Wait for navigation to complete"); is( namedDeck.selectedViewName, - name, - `The correct deck child for category ${name} is selected` + view, + `The correct deck child for view ${view} is selected` ); } }); diff --git a/browser/components/firefoxview/tests/browser/browser_firefoxview_paused.js b/browser/components/firefoxview/tests/browser/browser_firefoxview_paused.js index c95ac4fcf595..e14311a37ad5 100644 --- a/browser/components/firefoxview/tests/browser/browser_firefoxview_paused.js +++ b/browser/components/firefoxview/tests/browser/browser_firefoxview_paused.js @@ -322,7 +322,7 @@ add_task(async function test_opentabs() { const document = browser.contentDocument; const { openTabsView } = getTopLevelViewElements(document); - await navigateToCategoryAndWait(document, "opentabs"); + await navigateToViewAndWait(document, "opentabs"); const { openTabsList } = await getElements(document); ok(openTabsView, "Found the open tabs view"); @@ -387,7 +387,7 @@ add_task(async function test_recentlyclosed() { await withFirefoxView({}, async browser => { const document = browser.contentDocument; const { recentlyClosedView } = getTopLevelViewElements(document); - await navigateToCategoryAndWait(document, "recentlyclosed"); + await navigateToViewAndWait(document, "recentlyclosed"); const { recentlyClosedList } = await getElements(document); ok(recentlyClosedView, "Found the recently-closed view"); diff --git a/browser/components/firefoxview/tests/browser/browser_firefoxview_search_telemetry.js b/browser/components/firefoxview/tests/browser/browser_firefoxview_search_telemetry.js index 535a4d3a3045..ed9e6c0006c5 100644 --- a/browser/components/firefoxview/tests/browser/browser_firefoxview_search_telemetry.js +++ b/browser/components/firefoxview/tests/browser/browser_firefoxview_search_telemetry.js @@ -56,7 +56,7 @@ add_task(async function test_search_initiated_telemetry() { EventUtils.sendString("example.com", content); await telemetryEvent(searchEvent("recentbrowsing")); - await navigateToCategoryAndWait(document, "opentabs"); + await navigateToViewAndWait(document, "opentabs"); await clearAllParentTelemetryEvents(); is(document.location.hash, "#opentabs", "Searching within open tabs."); const openTabs = document.querySelector("named-deck > view-opentabs"); @@ -65,7 +65,7 @@ add_task(async function test_search_initiated_telemetry() { EventUtils.sendString("example.com", content); await telemetryEvent(searchEvent("opentabs")); - await navigateToCategoryAndWait(document, "recentlyclosed"); + await navigateToViewAndWait(document, "recentlyclosed"); await clearAllParentTelemetryEvents(); is( document.location.hash, @@ -84,7 +84,7 @@ add_task(async function test_search_initiated_telemetry() { EventUtils.sendString("example.com", content); await telemetryEvent(searchEvent("recentlyclosed")); - await navigateToCategoryAndWait(document, "syncedtabs"); + await navigateToViewAndWait(document, "syncedtabs"); await clearAllParentTelemetryEvents(); is(document.location.hash, "#syncedtabs", "Searching within synced tabs."); const syncedTabs = document.querySelector("named-deck > view-syncedtabs"); @@ -93,7 +93,7 @@ add_task(async function test_search_initiated_telemetry() { EventUtils.sendString("example.com", content); await telemetryEvent(searchEvent("syncedtabs")); - await navigateToCategoryAndWait(document, "history"); + await navigateToViewAndWait(document, "history"); await clearAllParentTelemetryEvents(); is(document.location.hash, "#history", "Searching within history."); const history = document.querySelector("named-deck > view-history"); @@ -310,7 +310,7 @@ add_task(async function test_sort_history_search_telemetry() { await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "history"); + await navigateToViewAndWait(document, "history"); const historyComponent = document.querySelector("view-history"); const searchTextbox = await TestUtils.waitForCondition( @@ -426,7 +426,7 @@ add_task(async function test_cumulative_searches_recently_closed_telemetry() { await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "recentlyclosed"); + await navigateToViewAndWait(document, "recentlyclosed"); is( document.location.hash, "#recentlyclosed", @@ -471,7 +471,7 @@ add_task(async function test_cumulative_searches_open_tabs_telemetry() { await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "opentabs"); + await navigateToViewAndWait(document, "opentabs"); is(document.location.hash, "#opentabs", "Searching within open tabs."); const openTabs = document.querySelector("named-deck > view-opentabs"); @@ -517,7 +517,7 @@ add_task(async function test_cumulative_searches_history_telemetry() { await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "history"); + await navigateToViewAndWait(document, "history"); is(document.location.hash, "#history", "Searching within history."); const history = document.querySelector("named-deck > view-history"); const searchTextbox = await TestUtils.waitForCondition(() => { @@ -579,7 +579,7 @@ add_task(async function test_cumulative_searches_syncedtabs_telemetry() { const { document } = browser.contentWindow; Services.obs.notifyObservers(null, UIState.ON_UPDATE); - await navigateToCategoryAndWait(document, "syncedtabs"); + await navigateToViewAndWait(document, "syncedtabs"); is(document.location.hash, "#syncedtabs", "Searching within synced tabs."); let syncedTabs = document.querySelector( "view-syncedtabs:not([slot=syncedtabs])" diff --git a/browser/components/firefoxview/tests/browser/browser_firefoxview_virtual_list.js b/browser/components/firefoxview/tests/browser/browser_firefoxview_virtual_list.js index 501deb8e6845..bf53796ef760 100644 --- a/browser/components/firefoxview/tests/browser/browser_firefoxview_virtual_list.js +++ b/browser/components/firefoxview/tests/browser/browser_firefoxview_virtual_list.js @@ -29,7 +29,7 @@ add_task(async function test_max_render_count_on_win_resize() { "Firefox View is loaded to the Recent Browsing page." ); - await navigateToCategoryAndWait(document, "history"); + await navigateToViewAndWait(document, "history"); let historyComponent = document.querySelector("view-history"); let tabList = historyComponent.lists[0]; diff --git a/browser/components/firefoxview/tests/browser/browser_history_firefoxview.js b/browser/components/firefoxview/tests/browser/browser_history_firefoxview.js index a6c697e39854..d9189c0e5af3 100644 --- a/browser/components/firefoxview/tests/browser/browser_history_firefoxview.js +++ b/browser/components/firefoxview/tests/browser/browser_history_firefoxview.js @@ -169,7 +169,7 @@ add_task(async function test_list_ordering() { await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "history"); + await navigateToViewAndWait(document, "history"); let historyComponent = document.querySelector("view-history"); historyComponent.profileAge = 8; @@ -262,7 +262,7 @@ add_task(async function test_empty_states() { await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "history"); + await navigateToViewAndWait(document, "history"); let historyComponent = document.querySelector("view-history"); historyComponent.profileAge = 8; @@ -350,7 +350,7 @@ add_task(async function test_observers_removed_when_view_is_hidden() { ); await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "history"); + await navigateToViewAndWait(document, "history"); const historyComponent = document.querySelector("view-history"); historyComponent.profileAge = 8; let visitList = await TestUtils.waitForCondition(() => @@ -399,7 +399,7 @@ add_task(async function test_show_all_history_telemetry() { await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "history"); + await navigateToViewAndWait(document, "history"); let historyComponent = document.querySelector("view-history"); historyComponent.profileAge = 8; @@ -424,7 +424,7 @@ add_task(async function test_show_all_history_telemetry() { add_task(async function test_search_history() { await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "history"); + await navigateToViewAndWait(document, "history"); const historyComponent = document.querySelector("view-history"); historyComponent.profileAge = 8; await historyComponentReady(historyComponent); @@ -504,7 +504,7 @@ add_task(async function test_persist_collapse_card_after_view_change() { await addHistoryItems(today); await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "history"); + await navigateToViewAndWait(document, "history"); const historyComponent = document.querySelector("view-history"); historyComponent.profileAge = 8; await TestUtils.waitForCondition( @@ -529,8 +529,8 @@ add_task(async function test_persist_collapse_card_after_view_change() { ); // Switch to a new view and then back to History - await navigateToCategoryAndWait(document, "syncedtabs"); - await navigateToCategoryAndWait(document, "history"); + await navigateToViewAndWait(document, "syncedtabs"); + await navigateToViewAndWait(document, "history"); // Check that first history card is still collapsed after changing view ok( diff --git a/browser/components/firefoxview/tests/browser/browser_opentabs_cards.js b/browser/components/firefoxview/tests/browser/browser_opentabs_cards.js index d57aa3cad104..8576cdcb856d 100644 --- a/browser/components/firefoxview/tests/browser/browser_opentabs_cards.js +++ b/browser/components/firefoxview/tests/browser/browser_opentabs_cards.js @@ -21,7 +21,7 @@ add_setup(function () { async function navigateToOpenTabs(browser) { const document = browser.contentDocument; if (document.querySelector("named-deck").selectedViewName != "opentabs") { - await navigateToCategoryAndWait(browser.contentDocument, "opentabs"); + await navigateToViewAndWait(browser.contentDocument, "opentabs"); } } @@ -591,7 +591,7 @@ add_task(async function search_open_tabs_recent_browsing() { }); await openFirefoxViewTab(window).then(async viewTab => { const browser = viewTab.linkedBrowser; - await navigateToCategoryAndWait(browser.contentDocument, "recentbrowsing"); + await navigateToViewAndWait(browser.contentDocument, "recentbrowsing"); const recentBrowsing = browser.contentDocument.querySelector( "view-recentbrowsing" ); diff --git a/browser/components/firefoxview/tests/browser/browser_opentabs_firefoxview.js b/browser/components/firefoxview/tests/browser/browser_opentabs_firefoxview.js index 57d0f8d0319e..1059a09aadbb 100644 --- a/browser/components/firefoxview/tests/browser/browser_opentabs_firefoxview.js +++ b/browser/components/firefoxview/tests/browser/browser_opentabs_firefoxview.js @@ -122,7 +122,7 @@ async function moreMenuSetup() { await clickFirefoxViewButton(window); const document = window.FirefoxViewHandler.tab.linkedBrowser.contentDocument; - await navigateToCategoryAndWait(document, "opentabs"); + await navigateToViewAndWait(document, "opentabs"); let openTabs = document.querySelector("view-opentabs[name=opentabs]"); setSortOption(openTabs, "tabStripOrder"); diff --git a/browser/components/firefoxview/tests/browser/browser_opentabs_recency.js b/browser/components/firefoxview/tests/browser/browser_opentabs_recency.js index e5beb4700a33..e835996d125a 100644 --- a/browser/components/firefoxview/tests/browser/browser_opentabs_recency.js +++ b/browser/components/firefoxview/tests/browser/browser_opentabs_recency.js @@ -159,8 +159,9 @@ function getOpenTabsComponent(browser) { async function checkTabList(browser, expected) { const tabsView = getOpenTabsComponent(browser); const openTabsCard = tabsView.shadowRoot.querySelector("view-opentabs-card"); - await tabsView.getUpdateComplete(); + await tabsView.updateComplete; const tabList = openTabsCard.shadowRoot.querySelector("fxview-tab-list"); + await tabList.getUpdateComplete(); Assert.ok(tabList, "Found the tab list element"); await TestUtils.waitForCondition(() => tabList.rowEls.length); let actual = Array.from(tabList.rowEls).map(row => row.url); diff --git a/browser/components/firefoxview/tests/browser/browser_opentabs_tab_indicators.js b/browser/components/firefoxview/tests/browser/browser_opentabs_tab_indicators.js index 1375052125cd..c8efd10a5cac 100644 --- a/browser/components/firefoxview/tests/browser/browser_opentabs_tab_indicators.js +++ b/browser/components/firefoxview/tests/browser/browser_opentabs_tab_indicators.js @@ -22,7 +22,7 @@ add_task(async function test_notification_dot_indicator() { await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; let win = browser.ownerGlobal; - await navigateToCategoryAndWait(document, "opentabs"); + await navigateToViewAndWait(document, "opentabs"); // load page that opens prompt when page is hidden let openedTab = await BrowserTestUtils.openNewForegroundTab( gBrowser, @@ -48,7 +48,7 @@ add_task(async function test_notification_dot_indicator() { await openTabs.updateComplete; await TestUtils.waitForCondition( - () => openTabs.viewCards[0].tabList.rowEls[1].attention, + () => openTabs.viewCards[0].tabList.rowEls[0].attention, "The opened tab doesn't have the attention property, so no notification dot is shown." ); @@ -79,7 +79,7 @@ add_task(async function test_container_indicator() { URLs[0] ); - await navigateToCategoryAndWait(document, "opentabs"); + await navigateToViewAndWait(document, "opentabs"); let openTabs = document.querySelector("view-opentabs[name=opentabs]"); @@ -118,7 +118,7 @@ add_task(async function test_container_indicator() { add_task(async function test_sound_playing_muted_indicator() { await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "opentabs"); + await navigateToViewAndWait(document, "opentabs"); // Load a page in a container tab let soundTab = await BrowserTestUtils.openNewForegroundTab( @@ -146,7 +146,7 @@ add_task(async function test_sound_playing_muted_indicator() { "The tab list hasn't rendered." ); - let soundPlayingTabElem = openTabs.viewCards[0].tabList.rowEls[1]; + let soundPlayingTabElem = openTabs.viewCards[0].tabList.rowEls[0]; await TestUtils.waitForCondition(() => soundPlayingTabElem.soundPlaying); diff --git a/browser/components/firefoxview/tests/browser/browser_recentlyclosed_firefoxview.js b/browser/components/firefoxview/tests/browser/browser_recentlyclosed_firefoxview.js index 313d86416ea8..fcfcf205622e 100644 --- a/browser/components/firefoxview/tests/browser/browser_recentlyclosed_firefoxview.js +++ b/browser/components/firefoxview/tests/browser/browser_recentlyclosed_firefoxview.js @@ -196,7 +196,7 @@ add_task(async function test_initial_closed_tab() { await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; is(document.location.href, getFirefoxViewURL()); - await navigateToCategoryAndWait(document, "recentlyclosed"); + await navigateToViewAndWait(document, "recentlyclosed"); let { cleanup } = await prepareSingleClosedTab(); await switchToFxViewTab(window); let [listItems] = await waitForRecentlyClosedTabsList(document); @@ -220,7 +220,7 @@ add_task(async function test_list_ordering() { await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; await clearAllParentTelemetryEvents(); - navigateToCategory(document, "recentlyclosed"); + await navigateToViewAndWait(document, "recentlyclosed"); let [cardMainSlotNode, listItems] = await waitForRecentlyClosedTabsList( document ); @@ -248,7 +248,7 @@ add_task(async function test_list_updates() { await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - navigateToCategory(document, "recentlyclosed"); + await navigateToViewAndWait(document, "recentlyclosed"); let [listElem, listItems] = await waitForRecentlyClosedTabsList(document); Assert.deepEqual( @@ -321,7 +321,7 @@ add_task(async function test_restore_tab() { await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - navigateToCategory(document, "recentlyclosed"); + await navigateToViewAndWait(document, "recentlyclosed"); let [listElem, listItems] = await waitForRecentlyClosedTabsList(document); Assert.deepEqual( @@ -365,7 +365,7 @@ add_task(async function test_dismiss_tab() { await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - navigateToCategory(document, "recentlyclosed"); + await navigateToViewAndWait(document, "recentlyclosed"); let [listElem, listItems] = await waitForRecentlyClosedTabsList(document); await clearAllParentTelemetryEvents(); @@ -429,7 +429,7 @@ add_task(async function test_empty_states() { const { document } = browser.contentWindow; is(document.location.href, "about:firefoxview"); - navigateToCategory(document, "recentlyclosed"); + await navigateToViewAndWait(document, "recentlyclosed"); let recentlyClosedComponent = document.querySelector( "view-recentlyclosed:not([slot=recentlyclosed])" ); @@ -479,7 +479,7 @@ add_task(async function test_observers_removed_when_view_is_hidden() { await withFirefoxView({}, async function (browser) { const { document } = browser.contentWindow; - navigateToCategory(document, "recentlyclosed"); + await navigateToViewAndWait(document, "recentlyclosed"); const [listElem] = await waitForRecentlyClosedTabsList(document); is(listElem.rowEls.length, 1); @@ -510,7 +510,7 @@ add_task(async function test_search() { let { cleanup, expectedURLs } = await prepareClosedTabs(); await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - navigateToCategory(document, "recentlyclosed"); + await navigateToViewAndWait(document, "recentlyclosed"); const [listElem] = await waitForRecentlyClosedTabsList(document); const recentlyClosedComponent = document.querySelector( "view-recentlyclosed:not([slot=recentlyclosed])" @@ -569,7 +569,7 @@ add_task(async function test_search_recent_browsing() { const { document } = browser.contentWindow; info("Input a search query."); - await navigateToCategoryAndWait(document, "recentbrowsing"); + await navigateToViewAndWait(document, "recentbrowsing"); const recentBrowsing = document.querySelector("view-recentbrowsing"); EventUtils.synthesizeMouseAtCenter( recentBrowsing.searchTextbox, diff --git a/browser/components/firefoxview/tests/browser/browser_syncedtabs_errors_firefoxview.js b/browser/components/firefoxview/tests/browser/browser_syncedtabs_errors_firefoxview.js index 15dba68551ff..86e4d9cdeeb9 100644 --- a/browser/components/firefoxview/tests/browser/browser_syncedtabs_errors_firefoxview.js +++ b/browser/components/firefoxview/tests/browser/browser_syncedtabs_errors_firefoxview.js @@ -56,7 +56,7 @@ add_task(async function test_network_offline() { sandbox.spy(TabsSetupFlowManager, "tryToClearError"); await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "syncedtabs"); + await navigateToViewAndWait(document, "syncedtabs"); Services.obs.notifyObservers(null, UIState.ON_UPDATE); Services.obs.notifyObservers( @@ -112,7 +112,7 @@ add_task(async function test_sync_error() { const sandbox = await setupWithDesktopDevices(); await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "syncedtabs"); + await navigateToViewAndWait(document, "syncedtabs"); Services.obs.notifyObservers(null, UIState.ON_UPDATE); Services.obs.notifyObservers(null, "weave:service:sync:error"); diff --git a/browser/components/firefoxview/tests/browser/browser_syncedtabs_firefoxview.js b/browser/components/firefoxview/tests/browser/browser_syncedtabs_firefoxview.js index 8a3c63985bf7..11f135cd522d 100644 --- a/browser/components/firefoxview/tests/browser/browser_syncedtabs_firefoxview.js +++ b/browser/components/firefoxview/tests/browser/browser_syncedtabs_firefoxview.js @@ -29,7 +29,7 @@ add_task(async function test_unconfigured_initial_state() { }); await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "syncedtabs"); + await navigateToViewAndWait(document, "syncedtabs"); Services.obs.notifyObservers(null, UIState.ON_UPDATE); let syncedTabsComponent = document.querySelector( @@ -85,7 +85,7 @@ add_task(async function test_signed_in() { await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "syncedtabs"); + await navigateToViewAndWait(document, "syncedtabs"); Services.obs.notifyObservers(null, UIState.ON_UPDATE); let syncedTabsComponent = document.querySelector( @@ -148,7 +148,7 @@ add_task(async function test_no_synced_tabs() { await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "syncedtabs"); + await navigateToViewAndWait(document, "syncedtabs"); Services.obs.notifyObservers(null, UIState.ON_UPDATE); let syncedTabsComponent = document.querySelector( @@ -188,7 +188,7 @@ add_task(async function test_no_error_for_two_desktop() { await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "syncedtabs"); + await navigateToViewAndWait(document, "syncedtabs"); Services.obs.notifyObservers(null, UIState.ON_UPDATE); let syncedTabsComponent = document.querySelector( @@ -232,7 +232,7 @@ add_task(async function test_empty_state() { await withFirefoxView({ openNewWindow: true }, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "syncedtabs"); + await navigateToViewAndWait(document, "syncedtabs"); Services.obs.notifyObservers(null, UIState.ON_UPDATE); let syncedTabsComponent = document.querySelector( @@ -277,7 +277,7 @@ add_task(async function test_tabs() { await withFirefoxView({ openNewWindow: true }, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "syncedtabs"); + await navigateToViewAndWait(document, "syncedtabs"); Services.obs.notifyObservers(null, UIState.ON_UPDATE); let syncedTabsComponent = document.querySelector( @@ -365,7 +365,7 @@ add_task(async function test_empty_desktop_same_name() { await withFirefoxView({ openNewWindow: true }, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "syncedtabs"); + await navigateToViewAndWait(document, "syncedtabs"); Services.obs.notifyObservers(null, UIState.ON_UPDATE); let syncedTabsComponent = document.querySelector( @@ -413,7 +413,7 @@ add_task(async function test_empty_desktop_same_name_three() { await withFirefoxView({ openNewWindow: true }, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "syncedtabs"); + await navigateToViewAndWait(document, "syncedtabs"); Services.obs.notifyObservers(null, UIState.ON_UPDATE); let syncedTabsComponent = document.querySelector( @@ -459,7 +459,7 @@ add_task(async function search_synced_tabs() { await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "syncedtabs"); + await navigateToViewAndWait(document, "syncedtabs"); Services.obs.notifyObservers(null, UIState.ON_UPDATE); let syncedTabsComponent = document.querySelector( @@ -666,7 +666,7 @@ add_task(async function search_synced_tabs_recent_browsing() { }); await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; - await navigateToCategoryAndWait(document, "recentbrowsing"); + await navigateToViewAndWait(document, "recentbrowsing"); Services.obs.notifyObservers(null, UIState.ON_UPDATE); const recentBrowsing = document.querySelector("view-recentbrowsing"); diff --git a/browser/components/firefoxview/tests/browser/head.js b/browser/components/firefoxview/tests/browser/head.js index 875dc3c36cb8..64560e524a0c 100644 --- a/browser/components/firefoxview/tests/browser/head.js +++ b/browser/components/firefoxview/tests/browser/head.js @@ -548,31 +548,19 @@ registerCleanupFunction(() => { gSandbox?.restore(); }); -function navigateToCategory(document, category) { - const navigation = document.querySelector("fxview-category-navigation"); - let navButton = Array.from(navigation.categoryButtons).filter( - categoryButton => { - return categoryButton.name === category; - } - )[0]; - navButton.buttonEl.click(); -} - -async function navigateToCategoryAndWait(document, category) { - info(`navigateToCategoryAndWait, for ${category}`); - const navigation = document.querySelector("fxview-category-navigation"); +async function navigateToViewAndWait(document, view) { + info(`navigateToViewAndWait, for ${view}`); + const navigation = document.querySelector("moz-page-nav"); const win = document.ownerGlobal; SimpleTest.promiseFocus(win); - let navButton = Array.from(navigation.categoryButtons).find( - categoryButton => { - return categoryButton.name === category; - } - ); + let navButton = Array.from(navigation.pageNavButtons).find(pageNavButton => { + return pageNavButton.view === view; + }); const namedDeck = document.querySelector("named-deck"); await BrowserTestUtils.waitForCondition( () => navButton.getBoundingClientRect().height, - `Waiting for ${category} button to be clickable` + `Waiting for ${view} button to be clickable` ); EventUtils.synthesizeMouseAtCenter(navButton, {}, win); @@ -582,10 +570,10 @@ async function navigateToCategoryAndWait(document, category) { child => child.slot == "selected" ); return ( - namedDeck.selectedViewName == category && + namedDeck.selectedViewName == view && selectedView?.getBoundingClientRect().height ); - }, `Waiting for ${category} to be visible`); + }, `Waiting for ${view} to be visible`); } /** diff --git a/browser/components/firefoxview/tests/chrome/chrome.toml b/browser/components/firefoxview/tests/chrome/chrome.toml index b1677430b269..3edeefd4a9b6 100644 --- a/browser/components/firefoxview/tests/chrome/chrome.toml +++ b/browser/components/firefoxview/tests/chrome/chrome.toml @@ -2,6 +2,4 @@ ["test_card_container.html"] -["test_fxview_category_navigation.html"] - ["test_fxview_tab_list.html"] diff --git a/browser/components/firefoxview/tests/chrome/test_fxview_category_navigation.html b/browser/components/firefoxview/tests/chrome/test_fxview_category_navigation.html deleted file mode 100644 index 0ea0a94baf6e..000000000000 --- a/browser/components/firefoxview/tests/chrome/test_fxview_category_navigation.html +++ /dev/null @@ -1,322 +0,0 @@ - - - - - FxviewCategoryNavigation Tests - - - - - - - - -

-
- -
-

-
-
-
diff --git a/browser/components/storybook/stories/fxview-category-navigation.stories.mjs b/browser/components/storybook/stories/fxview-category-navigation.stories.mjs
deleted file mode 100644
index 74a379a5a802..000000000000
--- a/browser/components/storybook/stories/fxview-category-navigation.stories.mjs
+++ /dev/null
@@ -1,113 +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/. */
-
-import { html } from "lit.all.mjs";
-// eslint-disable-next-line import/no-unassigned-import
-import "browser/components/firefoxview/fxview-category-navigation.mjs";
-
-export default {
-  title: "Domain-specific UI Widgets/Firefox View/Category Navigation",
-  component: "fxview-category-navigation",
-  parameters: {
-    status: "in-development",
-    actions: {
-      handles: ["change-category"],
-    },
-    fluent: `
-fxview-category-button-one = Category 1
-  .title = Category 1
-fxview-category-button-two = Category 2
-  .title = Category 2
-fxview-category-button-three = Category 3
-  .title = Category 3
-fxview-category-footer-button = Settings
-  .title = Settings
-     `,
-  },
-};
-
-const Template = () => html`
-  
-  
- -

Header

- - - - - - - -
-
-`; - -export const Default = Template.bind({}); -Default.args = {}; diff --git a/browser/locales/en-US/browser/firefoxView.ftl b/browser/locales/en-US/browser/firefoxView.ftl index 2057066d4911..c2e5f36d2ff4 100644 --- a/browser/locales/en-US/browser/firefoxView.ftl +++ b/browser/locales/en-US/browser/firefoxView.ftl @@ -12,6 +12,9 @@ menu-tools-firefox-view = firefoxview-page-title = { -firefoxview-brand-name } +firefoxview-page-heading = + .heading = { -firefoxview-brand-name } + firefoxview-page-label = .label = { -firefoxview-brand-name } diff --git a/toolkit/content/jar.mn b/toolkit/content/jar.mn index 8b18c945253d..2aa0c0ecf12d 100644 --- a/toolkit/content/jar.mn +++ b/toolkit/content/jar.mn @@ -95,6 +95,9 @@ toolkit.jar: content/global/elements/moz-button-group.mjs (widgets/moz-button-group/moz-button-group.mjs) content/global/elements/moz-card.css (widgets/moz-card/moz-card.css) content/global/elements/moz-card.mjs (widgets/moz-card/moz-card.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) content/global/elements/moz-five-star.css (widgets/moz-five-star/moz-five-star.css) content/global/elements/moz-five-star.mjs (widgets/moz-five-star/moz-five-star.mjs) content/global/elements/moz-input-box.js (widgets/moz-input-box.js) diff --git a/toolkit/content/tests/widgets/chrome.toml b/toolkit/content/tests/widgets/chrome.toml index af2c778947d7..a8bb7cd3159e 100644 --- a/toolkit/content/tests/widgets/chrome.toml +++ b/toolkit/content/tests/widgets/chrome.toml @@ -32,6 +32,8 @@ skip-if = ["os == 'mac'"] ["test_moz_message_bar.html"] +["test_moz_page_nav.html"] + ["test_moz_support_link.html"] ["test_moz_toggle.html"] @@ -65,4 +67,5 @@ skip-if = [ "os == 'android'", "os == 'linux' && debug", # Bug 1765783 ] + ["test_videocontrols_onclickplay.html"] diff --git a/toolkit/content/tests/widgets/test_moz_page_nav.html b/toolkit/content/tests/widgets/test_moz_page_nav.html new file mode 100644 index 000000000000..604df7c024f8 --- /dev/null +++ b/toolkit/content/tests/widgets/test_moz_page_nav.html @@ -0,0 +1,306 @@ + + + + + MozPageNav Tests + + + + + + + + +

+
+ +
+

+
+
+
diff --git a/browser/components/firefoxview/fxview-category-button.css b/toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css
similarity index 66%
rename from browser/components/firefoxview/fxview-category-button.css
rename to toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css
index 1bce29f3435f..2975bb1a7cc5 100644
--- a/browser/components/firefoxview/fxview-category-button.css
+++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css
@@ -3,12 +3,15 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 :host {
-  border-radius: 4px;
+  border-radius: var(--border-radius-small);
+  &:focus-visible {
+    outline-offset: var(--page-nav-focus-outline-inset);
+  }
 }
 
 button {
-  background-color: initial;
-  border: 1px solid var(--in-content-primary-button-border-color);
+  background-color: var(--page-nav-button-background-color);
+  border: 1px solid var(--page-nav-border-color);
   -moz-context-properties: fill, fill-opacity;
   fill: currentColor;
   display: grid;
@@ -18,11 +21,11 @@ button {
   font-size: inherit;
   width: 100%;
   font-weight: normal;
-  border-radius: 4px;
-  color: inherit;
+  border-radius: var(--page-nav-button-border-radius);
+  color: var(--page-nav-button-text-color);
   text-align: start;
   transition: background-color 150ms;
-  padding: var(--fxviewcategorynav-button-padding);
+  padding: var(--page-nav-button-padding);
 }
 
 button:hover {
@@ -38,8 +41,7 @@ button:hover {
 
   button:hover,
   button[selected]:hover {
-    background-color: var(--in-content-button-background-hover);
-    border-color: var(--in-content-button-border-color-hover);
+    background-color: var(--page-nav-button-background-color-hover);
   }
 
   button[selected]:hover {
@@ -57,15 +59,15 @@ button:hover {
   }
 
   button[selected]:not(:hover) {
-    color: var(--in-content-accent-color);
-    background-color: color-mix(in srgb, var(--fxview-primary-action-background) 5%, transparent);
-    border-inline-start-color: var(--in-content-accent-color);
+    color: var(--color-accent-primary);
+    background-color: color-mix(in srgb, var(--page-nav-button-background-color-selected) 5%, transparent);
+    border-inline-start-color: var(--color-accent-primary);
   }
 }
 
 @media (prefers-color-scheme: dark) {
   button[selected] {
-    background-color: color-mix(in srgb, var(--fxview-primary-action-background) 12%, transparent);
+    background-color: color-mix(in srgb, var(--page-nav-button-background-color-selected) 12%, transparent);
   }
 }
 
@@ -73,13 +75,10 @@ button:focus-visible,
 button[selected]:focus-visible {
   outline: var(--focus-outline);
   outline-offset: var(--focus-outline-offset);
+  border-radius: var(--border-radius-small);
 }
 
-.category-icon {
-  background-color: initial;
-  background-size: 20px;
-  background-repeat: no-repeat;
-  background-position: center;
+.page-nav-icon {
   height: 20px;
   width: 20px;
   -moz-context-properties: fill;
@@ -90,7 +89,7 @@ button[selected]:focus-visible {
   button {
     transition: none;
     border-color: ButtonText;
-    background-color: var(--in-content-button-background);
+    background-color: var(--button-background-color);
   }
 
   button:hover {
@@ -105,8 +104,7 @@ button[selected]:focus-visible {
 }
 
 slot {
-  font-size: 1.13em;
-  line-height: 1.4;
+  font-size: var(--font-size-large);
   margin: 0;
   padding-inline-start: 0;
   user-select: none;
diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav.css b/toolkit/content/widgets/moz-page-nav/moz-page-nav.css
new file mode 100644
index 000000000000..49000f622d73
--- /dev/null
+++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav.css
@@ -0,0 +1,76 @@
+/* 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/. */
+
+:host {
+  --page-nav-button-border-radius: var(--button-border-radius);
+  --page-nav-button-text-color: var(--button-text-color);
+  --page-nav-button-background-color: transparent;
+  --page-nav-button-background-color-hover: var(--button-background-color-hover);
+  --page-nav-button-background-color-selected: var(--color-accent-primary);
+  --page-nav-button-padding: var(--space-small);
+  --page-nav-margin-top: 72px;
+  --page-nav-margin-bottom: 36px;
+  --page-nav-gap: 25px;
+  --page-nav-button-gap: var(--space-xsmall);
+  --page-nav-border-color: var(--border-color);
+  --page-nav-focus-outline-inset: var(--focus-outline-inset);
+  --page-nav-width: 240px;
+  margin-inline-start: 42px;
+  position: sticky;
+  top: 0;
+  height: 100vh;
+  width: var(--page-nav-width);
+
+  @media (prefers-reduced-motion) {
+    /* (See Bug 1610081) Setting border-inline-end to add clear differentiation between side navigation and main content area */
+    border-inline-end: 1px solid var(--page-nav-border-color);
+  }
+
+  @media (max-width: 52rem) {
+    grid-template-rows: 1fr auto;
+    --page-nav-width: 118px;
+  }
+}
+
+nav {
+  display: grid;
+  grid-template-rows: min-content 1fr auto;
+  gap: var(--page-nav-gap);
+  margin-block: var(--page-nav-margin-top) var(--page-nav-margin-bottom);
+  height: calc(100vh - var(--page-nav-margin-top) - var(--page-nav-margin-bottom));
+  @media (max-width: 52rem) {
+    grid-template-rows: 1fr auto;
+  }
+}
+
+.page-nav-header {
+  /* Align the header text/icon with the page nav button icons */
+  margin-inline-start: var(--page-nav-button-padding);
+  font-size: var(--font-size-xlarge);
+  font-weight: var(--font-weight-bold);
+  margin-block: 0;
+
+  @media (max-width: 52rem) {
+    display: none;
+  }
+}
+
+.primary-nav-group,
+#secondary-nav-group {
+  display: grid;
+  grid-template-columns: 1fr;
+  grid-auto-rows: min-content;
+  gap: var(--page-nav-button-gap);
+
+  @media (max-width: 52rem) {
+    justify-content: center;
+    grid-template-columns: min-content;
+  }
+}
+
+@media (prefers-contrast) {
+  .primary-nav-group {
+    gap: var(--space-small);
+  }
+}
diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs b/toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs
new file mode 100644
index 000000000000..f998ee735fa1
--- /dev/null
+++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs
@@ -0,0 +1,170 @@
+/* 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 "chrome://global/content/vendor/lit.all.mjs";
+import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
+
+/**
+ * A grouping of navigation buttons that is displayed at the page level,
+ * intended to change the selected view, provide a heading, and have links
+ * to external resources.
+ *
+ * @tagname moz-page-nav
+ * @property {string} currentView - The currently selected view.
+ * @property {string} heading - A heading to be displayed at the top of the navigation.
+ * @slot [default] - Used to append moz-page-nav-button elements to the navigation.
+ */
+export default class MozPageNav extends MozLitElement {
+  static properties = {
+    currentView: { type: String },
+    heading: { type: String },
+  };
+
+  static queries = {
+    headingEl: "#page-nav-header",
+    primaryNavGroupSlot: ".primary-nav-group slot",
+    secondaryNavGroupSlot: "#secondary-nav-group slot",
+  };
+
+  get pageNavButtons() {
+    return this.primaryNavGroupSlot
+      .assignedNodes()
+      .filter(
+        node => node?.localName === "moz-page-nav-button" && !node.hidden
+      );
+  }
+
+  onChangeView(e) {
+    this.currentView = e.target.view;
+  }
+
+  handleFocus(e) {
+    if (e.key == "ArrowDown" || e.key == "ArrowRight") {
+      e.preventDefault();
+      this.focusNextView();
+    } else if (e.key == "ArrowUp" || e.key == "ArrowLeft") {
+      e.preventDefault();
+      this.focusPreviousView();
+    }
+  }
+
+  focusPreviousView() {
+    let pageNavButtons = this.pageNavButtons;
+    let currentIndex = pageNavButtons.findIndex(b => b.selected);
+    let prev = pageNavButtons[currentIndex - 1];
+    if (prev) {
+      prev.activate();
+      prev.buttonEl.focus();
+    }
+  }
+
+  focusNextView() {
+    let pageNavButtons = this.pageNavButtons;
+    let currentIndex = pageNavButtons.findIndex(b => b.selected);
+    let next = pageNavButtons[currentIndex + 1];
+    if (next) {
+      next.activate();
+      next.buttonEl.focus();
+    }
+  }
+
+  render() {
+    return html`
+      
+      
+    `;
+  }
+
+  updated() {
+    let isViewSelected = false;
+    let assignedPageNavButtons = this.pageNavButtons;
+    for (let button of assignedPageNavButtons) {
+      button.selected = button.view == this.currentView;
+      isViewSelected = isViewSelected || button.selected;
+    }
+    if (!isViewSelected && assignedPageNavButtons.length) {
+      // Current page nav has no matching view, reset to the first view.
+      assignedPageNavButtons[0].activate();
+    }
+  }
+}
+customElements.define("moz-page-nav", MozPageNav);
+
+/**
+ * A navigation button intended to change the selected view within a page.
+ *
+ * @tagname moz-page-nav-button
+ * @property {string} iconSrc - The chrome:// url for the icon used for the button.
+ * @property {string} l10nId - The fluent ID for the button's text
+ * @property {boolean} selected - Whether or not the button is currently selected.
+ * @slot [default] - Used to append the l10n string to the button.
+ */
+export class MozPageNavButton extends MozLitElement {
+  static properties = {
+    iconSrc: { type: String },
+    l10nId: { type: String },
+    selected: { type: Boolean },
+  };
+
+  connectedCallback() {
+    super.connectedCallback();
+    this.setAttribute("role", "none");
+  }
+
+  static queries = {
+    buttonEl: "button",
+  };
+
+  get view() {
+    return this.getAttribute("view");
+  }
+
+  activate() {
+    this.dispatchEvent(
+      new CustomEvent("change-view", {
+        bubbles: true,
+        composed: true,
+      })
+    );
+  }
+
+  render() {
+    return html`
+      
+      
+    `;
+  }
+}
+customElements.define("moz-page-nav-button", MozPageNavButton);
diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav.stories.mjs b/toolkit/content/widgets/moz-page-nav/moz-page-nav.stories.mjs
new file mode 100644
index 000000000000..4ac7b455cf64
--- /dev/null
+++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav.stories.mjs
@@ -0,0 +1,77 @@
+/* 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";
+// eslint-disable-next-line import/no-unassigned-import
+import "./moz-page-nav.mjs";
+
+export default {
+  title: "UI Widgets/Page Nav",
+  component: "moz-page-nav",
+  parameters: {
+    status: "in-development",
+    actions: {
+      handles: ["change-view"],
+    },
+    fluent: `
+moz-page-nav-button-one = View 1
+  .title = View 1
+moz-page-nav-button-two = View 2
+  .title = View 2
+moz-page-nav-button-three = View 3
+  .title = View 3
+moz-page-link-one = Support Page
+  .title = Support Page
+moz-page-nav-heading =
+  .heading = Heading
+     `,
+  },
+};
+
+const Template = () => html`
+  
+  
+ + + + + + + + +
+
+`; + +export const Default = Template.bind({}); +Default.args = {};