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
This commit is contained in:
Kelly Cochrane 2024-02-09 22:01:30 +00:00
parent c056a64e2e
commit fa0e1f01ae
39 changed files with 871 additions and 899 deletions

View file

@ -10,12 +10,21 @@ const {
"resource://testing-common/FirefoxViewTestUtils.sys.mjs" "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() { add_task(async function test_data_l10n_customize_mode() {
FirefoxViewTestUtilsInit(this); FirefoxViewTestUtilsInit(this);
await withFirefoxView({ win: window }, async function (browser) { await withFirefoxView({ win: window }, async function (browser) {
/** /**
* Bug 1856572, Bug 1857622: Without requesting two animation frames * Bug 1856572, Bug 1857622: Without requesting two animation frames
* the "missing Fluent strings" issue will not reproduce. * 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 => await new Promise(r =>
requestAnimationFrame(() => requestAnimationFrame(r)) requestAnimationFrame(() => requestAnimationFrame(r))
@ -23,39 +32,47 @@ add_task(async function test_data_l10n_customize_mode() {
await startCustomizing(); await startCustomizing();
await endCustomizing(); await endCustomizing();
await openFirefoxViewTab(window); await openFirefoxViewTab(window);
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
let header = document.querySelector("h1"); let secondPageNavButton = document.querySelectorAll(
document.l10n.setAttributes(header, "firefoxview-overview-header"); "moz-page-nav-button"
)[0];
document.l10n.setAttributes(
secondPageNavButton,
"firefoxview-overview-header"
);
let previousText = await document.l10n.formatValue( 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 * This should be replaced with
* BrowserTestUtils.waitForMutationCondition(header, {characterData: true}, ...) * BrowserTestUtils.waitForMutationCondition(secondPageNavButton, {characterData: true}, ...)
* but apparently Fluent manipulation of textContent doesn't result * but apparently Fluent manipulation of textContent doesn't result
* in a characterData mutation occurring. * in a characterData mutation occurring.
*/ */
await BrowserTestUtils.waitForCondition(() => { await BrowserTestUtils.waitForCondition(() => {
return header.textContent != previousText; return (
secondPageNavButton.textContent !== previousText &&
secondPageNavButton.textContent === translatedText
);
}, "waiting for text content to change"); }, "waiting for text content to change");
Assert.equal( Assert.equal(
header.getAttribute("data-l10n-id"), secondPageNavButton.getAttribute("data-l10n-id"),
"firefoxview-overview-header", "firefoxview-overview-header",
"data-l10n-id should be updated" "data-l10n-id should be updated"
); );
Assert.notEqual( Assert.notEqual(
previousText, previousText,
header.textContent, secondPageNavButton.textContent,
"The header's text content should be updated" "The second page-nav button text content should be updated"
);
let translatedText = await window.content.document.l10n.formatValue(
"firefoxview-overview-header"
); );
Assert.equal( Assert.equal(
translatedText, translatedText,
header.textContent, secondPageNavButton.textContent,
"The changed text should be the translated value of 'firefoxview-overview-header" "The changed text should be the translated value of 'firefoxview-overview-header"
); );
}); });

View file

Before

Width:  |  Height:  |  Size: 655 B

After

Width:  |  Height:  |  Size: 655 B

View file

Before

Width:  |  Height:  |  Size: 558 B

After

Width:  |  Height:  |  Size: 558 B

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

Before

Width:  |  Height:  |  Size: 658 B

After

Width:  |  Height:  |  Size: 658 B

View file

Before

Width:  |  Height:  |  Size: 743 B

After

Width:  |  Height:  |  Size: 743 B

View file

@ -69,6 +69,10 @@ body {
grid-template-columns: var(--fxview-sidebar-width) 1fr; grid-template-columns: var(--fxview-sidebar-width) 1fr;
background-color: var(--fxview-background-color); background-color: var(--fxview-background-color);
color: var(--fxview-text-primary-color); color: var(--fxview-text-primary-color);
@media (max-width: 52rem) {
display: flex;
}
} }
.main-container { .main-container {
@ -88,26 +92,6 @@ body {
margin: 0; 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) { fxview-tab-list.with-dismiss-button::part(secondary-button) {
background-image: url("chrome://global/skin/icons/close.svg"); background-image: url("chrome://global/skin/icons/close.svg");
} }
@ -174,14 +158,6 @@ panel-list {
overflow-y: visible; 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"] { fxview-empty-state:not([isSelectedTab]) button[slot="primary-action"] {
margin-inline-start: 0; margin-inline-start: 0;
} }

View file

@ -41,54 +41,51 @@
></script> ></script>
<script <script
type="module" type="module"
src="chrome://browser/content/firefoxview/fxview-category-navigation.mjs" src="chrome://browser/content/firefoxview/syncedtabs.mjs"
></script> ></script>
<script <script
type="module" type="module"
src="chrome://browser/content/firefoxview/syncedtabs.mjs" src="chrome://global/content/elements/moz-page-nav.mjs"
></script> ></script>
<script src="chrome://browser/content/contentTheme.js"></script> <script src="chrome://browser/content/contentTheme.js"></script>
</head> </head>
<body> <body>
<fxview-category-navigation> <moz-page-nav
<h1 slot="category-nav-header" data-l10n-id="firefoxview-page-title"></h1> data-l10n-id="firefoxview-page-heading"
<fxview-category-button data-l10n-attrs="heading"
class="category" >
slot="category-button" <moz-page-nav-button
name="recentbrowsing" view="recentbrowsing"
data-l10n-id="firefoxview-overview-nav" data-l10n-id="firefoxview-overview-nav"
iconSrc="chrome://browser/content/firefoxview/view-recentbrowsing.svg"
> >
</fxview-category-button> </moz-page-nav-button>
<fxview-category-button <moz-page-nav-button
class="category" view="opentabs"
slot="category-button"
name="opentabs"
data-l10n-id="firefoxview-opentabs-nav" data-l10n-id="firefoxview-opentabs-nav"
iconSrc="chrome://browser/content/firefoxview/view-opentabs.svg"
> >
</fxview-category-button> </moz-page-nav-button>
<fxview-category-button <moz-page-nav-button
class="category" view="recentlyclosed"
slot="category-button"
name="recentlyclosed"
data-l10n-id="firefoxview-recently-closed-nav" data-l10n-id="firefoxview-recently-closed-nav"
iconSrc="chrome://browser/content/firefoxview/view-recentlyclosed.svg"
> >
</fxview-category-button> </moz-page-nav-button>
<fxview-category-button <moz-page-nav-button
class="category" view="syncedtabs"
slot="category-button"
name="syncedtabs"
data-l10n-id="firefoxview-synced-tabs-nav" data-l10n-id="firefoxview-synced-tabs-nav"
iconSrc="chrome://browser/content/firefoxview/view-syncedtabs.svg"
> >
</fxview-category-button> </moz-page-nav-button>
<fxview-category-button <moz-page-nav-button
class="category" view="history"
slot="category-button"
name="history"
data-l10n-id="firefoxview-history-nav" data-l10n-id="firefoxview-history-nav"
iconSrc="chrome://browser/content/firefoxview/view-history.svg"
> >
</fxview-category-button> </moz-page-nav-button>
</fxview-category-navigation> </moz-page-nav>
<main id="pages" role="application" data-l10n-id="firefoxview-page-label"> <main id="pages" role="application" data-l10n-id="firefoxview-page-label">
<div class="main-container"> <div class="main-container">
<named-deck> <named-deck>

View file

@ -3,35 +3,35 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
let pageList = []; let pageList = [];
let categoryPagesDeck = null; let viewsDeck = null;
let categoryNavigation = null; let pageNav = null;
let activeComponent = null; let activeComponent = null;
let searchKeyboardShortcut = null; let searchKeyboardShortcut = null;
const { topChromeWindow } = window.browsingContext; const { topChromeWindow } = window.browsingContext;
function onHashChange() { function onHashChange() {
let page = document.location?.hash.substring(1); let view = document.location?.hash.substring(1);
if (!page || !pageList.includes(page)) { if (!view || !pageList.includes(view)) {
page = "recentbrowsing"; view = "recentbrowsing";
} }
changePage(page); changeView(view);
} }
function changePage(page) { function changeView(view) {
categoryPagesDeck.selectedViewName = page; viewsDeck.selectedViewName = view;
categoryNavigation.currentCategory = page; pageNav.currentPage = view;
if (categoryNavigation.categoryButtons.includes(document.activeElement)) { if (pageNav.pageNavButtons.includes(document.activeElement)) {
let currentCategoryButton = categoryNavigation.categoryButtons.find( let currentPageButton = pageNav.pageNavButtons.find(
categoryButton => categoryButton.name === page pageButton => pageButton.view === view
); );
(currentCategoryButton || categoryNavigation.categoryButtons[0]).focus(); (currentPageButton || pageNav.pageNavButtons[0]).focus();
} }
} }
function onPagesDeckViewChange() { function onViewsDeckViewChange() {
for (const child of categoryPagesDeck.children) { for (const child of viewsDeck.children) {
if (child.getAttribute("name") == categoryPagesDeck.selectedViewName) { if (child.getAttribute("name") == viewsDeck.selectedViewName) {
child.enter(); child.enter();
activeComponent = child; activeComponent = child;
} else { } else {
@ -41,11 +41,11 @@ function onPagesDeckViewChange() {
} }
function recordNavigationTelemetry(source, eventTarget) { function recordNavigationTelemetry(source, eventTarget) {
let page = "recentbrowsing"; let view = "recentbrowsing";
if (source === "category-navigation") { if (source === "category-navigation") {
page = eventTarget.parentNode.currentCategory; view = eventTarget.parentNode.currentView;
} else if (source === "view-all") { } else if (source === "view-all") {
page = eventTarget.shortPageName; view = eventTarget.shortPageName;
} }
// Record telemetry // Record telemetry
Services.telemetry.recordEvent( Services.telemetry.recordEvent(
@ -54,7 +54,7 @@ function recordNavigationTelemetry(source, eventTarget) {
"navigation", "navigation",
null, null,
{ {
page, page: view,
source, source,
} }
); );
@ -73,7 +73,7 @@ async function updateSearchTextboxSize() {
const placeholder = msg.attributes[0].value; const placeholder = msg.attributes[0].value;
maxLength = Math.max(maxLength, placeholder.length); maxLength = Math.max(maxLength, placeholder.length);
} }
for (const child of categoryPagesDeck.children) { for (const child of viewsDeck.children) {
child.searchTextboxSize = maxLength; child.searchTextboxSize = maxLength;
} }
} }
@ -89,15 +89,15 @@ async function updateSearchKeyboardShortcut() {
window.addEventListener("DOMContentLoaded", async () => { window.addEventListener("DOMContentLoaded", async () => {
recordEnteredTelemetry(); recordEnteredTelemetry();
categoryNavigation = document.querySelector("fxview-category-navigation"); pageNav = document.querySelector("moz-page-nav");
categoryPagesDeck = document.querySelector("named-deck"); viewsDeck = document.querySelector("named-deck");
for (const item of categoryNavigation.categoryButtons) { for (const item of pageNav.pageNavButtons) {
pageList.push(item.getAttribute("name")); pageList.push(item.getAttribute("view"));
} }
window.addEventListener("hashchange", onHashChange); window.addEventListener("hashchange", onHashChange);
window.addEventListener("change-category", function (event) { window.addEventListener("change-view", function (event) {
location.hash = event.target.getAttribute("name"); location.hash = event.target.getAttribute("view");
window.scrollTo(0, 0); window.scrollTo(0, 0);
recordNavigationTelemetry("category-navigation", event.target); recordNavigationTelemetry("category-navigation", event.target);
}); });
@ -105,11 +105,11 @@ window.addEventListener("DOMContentLoaded", async () => {
recordNavigationTelemetry("view-all", event.originalTarget); recordNavigationTelemetry("view-all", event.originalTarget);
}); });
categoryPagesDeck.addEventListener("view-changed", onPagesDeckViewChange); viewsDeck.addEventListener("view-changed", onViewsDeckViewChange);
// set the initial state // set the initial state
onHashChange(); onHashChange();
onPagesDeckViewChange(); onViewsDeckViewChange();
await updateSearchTextboxSize(); await updateSearchTextboxSize();
await updateSearchKeyboardShortcut(); await updateSearchKeyboardShortcut();

View file

@ -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;
}
}

View file

@ -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`
<link
rel="stylesheet"
href="chrome://browser/content/firefoxview/fxview-category-navigation.css"
/>
<nav>
<div class="category-nav-header">
<slot name="category-nav-header"></slot>
</div>
<div
class="category-nav-buttons"
role="tablist"
aria-orientation="vertical"
>
<slot
name="category-button"
@change-category=${this.onChangeCategory}
@keydown=${this.handleFocus}
></slot>
</div>
<div class="category-nav-footer">
<slot name="category-nav-footer"></slot>
</div>
</nav>
`;
}
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`
<link
rel="stylesheet"
href="chrome://browser/content/firefoxview/fxview-category-button.css"
/>
<button
aria-hidden="true"
tabindex="-1"
?selected=${this.selected}
@click=${this.activate}
>
<span class="category-icon" part="icon"></span>
<slot></slot>
</button>
`;
}
updated() {
this.setAttribute("aria-selected", this.selected);
this.setAttribute("tabindex", this.selected ? 0 : -1);
}
}
customElements.define("fxview-category-button", FxviewCategoryButton);

View file

@ -15,9 +15,6 @@ browser.jar:
content/browser/firefoxview/view-syncedtabs.css content/browser/firefoxview/view-syncedtabs.css
content/browser/firefoxview/recentbrowsing.mjs content/browser/firefoxview/recentbrowsing.mjs
content/browser/firefoxview/firefoxview.css 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.css
content/browser/firefoxview/fxview-empty-state.mjs content/browser/firefoxview/fxview-empty-state.mjs
content/browser/firefoxview/helpers.mjs content/browser/firefoxview/helpers.mjs
@ -29,12 +26,12 @@ browser.jar:
content/browser/firefoxview/recentlyclosed.mjs content/browser/firefoxview/recentlyclosed.mjs
content/browser/firefoxview/viewpage.mjs content/browser/firefoxview/viewpage.mjs
content/browser/firefoxview/history-empty.svg (content/history-empty.svg) 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/recentlyclosed-empty.svg (content/recentlyclosed-empty.svg)
content/browser/firefoxview/synced-tabs-error.svg (content/synced-tabs-error.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.svg (content/callout-tab-pickup.svg)
content/browser/callout-tab-pickup-dark.svg (content/callout-tab-pickup-dark.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)

View file

@ -27,37 +27,37 @@ skip-if = ["true"] # Bug 1869605 and # Bug 1870296
["browser_firefoxview.js"] ["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"] ["browser_firefoxview_general_telemetry.js"]
fail-if = ["a11y_checks"] # Bug 1850591 clicked moz-page-nav-button button is not focusable
["browser_firefoxview_navigation.js"] ["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"] ["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"] ["browser_firefoxview_virtual_list.js"]
fail-if = ["a11y_checks"] # Bug 1850591 clicked moz-page-nav-button button is not focusable
["browser_history_firefoxview.js"] ["browser_history_firefoxview.js"]
skip-if = ["true"] # Bug 1877594 skip-if = ["true"] # Bug 1877594
["browser_opentabs_firefoxview.js"] ["browser_notification_dot.js"]
skip-if = ["true"] # Bug 1851453
["browser_opentabs_cards.js"] ["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 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"] ["browser_opentabs_recency.js"]
skip-if = [ skip-if = [
"os == 'win'", "os == 'win'",
@ -65,9 +65,19 @@ skip-if = [
] # macos times out, see bug 1857293, skipped for windows, see bug 1858460 ] # macos times out, see bug 1857293, skipped for windows, see bug 1858460
["browser_opentabs_tab_indicators.js"] ["browser_opentabs_tab_indicators.js"]
fail-if = ["a11y_checks"] # Bug 1850591 clicked moz-page-nav-button button is not focusable
["browser_recentlyclosed_firefoxview.js"] ["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"] ["browser_syncedtabs_errors_firefoxview.js"]
fail-if = ["a11y_checks"] # Bug 1850591 clicked moz-page-nav-button button is not focusable
["browser_syncedtabs_firefoxview.js"] ["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"]

View file

@ -6,10 +6,7 @@ add_task(async function about_firefoxview_smoke_test() {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
// sanity check the important regions exist on this page // sanity check the important regions exist on this page
ok( ok(document.querySelector("moz-page-nav"), "moz-page-nav element exists");
document.querySelector("fxview-category-navigation"),
"fxview-category-navigation element exists"
);
ok(document.querySelector("named-deck"), "named-deck element exists"); ok(document.querySelector("named-deck"), "named-deck element exists");
}); });
}); });

View file

@ -39,7 +39,7 @@ add_task(async function firefox_view_entered_telemetry() {
enteredTelemetry[4] = { page: "recentlyclosed" }; enteredTelemetry[4] = { page: "recentlyclosed" };
enteredAndTabSelectedEvents = [tabSelectedTelemetry, enteredTelemetry]; enteredAndTabSelectedEvents = [tabSelectedTelemetry, enteredTelemetry];
navigateToCategory(document, "recentlyclosed"); await navigateToViewAndWait(document, "recentlyclosed");
await clearAllParentTelemetryEvents(); await clearAllParentTelemetryEvents();
await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots"); await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots");
is( is(
@ -107,9 +107,9 @@ add_task(async function test_change_page_telemetry() {
], ],
]; ];
await clearAllParentTelemetryEvents(); await clearAllParentTelemetryEvents();
navigateToCategory(document, "recentlyclosed"); await navigateToViewAndWait(document, "recentlyclosed");
await telemetryEvent(changePageEvent); await telemetryEvent(changePageEvent);
navigateToCategory(document, "recentbrowsing"); await navigateToViewAndWait(document, "recentbrowsing");
let openTabsComponent = document.querySelector( let openTabsComponent = document.querySelector(
"view-opentabs[slot=opentabs]" "view-opentabs[slot=opentabs]"
@ -189,7 +189,7 @@ add_task(async function test_context_menu_new_window_telemetry() {
); );
// Test history context menu options // Test history context menu options
await navigateToCategoryAndWait(document, "history"); await navigateToViewAndWait(document, "history");
let historyComponent = document.querySelector("view-history"); let historyComponent = document.querySelector("view-history");
await TestUtils.waitForCondition(() => historyComponent.fullyUpdated); await TestUtils.waitForCondition(() => historyComponent.fullyUpdated);
await TestUtils.waitForCondition( await TestUtils.waitForCondition(
@ -245,7 +245,7 @@ add_task(async function test_context_menu_private_window_telemetry() {
); );
// Test history context menu options // Test history context menu options
await navigateToCategoryAndWait(document, "history"); await navigateToViewAndWait(document, "history");
let historyComponent = document.querySelector("view-history"); let historyComponent = document.querySelector("view-history");
await TestUtils.waitForCondition(() => historyComponent.fullyUpdated); await TestUtils.waitForCondition(() => historyComponent.fullyUpdated);
await TestUtils.waitForCondition( await TestUtils.waitForCondition(
@ -314,7 +314,7 @@ add_task(async function test_context_menu_delete_from_history_telemetry() {
); );
// Test history context menu options // Test history context menu options
await navigateToCategoryAndWait(document, "history"); await navigateToViewAndWait(document, "history");
let historyComponent = document.querySelector("view-history"); let historyComponent = document.querySelector("view-history");
await TestUtils.waitForCondition(() => historyComponent.fullyUpdated); await TestUtils.waitForCondition(() => historyComponent.fullyUpdated);
await TestUtils.waitForCondition( await TestUtils.waitForCondition(

View file

@ -3,15 +3,15 @@
const URL_BASE = `${getFirefoxViewURL()}#`; const URL_BASE = `${getFirefoxViewURL()}#`;
function assertCorrectPage(document, name, event) { function assertCorrectPage(document, view, event) {
is( is(
document.location.hash, document.location.hash,
`#${name}`, `#${view}`,
`Navigation button for ${name} navigates to ${URL_BASE + name} on ${event}.` `Navigation button for ${view} navigates to ${URL_BASE + view} on ${event}.`
); );
is( is(
document.querySelector("named-deck").selectedViewName, document.querySelector("named-deck").selectedViewName,
name, view,
"The correct deck child is selected" "The correct deck child is selected"
); );
} }
@ -22,21 +22,21 @@ add_task(async function test_side_component_navigation_by_click() {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
let win = browser.ownerGlobal; let win = browser.ownerGlobal;
const categoryButtons = document.querySelectorAll("fxview-category-button"); const pageNavButtons = document.querySelectorAll("moz-page-nav-button");
for (let element of categoryButtons) { for (let element of pageNavButtons) {
const name = element.name; const view = element.view;
let buttonClicked = BrowserTestUtils.waitForEvent( let buttonClicked = BrowserTestUtils.waitForEvent(
element.buttonEl, element.buttonEl,
"click", "click",
win win
); );
info(`Clicking navigation button for ${name}`); info(`Clicking navigation button for ${view}`);
EventUtils.synthesizeMouseAtCenter(element.buttonEl, {}, content); EventUtils.synthesizeMouseAtCenter(element.buttonEl, {}, content);
await buttonClicked; 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; const { document } = browser.contentWindow;
let win = browser.ownerGlobal; let win = browser.ownerGlobal;
const categoryButtons = document.querySelectorAll("fxview-category-button"); const pageNavButtons = document.querySelectorAll("moz-page-nav-button");
const firstButton = categoryButtons[0]; const firstButton = pageNavButtons[0].buttonEl;
firstButton.focus(); firstButton.focus();
is( is(
document.activeElement, document.activeElement.shadowRoot.activeElement,
firstButton, firstButton,
"The first category button has focus" "The first page nav button has focus"
); );
for (let element of Array.from(categoryButtons).slice(1)) { for (let element of Array.from(pageNavButtons).slice(1)) {
const name = element.name; const view = element.view;
let buttonFocused = BrowserTestUtils.waitForEvent(element, "focus", win); let buttonFocused = BrowserTestUtils.waitForEvent(element, "focus", win);
info(`Focus is on ${document.activeElement.name}`); info(`Focus is on ${document.activeElement.view}`);
info(`Arrow down on navigation to ${name}`); info(`Arrow down on navigation to ${view}`);
EventUtils.synthesizeKey("KEY_ArrowDown", {}, win); EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
await buttonFocused; 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 => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; 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"); const namedDeck = document.querySelector("named-deck");
for (let element of categoryButtons) { for (let element of pageNavButtons) {
const name = element.name; const view = element.view;
info(`Navigating to ${URL_BASE + name}`); info(`Navigating to ${URL_BASE + view}`);
document.location.assign(URL_BASE + name); document.location.assign(URL_BASE + view);
await BrowserTestUtils.waitForCondition(() => { await BrowserTestUtils.waitForCondition(() => {
return namedDeck.selectedViewName === name; return namedDeck.selectedViewName === view;
}, "Wait for navigation to complete"); }, "Wait for navigation to complete");
is( is(
namedDeck.selectedViewName, namedDeck.selectedViewName,
name, view,
`The correct deck child for category ${name} is selected` `The correct deck child for view ${view} is selected`
); );
} }
}); });

View file

@ -322,7 +322,7 @@ add_task(async function test_opentabs() {
const document = browser.contentDocument; const document = browser.contentDocument;
const { openTabsView } = getTopLevelViewElements(document); const { openTabsView } = getTopLevelViewElements(document);
await navigateToCategoryAndWait(document, "opentabs"); await navigateToViewAndWait(document, "opentabs");
const { openTabsList } = await getElements(document); const { openTabsList } = await getElements(document);
ok(openTabsView, "Found the open tabs view"); ok(openTabsView, "Found the open tabs view");
@ -387,7 +387,7 @@ add_task(async function test_recentlyclosed() {
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const document = browser.contentDocument; const document = browser.contentDocument;
const { recentlyClosedView } = getTopLevelViewElements(document); const { recentlyClosedView } = getTopLevelViewElements(document);
await navigateToCategoryAndWait(document, "recentlyclosed"); await navigateToViewAndWait(document, "recentlyclosed");
const { recentlyClosedList } = await getElements(document); const { recentlyClosedList } = await getElements(document);
ok(recentlyClosedView, "Found the recently-closed view"); ok(recentlyClosedView, "Found the recently-closed view");

View file

@ -56,7 +56,7 @@ add_task(async function test_search_initiated_telemetry() {
EventUtils.sendString("example.com", content); EventUtils.sendString("example.com", content);
await telemetryEvent(searchEvent("recentbrowsing")); await telemetryEvent(searchEvent("recentbrowsing"));
await navigateToCategoryAndWait(document, "opentabs"); await navigateToViewAndWait(document, "opentabs");
await clearAllParentTelemetryEvents(); await clearAllParentTelemetryEvents();
is(document.location.hash, "#opentabs", "Searching within open tabs."); is(document.location.hash, "#opentabs", "Searching within open tabs.");
const openTabs = document.querySelector("named-deck > view-opentabs"); 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); EventUtils.sendString("example.com", content);
await telemetryEvent(searchEvent("opentabs")); await telemetryEvent(searchEvent("opentabs"));
await navigateToCategoryAndWait(document, "recentlyclosed"); await navigateToViewAndWait(document, "recentlyclosed");
await clearAllParentTelemetryEvents(); await clearAllParentTelemetryEvents();
is( is(
document.location.hash, document.location.hash,
@ -84,7 +84,7 @@ add_task(async function test_search_initiated_telemetry() {
EventUtils.sendString("example.com", content); EventUtils.sendString("example.com", content);
await telemetryEvent(searchEvent("recentlyclosed")); await telemetryEvent(searchEvent("recentlyclosed"));
await navigateToCategoryAndWait(document, "syncedtabs"); await navigateToViewAndWait(document, "syncedtabs");
await clearAllParentTelemetryEvents(); await clearAllParentTelemetryEvents();
is(document.location.hash, "#syncedtabs", "Searching within synced tabs."); is(document.location.hash, "#syncedtabs", "Searching within synced tabs.");
const syncedTabs = document.querySelector("named-deck > view-syncedtabs"); 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); EventUtils.sendString("example.com", content);
await telemetryEvent(searchEvent("syncedtabs")); await telemetryEvent(searchEvent("syncedtabs"));
await navigateToCategoryAndWait(document, "history"); await navigateToViewAndWait(document, "history");
await clearAllParentTelemetryEvents(); await clearAllParentTelemetryEvents();
is(document.location.hash, "#history", "Searching within history."); is(document.location.hash, "#history", "Searching within history.");
const history = document.querySelector("named-deck > view-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 => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "history"); await navigateToViewAndWait(document, "history");
const historyComponent = document.querySelector("view-history"); const historyComponent = document.querySelector("view-history");
const searchTextbox = await TestUtils.waitForCondition( const searchTextbox = await TestUtils.waitForCondition(
@ -426,7 +426,7 @@ add_task(async function test_cumulative_searches_recently_closed_telemetry() {
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "recentlyclosed"); await navigateToViewAndWait(document, "recentlyclosed");
is( is(
document.location.hash, document.location.hash,
"#recentlyclosed", "#recentlyclosed",
@ -471,7 +471,7 @@ add_task(async function test_cumulative_searches_open_tabs_telemetry() {
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "opentabs"); await navigateToViewAndWait(document, "opentabs");
is(document.location.hash, "#opentabs", "Searching within open tabs."); is(document.location.hash, "#opentabs", "Searching within open tabs.");
const openTabs = document.querySelector("named-deck > view-opentabs"); 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 => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "history"); await navigateToViewAndWait(document, "history");
is(document.location.hash, "#history", "Searching within history."); is(document.location.hash, "#history", "Searching within history.");
const history = document.querySelector("named-deck > view-history"); const history = document.querySelector("named-deck > view-history");
const searchTextbox = await TestUtils.waitForCondition(() => { const searchTextbox = await TestUtils.waitForCondition(() => {
@ -579,7 +579,7 @@ add_task(async function test_cumulative_searches_syncedtabs_telemetry() {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
Services.obs.notifyObservers(null, UIState.ON_UPDATE); Services.obs.notifyObservers(null, UIState.ON_UPDATE);
await navigateToCategoryAndWait(document, "syncedtabs"); await navigateToViewAndWait(document, "syncedtabs");
is(document.location.hash, "#syncedtabs", "Searching within synced tabs."); is(document.location.hash, "#syncedtabs", "Searching within synced tabs.");
let syncedTabs = document.querySelector( let syncedTabs = document.querySelector(
"view-syncedtabs:not([slot=syncedtabs])" "view-syncedtabs:not([slot=syncedtabs])"

View file

@ -29,7 +29,7 @@ add_task(async function test_max_render_count_on_win_resize() {
"Firefox View is loaded to the Recent Browsing page." "Firefox View is loaded to the Recent Browsing page."
); );
await navigateToCategoryAndWait(document, "history"); await navigateToViewAndWait(document, "history");
let historyComponent = document.querySelector("view-history"); let historyComponent = document.querySelector("view-history");
let tabList = historyComponent.lists[0]; let tabList = historyComponent.lists[0];

View file

@ -169,7 +169,7 @@ add_task(async function test_list_ordering() {
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "history"); await navigateToViewAndWait(document, "history");
let historyComponent = document.querySelector("view-history"); let historyComponent = document.querySelector("view-history");
historyComponent.profileAge = 8; historyComponent.profileAge = 8;
@ -262,7 +262,7 @@ add_task(async function test_empty_states() {
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "history"); await navigateToViewAndWait(document, "history");
let historyComponent = document.querySelector("view-history"); let historyComponent = document.querySelector("view-history");
historyComponent.profileAge = 8; historyComponent.profileAge = 8;
@ -350,7 +350,7 @@ add_task(async function test_observers_removed_when_view_is_hidden() {
); );
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "history"); await navigateToViewAndWait(document, "history");
const historyComponent = document.querySelector("view-history"); const historyComponent = document.querySelector("view-history");
historyComponent.profileAge = 8; historyComponent.profileAge = 8;
let visitList = await TestUtils.waitForCondition(() => let visitList = await TestUtils.waitForCondition(() =>
@ -399,7 +399,7 @@ add_task(async function test_show_all_history_telemetry() {
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "history"); await navigateToViewAndWait(document, "history");
let historyComponent = document.querySelector("view-history"); let historyComponent = document.querySelector("view-history");
historyComponent.profileAge = 8; historyComponent.profileAge = 8;
@ -424,7 +424,7 @@ add_task(async function test_show_all_history_telemetry() {
add_task(async function test_search_history() { add_task(async function test_search_history() {
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "history"); await navigateToViewAndWait(document, "history");
const historyComponent = document.querySelector("view-history"); const historyComponent = document.querySelector("view-history");
historyComponent.profileAge = 8; historyComponent.profileAge = 8;
await historyComponentReady(historyComponent); await historyComponentReady(historyComponent);
@ -504,7 +504,7 @@ add_task(async function test_persist_collapse_card_after_view_change() {
await addHistoryItems(today); await addHistoryItems(today);
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "history"); await navigateToViewAndWait(document, "history");
const historyComponent = document.querySelector("view-history"); const historyComponent = document.querySelector("view-history");
historyComponent.profileAge = 8; historyComponent.profileAge = 8;
await TestUtils.waitForCondition( 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 // Switch to a new view and then back to History
await navigateToCategoryAndWait(document, "syncedtabs"); await navigateToViewAndWait(document, "syncedtabs");
await navigateToCategoryAndWait(document, "history"); await navigateToViewAndWait(document, "history");
// Check that first history card is still collapsed after changing view // Check that first history card is still collapsed after changing view
ok( ok(

View file

@ -21,7 +21,7 @@ add_setup(function () {
async function navigateToOpenTabs(browser) { async function navigateToOpenTabs(browser) {
const document = browser.contentDocument; const document = browser.contentDocument;
if (document.querySelector("named-deck").selectedViewName != "opentabs") { 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 => { await openFirefoxViewTab(window).then(async viewTab => {
const browser = viewTab.linkedBrowser; const browser = viewTab.linkedBrowser;
await navigateToCategoryAndWait(browser.contentDocument, "recentbrowsing"); await navigateToViewAndWait(browser.contentDocument, "recentbrowsing");
const recentBrowsing = browser.contentDocument.querySelector( const recentBrowsing = browser.contentDocument.querySelector(
"view-recentbrowsing" "view-recentbrowsing"
); );

View file

@ -122,7 +122,7 @@ async function moreMenuSetup() {
await clickFirefoxViewButton(window); await clickFirefoxViewButton(window);
const document = window.FirefoxViewHandler.tab.linkedBrowser.contentDocument; const document = window.FirefoxViewHandler.tab.linkedBrowser.contentDocument;
await navigateToCategoryAndWait(document, "opentabs"); await navigateToViewAndWait(document, "opentabs");
let openTabs = document.querySelector("view-opentabs[name=opentabs]"); let openTabs = document.querySelector("view-opentabs[name=opentabs]");
setSortOption(openTabs, "tabStripOrder"); setSortOption(openTabs, "tabStripOrder");

View file

@ -159,8 +159,9 @@ function getOpenTabsComponent(browser) {
async function checkTabList(browser, expected) { async function checkTabList(browser, expected) {
const tabsView = getOpenTabsComponent(browser); const tabsView = getOpenTabsComponent(browser);
const openTabsCard = tabsView.shadowRoot.querySelector("view-opentabs-card"); const openTabsCard = tabsView.shadowRoot.querySelector("view-opentabs-card");
await tabsView.getUpdateComplete(); await tabsView.updateComplete;
const tabList = openTabsCard.shadowRoot.querySelector("fxview-tab-list"); const tabList = openTabsCard.shadowRoot.querySelector("fxview-tab-list");
await tabList.getUpdateComplete();
Assert.ok(tabList, "Found the tab list element"); Assert.ok(tabList, "Found the tab list element");
await TestUtils.waitForCondition(() => tabList.rowEls.length); await TestUtils.waitForCondition(() => tabList.rowEls.length);
let actual = Array.from(tabList.rowEls).map(row => row.url); let actual = Array.from(tabList.rowEls).map(row => row.url);

View file

@ -22,7 +22,7 @@ add_task(async function test_notification_dot_indicator() {
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
let win = browser.ownerGlobal; let win = browser.ownerGlobal;
await navigateToCategoryAndWait(document, "opentabs"); await navigateToViewAndWait(document, "opentabs");
// load page that opens prompt when page is hidden // load page that opens prompt when page is hidden
let openedTab = await BrowserTestUtils.openNewForegroundTab( let openedTab = await BrowserTestUtils.openNewForegroundTab(
gBrowser, gBrowser,
@ -48,7 +48,7 @@ add_task(async function test_notification_dot_indicator() {
await openTabs.updateComplete; await openTabs.updateComplete;
await TestUtils.waitForCondition( 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." "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] URLs[0]
); );
await navigateToCategoryAndWait(document, "opentabs"); await navigateToViewAndWait(document, "opentabs");
let openTabs = document.querySelector("view-opentabs[name=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() { add_task(async function test_sound_playing_muted_indicator() {
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "opentabs"); await navigateToViewAndWait(document, "opentabs");
// Load a page in a container tab // Load a page in a container tab
let soundTab = await BrowserTestUtils.openNewForegroundTab( let soundTab = await BrowserTestUtils.openNewForegroundTab(
@ -146,7 +146,7 @@ add_task(async function test_sound_playing_muted_indicator() {
"The tab list hasn't rendered." "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); await TestUtils.waitForCondition(() => soundPlayingTabElem.soundPlaying);

View file

@ -196,7 +196,7 @@ add_task(async function test_initial_closed_tab() {
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
is(document.location.href, getFirefoxViewURL()); is(document.location.href, getFirefoxViewURL());
await navigateToCategoryAndWait(document, "recentlyclosed"); await navigateToViewAndWait(document, "recentlyclosed");
let { cleanup } = await prepareSingleClosedTab(); let { cleanup } = await prepareSingleClosedTab();
await switchToFxViewTab(window); await switchToFxViewTab(window);
let [listItems] = await waitForRecentlyClosedTabsList(document); let [listItems] = await waitForRecentlyClosedTabsList(document);
@ -220,7 +220,7 @@ add_task(async function test_list_ordering() {
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await clearAllParentTelemetryEvents(); await clearAllParentTelemetryEvents();
navigateToCategory(document, "recentlyclosed"); await navigateToViewAndWait(document, "recentlyclosed");
let [cardMainSlotNode, listItems] = await waitForRecentlyClosedTabsList( let [cardMainSlotNode, listItems] = await waitForRecentlyClosedTabsList(
document document
); );
@ -248,7 +248,7 @@ add_task(async function test_list_updates() {
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
navigateToCategory(document, "recentlyclosed"); await navigateToViewAndWait(document, "recentlyclosed");
let [listElem, listItems] = await waitForRecentlyClosedTabsList(document); let [listElem, listItems] = await waitForRecentlyClosedTabsList(document);
Assert.deepEqual( Assert.deepEqual(
@ -321,7 +321,7 @@ add_task(async function test_restore_tab() {
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
navigateToCategory(document, "recentlyclosed"); await navigateToViewAndWait(document, "recentlyclosed");
let [listElem, listItems] = await waitForRecentlyClosedTabsList(document); let [listElem, listItems] = await waitForRecentlyClosedTabsList(document);
Assert.deepEqual( Assert.deepEqual(
@ -365,7 +365,7 @@ add_task(async function test_dismiss_tab() {
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
navigateToCategory(document, "recentlyclosed"); await navigateToViewAndWait(document, "recentlyclosed");
let [listElem, listItems] = await waitForRecentlyClosedTabsList(document); let [listElem, listItems] = await waitForRecentlyClosedTabsList(document);
await clearAllParentTelemetryEvents(); await clearAllParentTelemetryEvents();
@ -429,7 +429,7 @@ add_task(async function test_empty_states() {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
is(document.location.href, "about:firefoxview"); is(document.location.href, "about:firefoxview");
navigateToCategory(document, "recentlyclosed"); await navigateToViewAndWait(document, "recentlyclosed");
let recentlyClosedComponent = document.querySelector( let recentlyClosedComponent = document.querySelector(
"view-recentlyclosed:not([slot=recentlyclosed])" "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) { await withFirefoxView({}, async function (browser) {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
navigateToCategory(document, "recentlyclosed"); await navigateToViewAndWait(document, "recentlyclosed");
const [listElem] = await waitForRecentlyClosedTabsList(document); const [listElem] = await waitForRecentlyClosedTabsList(document);
is(listElem.rowEls.length, 1); is(listElem.rowEls.length, 1);
@ -510,7 +510,7 @@ add_task(async function test_search() {
let { cleanup, expectedURLs } = await prepareClosedTabs(); let { cleanup, expectedURLs } = await prepareClosedTabs();
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
navigateToCategory(document, "recentlyclosed"); await navigateToViewAndWait(document, "recentlyclosed");
const [listElem] = await waitForRecentlyClosedTabsList(document); const [listElem] = await waitForRecentlyClosedTabsList(document);
const recentlyClosedComponent = document.querySelector( const recentlyClosedComponent = document.querySelector(
"view-recentlyclosed:not([slot=recentlyclosed])" "view-recentlyclosed:not([slot=recentlyclosed])"
@ -569,7 +569,7 @@ add_task(async function test_search_recent_browsing() {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
info("Input a search query."); info("Input a search query.");
await navigateToCategoryAndWait(document, "recentbrowsing"); await navigateToViewAndWait(document, "recentbrowsing");
const recentBrowsing = document.querySelector("view-recentbrowsing"); const recentBrowsing = document.querySelector("view-recentbrowsing");
EventUtils.synthesizeMouseAtCenter( EventUtils.synthesizeMouseAtCenter(
recentBrowsing.searchTextbox, recentBrowsing.searchTextbox,

View file

@ -56,7 +56,7 @@ add_task(async function test_network_offline() {
sandbox.spy(TabsSetupFlowManager, "tryToClearError"); sandbox.spy(TabsSetupFlowManager, "tryToClearError");
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "syncedtabs"); await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE); Services.obs.notifyObservers(null, UIState.ON_UPDATE);
Services.obs.notifyObservers( Services.obs.notifyObservers(
@ -112,7 +112,7 @@ add_task(async function test_sync_error() {
const sandbox = await setupWithDesktopDevices(); const sandbox = await setupWithDesktopDevices();
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "syncedtabs"); await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE); Services.obs.notifyObservers(null, UIState.ON_UPDATE);
Services.obs.notifyObservers(null, "weave:service:sync:error"); Services.obs.notifyObservers(null, "weave:service:sync:error");

View file

@ -29,7 +29,7 @@ add_task(async function test_unconfigured_initial_state() {
}); });
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "syncedtabs"); await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE); Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector( let syncedTabsComponent = document.querySelector(
@ -85,7 +85,7 @@ add_task(async function test_signed_in() {
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "syncedtabs"); await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE); Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector( let syncedTabsComponent = document.querySelector(
@ -148,7 +148,7 @@ add_task(async function test_no_synced_tabs() {
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "syncedtabs"); await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE); Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector( let syncedTabsComponent = document.querySelector(
@ -188,7 +188,7 @@ add_task(async function test_no_error_for_two_desktop() {
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "syncedtabs"); await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE); Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector( let syncedTabsComponent = document.querySelector(
@ -232,7 +232,7 @@ add_task(async function test_empty_state() {
await withFirefoxView({ openNewWindow: true }, async browser => { await withFirefoxView({ openNewWindow: true }, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "syncedtabs"); await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE); Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector( let syncedTabsComponent = document.querySelector(
@ -277,7 +277,7 @@ add_task(async function test_tabs() {
await withFirefoxView({ openNewWindow: true }, async browser => { await withFirefoxView({ openNewWindow: true }, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "syncedtabs"); await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE); Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector( let syncedTabsComponent = document.querySelector(
@ -365,7 +365,7 @@ add_task(async function test_empty_desktop_same_name() {
await withFirefoxView({ openNewWindow: true }, async browser => { await withFirefoxView({ openNewWindow: true }, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "syncedtabs"); await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE); Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector( let syncedTabsComponent = document.querySelector(
@ -413,7 +413,7 @@ add_task(async function test_empty_desktop_same_name_three() {
await withFirefoxView({ openNewWindow: true }, async browser => { await withFirefoxView({ openNewWindow: true }, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "syncedtabs"); await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE); Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector( let syncedTabsComponent = document.querySelector(
@ -459,7 +459,7 @@ add_task(async function search_synced_tabs() {
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "syncedtabs"); await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE); Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector( let syncedTabsComponent = document.querySelector(
@ -666,7 +666,7 @@ add_task(async function search_synced_tabs_recent_browsing() {
}); });
await withFirefoxView({}, async browser => { await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow; const { document } = browser.contentWindow;
await navigateToCategoryAndWait(document, "recentbrowsing"); await navigateToViewAndWait(document, "recentbrowsing");
Services.obs.notifyObservers(null, UIState.ON_UPDATE); Services.obs.notifyObservers(null, UIState.ON_UPDATE);
const recentBrowsing = document.querySelector("view-recentbrowsing"); const recentBrowsing = document.querySelector("view-recentbrowsing");

View file

@ -548,31 +548,19 @@ registerCleanupFunction(() => {
gSandbox?.restore(); gSandbox?.restore();
}); });
function navigateToCategory(document, category) { async function navigateToViewAndWait(document, view) {
const navigation = document.querySelector("fxview-category-navigation"); info(`navigateToViewAndWait, for ${view}`);
let navButton = Array.from(navigation.categoryButtons).filter( const navigation = document.querySelector("moz-page-nav");
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");
const win = document.ownerGlobal; const win = document.ownerGlobal;
SimpleTest.promiseFocus(win); SimpleTest.promiseFocus(win);
let navButton = Array.from(navigation.categoryButtons).find( let navButton = Array.from(navigation.pageNavButtons).find(pageNavButton => {
categoryButton => { return pageNavButton.view === view;
return categoryButton.name === category; });
}
);
const namedDeck = document.querySelector("named-deck"); const namedDeck = document.querySelector("named-deck");
await BrowserTestUtils.waitForCondition( await BrowserTestUtils.waitForCondition(
() => navButton.getBoundingClientRect().height, () => navButton.getBoundingClientRect().height,
`Waiting for ${category} button to be clickable` `Waiting for ${view} button to be clickable`
); );
EventUtils.synthesizeMouseAtCenter(navButton, {}, win); EventUtils.synthesizeMouseAtCenter(navButton, {}, win);
@ -582,10 +570,10 @@ async function navigateToCategoryAndWait(document, category) {
child => child.slot == "selected" child => child.slot == "selected"
); );
return ( return (
namedDeck.selectedViewName == category && namedDeck.selectedViewName == view &&
selectedView?.getBoundingClientRect().height selectedView?.getBoundingClientRect().height
); );
}, `Waiting for ${category} to be visible`); }, `Waiting for ${view} to be visible`);
} }
/** /**

View file

@ -2,6 +2,4 @@
["test_card_container.html"] ["test_card_container.html"]
["test_fxview_category_navigation.html"]
["test_fxview_tab_list.html"] ["test_fxview_tab_list.html"]

View file

@ -1,322 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>FxviewCategoryNavigation Tests</title>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.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 type="module" src="chrome://browser/content/firefoxview/fxview-category-navigation.mjs"></script>
</head>
<style>
body {
display: flex;
}
#navigation {
width: var(--in-content-sidebar-width);
}
fxview-category-button[name="category-one"]::part(icon) {
background-image: url("chrome://mozapps/skin/extensions/category-discover.svg");
}
fxview-category-button[name="category-two"]::part(icon) {
background-image: url("chrome://mozapps/skin/extensions/category-discover.svg");
}
fxview-category-button[name="category-three"]::part(icon) {
background-image: url("chrome://mozapps/skin/extensions/category-discover.svg");
}
fxview-category-button[name="category-four"]::part(icon) {
background-image: url("chrome://mozapps/skin/extensions/category-discover.svg");
}
fxview-category-button[name="category-five"]::part(icon) {
background-image: url("chrome://mozapps/skin/extensions/category-discover.svg");
}
</style>
<body>
<p id="display"></p>
<div id="content">
<div id="navigation">
<fxview-category-navigation>
<h2 slot="category-nav-header">Header</h2>
<fxview-category-button class="category" slot="category-button" name="category-one">
<span class="category-name">Category 1</span>
</fxview-category-button>
<fxview-category-button class="category" slot="category-button" name="category-two">
<span class="category-name">Category 2</span>
</fxview-category-button>
<fxview-category-button class="category" slot="category-button" name="category-three">
<span class="category-name">Category 3</span>
</fxview-category-button>
<fxview-category-button class="category" slot="category-button" name="category-four">
<span class="category-name">Category 4</span>
</fxview-category-button>
<fxview-category-button class="category" slot="category-button" name="category-five">
<span class="category-name">Category 5</span>
</fxview-category-button>
</fxview-category-navigation>
</div>
</div>
<pre id="test"></pre>
<script>
Services.scriptloader.loadSubScript(
"chrome://browser/content/utilityOverlay.js",
this
);
const { BrowserTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/BrowserTestUtils.sys.mjs"
);
const fxviewCategoryNav = document.querySelector("fxview-category-navigation");
function isActiveElement(expectedActiveEl) {
return expectedActiveEl.getRootNode().activeElement == expectedActiveEl;
}
/**
* Tests that the first category is selected by default
*/
add_task(async function test_first_item_selected_by_default() {
is(
fxviewCategoryNav.categoryButtons.length,
5,
"Five category buttons are in the navigation"
);
ok(
fxviewCategoryNav.categoryButtons[0].name === fxviewCategoryNav.currentCategory,
"The first category button is selected by default"
)
});
/**
* Tests that categories are selected when clicked
*/
add_task(async function test_select_category() {
let gBrowser = BrowserWindowTracker.getTopWindow().top.gBrowser;
let secondCategory = fxviewCategoryNav.categoryButtons[1];
let categoryChanged = BrowserTestUtils.waitForEvent(
gBrowser,
"change-category"
);
secondCategory.buttonEl.click();
await categoryChanged;
ok(
secondCategory.name === fxviewCategoryNav.currentCategory,
"The second category button is selected"
)
let thirdCategory = fxviewCategoryNav.categoryButtons[2];
categoryChanged = BrowserTestUtils.waitForEvent(
gBrowser,
"change-category"
);
thirdCategory.buttonEl.click();
await categoryChanged;
ok(
thirdCategory.name === fxviewCategoryNav.currentCategory,
"The third category button is selected"
)
let firstCategory = fxviewCategoryNav.categoryButtons[0];
categoryChanged = BrowserTestUtils.waitForEvent(
gBrowser,
"change-category"
);
firstCategory.buttonEl.click();
await categoryChanged;
ok(
firstCategory.name === fxviewCategoryNav.currentCategory,
"The first category button is selected"
)
});
/**
* Tests that categories are keyboard-navigable
*/
add_task(async function test_keyboard_navigation() {
const arrowDown = async () => {
info("Arrow down");
synthesizeKey("KEY_ArrowDown", {});
await fxviewCategoryNav.getUpdateComplete();
};
const arrowUp = async () => {
info("Arrow up");
synthesizeKey("KEY_ArrowUp", {});
await fxviewCategoryNav.getUpdateComplete();
};
const arrowLeft = async () => {
info("Arrow left");
synthesizeKey("KEY_ArrowLeft", {});
await fxviewCategoryNav.getUpdateComplete();
};
const arrowRight = async () => {
info("Arrow right");
synthesizeKey("KEY_ArrowRight", {});
await fxviewCategoryNav.getUpdateComplete();
};
// Setting this pref allows the test to run as expected with a keyboard on MacOS
await SpecialPowers.pushPrefEnv({
set: [["accessibility.tabfocus", 7]],
});
let firstCategory = fxviewCategoryNav.categoryButtons[0];
let secondCategory = fxviewCategoryNav.categoryButtons[1];
let thirdCategory = fxviewCategoryNav.categoryButtons[2];
let fourthCategory = fxviewCategoryNav.categoryButtons[3];
let fifthCategory = fxviewCategoryNav.categoryButtons[4];
is(
firstCategory.name,
fxviewCategoryNav.currentCategory,
"The first category button is selected"
)
firstCategory.focus();
await arrowDown();
ok(
isActiveElement(secondCategory),
"The second category button is the active element after first arrow down"
);
is(
secondCategory.name,
fxviewCategoryNav.currentCategory,
"The second category button is selected"
)
await arrowDown();
is(
thirdCategory.name,
fxviewCategoryNav.currentCategory,
"The third category button is selected"
)
await arrowDown();
is(
fourthCategory.name,
fxviewCategoryNav.currentCategory,
"The fourth category button is selected"
)
await arrowDown();
is(
fifthCategory.name,
fxviewCategoryNav.currentCategory,
"The fifth category button is selected"
)
await arrowDown();
is(
fifthCategory.name,
fxviewCategoryNav.currentCategory,
"The fifth category button is still selected"
)
await arrowUp();
is(
fourthCategory.name,
fxviewCategoryNav.currentCategory,
"The fourth category button is selected"
)
await arrowUp();
is(
thirdCategory.name,
fxviewCategoryNav.currentCategory,
"The third category button is selected"
)
await arrowUp();
is(
secondCategory.name,
fxviewCategoryNav.currentCategory,
"The second category button is selected"
)
await arrowUp();
is(
firstCategory.name,
fxviewCategoryNav.currentCategory,
"The first category button is selected"
)
await arrowUp();
is(
firstCategory.name,
fxviewCategoryNav.currentCategory,
"The first category button is still selected"
)
// Test navigation with arrow left/right keys
is(
firstCategory.name,
fxviewCategoryNav.currentCategory,
"The first category button is selected"
)
firstCategory.focus();
await arrowRight();
ok(
isActiveElement(secondCategory),
"The second category button is the active element after first arrow right"
);
is(
secondCategory.name,
fxviewCategoryNav.currentCategory,
"The second category button is selected"
)
await arrowRight();
is(
thirdCategory.name,
fxviewCategoryNav.currentCategory,
"The third category button is selected"
)
await arrowRight();
is(
fourthCategory.name,
fxviewCategoryNav.currentCategory,
"The fourth category button is selected"
)
await arrowRight();
is(
fifthCategory.name,
fxviewCategoryNav.currentCategory,
"The fifth category button is selected"
)
await arrowRight();
is(
fifthCategory.name,
fxviewCategoryNav.currentCategory,
"The fifth category button is still selected"
)
await arrowLeft();
is(
fourthCategory.name,
fxviewCategoryNav.currentCategory,
"The fourth category button is selected"
)
await arrowLeft();
is(
thirdCategory.name,
fxviewCategoryNav.currentCategory,
"The third category button is selected"
)
await arrowLeft();
is(
secondCategory.name,
fxviewCategoryNav.currentCategory,
"The second category button is selected"
)
await arrowLeft();
is(
firstCategory.name,
fxviewCategoryNav.currentCategory,
"The first category button is selected"
)
await arrowLeft();
is(
firstCategory.name,
fxviewCategoryNav.currentCategory,
"The first category button is still selected"
)
await SpecialPowers.popPrefEnv();
});
</script>
</body>
</html>

View file

@ -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`
<style>
#page {
height: 100%;
display: grid;
grid-template-columns: var(--in-content-sidebar-width) 1fr;
}
fxview-category-navigation {
margin-inline-start: 10px;
}
fxview-category-button[name="category-one"]::part(icon) {
background-image: url("chrome://browser/skin/preferences/category-general.svg");
}
fxview-category-button[name="category-two"]::part(icon) {
background-image: url("chrome://browser/skin/preferences/category-general.svg");
}
fxview-category-button[name="category-three"]::part(icon) {
background-image: url("chrome://browser/skin/preferences/category-general.svg");
}
.footer-button {
display: flex;
gap: 12px;
font-weight: normal;
min-width: unset;
padding: 8px;
margin: 0;
}
.footer-button .cat-icon {
background-image: url("chrome://browser/skin/preferences/category-general.svg");
background-color: initial;
background-size: 20px;
background-repeat: no-repeat;
background-position: center;
height: 20px;
width: 20px;
display: inline-block;
-moz-context-properties: fill;
fill: currentColor;
}
@media (max-width: 52rem) {
#page {
grid-template-columns: 82px 1fr;
}
.cat-name {
display: none;
}
}
</style>
<div id="page">
<fxview-category-navigation>
<h2 slot="category-nav-header">Header</h2>
<fxview-category-button
slot="category-button"
name="category-one"
data-l10n-id="fxview-category-button-one"
>
</fxview-category-button>
<fxview-category-button
slot="category-button"
name="category-two"
data-l10n-id="fxview-category-button-two"
>
</fxview-category-button>
<fxview-category-button
slot="category-button"
name="category-three"
data-l10n-id="fxview-category-button-three"
>
</fxview-category-button>
<div slot="category-nav-footer" class="category-nav-footer">
<button class="footer-button ghost-button">
<span class="cat-icon"></span>
<span
class="cat-name"
data-l10n-id="fxview-category-footer-button"
></span>
</button>
</div>
</fxview-category-navigation>
</div>
`;
export const Default = Template.bind({});
Default.args = {};

View file

@ -12,6 +12,9 @@ menu-tools-firefox-view =
firefoxview-page-title = { -firefoxview-brand-name } firefoxview-page-title = { -firefoxview-brand-name }
firefoxview-page-heading =
.heading = { -firefoxview-brand-name }
firefoxview-page-label = firefoxview-page-label =
.label = { -firefoxview-brand-name } .label = { -firefoxview-brand-name }

View file

@ -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-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.css (widgets/moz-card/moz-card.css)
content/global/elements/moz-card.mjs (widgets/moz-card/moz-card.mjs) 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.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-five-star.mjs (widgets/moz-five-star/moz-five-star.mjs)
content/global/elements/moz-input-box.js (widgets/moz-input-box.js) content/global/elements/moz-input-box.js (widgets/moz-input-box.js)

View file

@ -32,6 +32,8 @@ skip-if = ["os == 'mac'"]
["test_moz_message_bar.html"] ["test_moz_message_bar.html"]
["test_moz_page_nav.html"]
["test_moz_support_link.html"] ["test_moz_support_link.html"]
["test_moz_toggle.html"] ["test_moz_toggle.html"]
@ -65,4 +67,5 @@ skip-if = [
"os == 'android'", "os == 'android'",
"os == 'linux' && debug", # Bug 1765783 "os == 'linux' && debug", # Bug 1765783
] ]
["test_videocontrols_onclickplay.html"] ["test_videocontrols_onclickplay.html"]

View file

@ -0,0 +1,306 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>MozPageNav Tests</title>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.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 type="module" src="chrome://global/content/elements/moz-page-nav.mjs"></script>
</head>
<style>
body {
display: flex;
}
#navigation {
width: var(--page-nav-width);
}
</style>
<body>
<p id="display"></p>
<div id="content">
<div id="navigation">
<moz-page-nav heading="Heading">
<moz-page-nav-button view="view-one" iconSrc="chrome://mozapps/skin/extensions/category-discover.svg">
<span class="view-name">View 1</span>
</moz-page-nav-button>
<moz-page-nav-button view="view-two" iconSrc="chrome://mozapps/skin/extensions/category-discover.svg">
<span class="view-name">View 2</span>
</moz-page-nav-button>
<moz-page-nav-button view="view-three" iconSrc="chrome://mozapps/skin/extensions/category-discover.svg">
<span class="view-name">View 3</span>
</moz-page-nav-button>
<moz-page-nav-button view="view-four" iconSrc="chrome://mozapps/skin/extensions/category-discover.svg">
<span class="view-name">View 4</span>
</moz-page-nav-button>
<moz-page-nav-button view="view-five" iconSrc="chrome://mozapps/skin/extensions/category-discover.svg">
<span class="view-name">View 5</span>
</moz-page-nav-button>
</moz-page-nav>
</div>
</div>
<pre id="test"></pre>
<script>
Services.scriptloader.loadSubScript(
"chrome://browser/content/utilityOverlay.js",
this
);
const { BrowserTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/BrowserTestUtils.sys.mjs"
);
const mozPageNav = document.querySelector("moz-page-nav");
function isActiveElement(expectedActiveEl) {
return expectedActiveEl.getRootNode().activeElement == expectedActiveEl;
}
/**
* Tests that the first page nav button is selected by default
*/
add_task(async function test_first_item_selected_by_default() {
is(
mozPageNav.pageNavButtons.length,
5,
"Five page nav buttons are in the navigation"
);
ok(
mozPageNav.pageNavButtons[0].view === mozPageNav.currentView,
"The first page nav button is selected by default"
)
});
/**
* Tests that views are selected when clicked
*/
add_task(async function test_select_view() {
let gBrowser = BrowserWindowTracker.getTopWindow().top.gBrowser;
let secondViewButton = mozPageNav.pageNavButtons[1];
let viewChanged = BrowserTestUtils.waitForEvent(
gBrowser,
"change-view"
);
secondViewButton.buttonEl.click();
await viewChanged;
ok(
secondViewButton.view === mozPageNav.currentView,
"The second page nav button is selected"
)
let thirdPageNavButton = mozPageNav.pageNavButtons[2];
viewChanged = BrowserTestUtils.waitForEvent(
gBrowser,
"change-view"
);
thirdPageNavButton.buttonEl.click();
await viewChanged;
ok(
thirdPageNavButton.view === mozPageNav.currentView,
"The third page nav button is selected"
)
let firstPageNavButton = mozPageNav.pageNavButtons[0];
viewChanged = BrowserTestUtils.waitForEvent(
gBrowser,
"change-view"
);
firstPageNavButton.buttonEl.click();
await viewChanged;
ok(
firstPageNavButton.view === mozPageNav.currentView,
"The first page nav button is selected"
)
});
/**
* Tests that categories are keyboard-navigable
*/
add_task(async function test_keyboard_navigation() {
const arrowDown = async () => {
info("Arrow down");
synthesizeKey("KEY_ArrowDown", {});
await mozPageNav.updateComplete;
};
const arrowUp = async () => {
info("Arrow up");
synthesizeKey("KEY_ArrowUp", {});
await mozPageNav.updateComplete;
};
const arrowLeft = async () => {
info("Arrow left");
synthesizeKey("KEY_ArrowLeft", {});
await mozPageNav.updateComplete;
};
const arrowRight = async () => {
info("Arrow right");
synthesizeKey("KEY_ArrowRight", {});
await mozPageNav.updateComplete;
};
// Setting this pref allows the test to run as expected with a keyboard on MacOS
await SpecialPowers.pushPrefEnv({
set: [["accessibility.tabfocus", 7]],
});
let firstPageNavButton = mozPageNav.pageNavButtons[0];
let secondPageNavButton = mozPageNav.pageNavButtons[1];
let thirdPageNavButton = mozPageNav.pageNavButtons[2];
let fourthPageNavButton = mozPageNav.pageNavButtons[3];
let fifthPageNavButton = mozPageNav.pageNavButtons[4];
is(
firstPageNavButton.view,
mozPageNav.currentView,
"The first page nav button is selected"
)
firstPageNavButton.buttonEl.focus();
await arrowDown();
ok(
isActiveElement(secondPageNavButton),
"The second page nav button is the active element after first arrow down"
);
is(
secondPageNavButton.view,
mozPageNav.currentView,
"The second page nav button is selected"
)
await arrowDown();
is(
thirdPageNavButton.view,
mozPageNav.currentView,
"The third page nav button is selected"
)
await arrowDown();
is(
fourthPageNavButton.view,
mozPageNav.currentView,
"The fourth page nav button is selected"
)
await arrowDown();
is(
fifthPageNavButton.view,
mozPageNav.currentView,
"The fifth page nav button is selected"
)
await arrowDown();
is(
fifthPageNavButton.view,
mozPageNav.currentView,
"The fifth page nav button is still selected"
)
await arrowUp();
is(
fourthPageNavButton.view,
mozPageNav.currentView,
"The fourth page nav button is selected"
)
await arrowUp();
is(
thirdPageNavButton.view,
mozPageNav.currentView,
"The third page nav button is selected"
)
await arrowUp();
is(
secondPageNavButton.view,
mozPageNav.currentView,
"The second page nav button is selected"
)
await arrowUp();
is(
firstPageNavButton.view,
mozPageNav.currentView,
"The first page nav button is selected"
)
await arrowUp();
is(
firstPageNavButton.view,
mozPageNav.currentView,
"The first page nav button is still selected"
)
// Test navigation with arrow left/right keys
is(
firstPageNavButton.view,
mozPageNav.currentView,
"The first page nav button is selected"
)
firstPageNavButton.buttonEl.focus();
await arrowRight();
ok(
isActiveElement(secondPageNavButton),
"The second page nav button is the active element after first arrow right"
);
is(
secondPageNavButton.view,
mozPageNav.currentView,
"The second page nav button is selected"
)
await arrowRight();
is(
thirdPageNavButton.view,
mozPageNav.currentView,
"The third page nav button is selected"
)
await arrowRight();
is(
fourthPageNavButton.view,
mozPageNav.currentView,
"The fourth page nav button is selected"
)
await arrowRight();
is(
fifthPageNavButton.view,
mozPageNav.currentView,
"The fifth page nav button is selected"
)
await arrowRight();
is(
fifthPageNavButton.view,
mozPageNav.currentView,
"The fifth page nav button is still selected"
)
await arrowLeft();
is(
fourthPageNavButton.view,
mozPageNav.currentView,
"The fourth page nav button is selected"
)
await arrowLeft();
is(
thirdPageNavButton.view,
mozPageNav.currentView,
"The third page nav button is selected"
)
await arrowLeft();
is(
secondPageNavButton.view,
mozPageNav.currentView,
"The second page nav button is selected"
)
await arrowLeft();
is(
firstPageNavButton.view,
mozPageNav.currentView,
"The first page nav button is selected"
)
await arrowLeft();
is(
firstPageNavButton.view,
mozPageNav.currentView,
"The first page nav button is still selected"
)
await SpecialPowers.popPrefEnv();
});
</script>
</body>
</html>

View file

@ -3,12 +3,15 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
:host { :host {
border-radius: 4px; border-radius: var(--border-radius-small);
&:focus-visible {
outline-offset: var(--page-nav-focus-outline-inset);
}
} }
button { button {
background-color: initial; background-color: var(--page-nav-button-background-color);
border: 1px solid var(--in-content-primary-button-border-color); border: 1px solid var(--page-nav-border-color);
-moz-context-properties: fill, fill-opacity; -moz-context-properties: fill, fill-opacity;
fill: currentColor; fill: currentColor;
display: grid; display: grid;
@ -18,11 +21,11 @@ button {
font-size: inherit; font-size: inherit;
width: 100%; width: 100%;
font-weight: normal; font-weight: normal;
border-radius: 4px; border-radius: var(--page-nav-button-border-radius);
color: inherit; color: var(--page-nav-button-text-color);
text-align: start; text-align: start;
transition: background-color 150ms; transition: background-color 150ms;
padding: var(--fxviewcategorynav-button-padding); padding: var(--page-nav-button-padding);
} }
button:hover { button:hover {
@ -38,8 +41,7 @@ button:hover {
button:hover, button:hover,
button[selected]:hover { button[selected]:hover {
background-color: var(--in-content-button-background-hover); background-color: var(--page-nav-button-background-color-hover);
border-color: var(--in-content-button-border-color-hover);
} }
button[selected]:hover { button[selected]:hover {
@ -57,15 +59,15 @@ button:hover {
} }
button[selected]:not(:hover) { button[selected]:not(:hover) {
color: var(--in-content-accent-color); color: var(--color-accent-primary);
background-color: color-mix(in srgb, var(--fxview-primary-action-background) 5%, transparent); background-color: color-mix(in srgb, var(--page-nav-button-background-color-selected) 5%, transparent);
border-inline-start-color: var(--in-content-accent-color); border-inline-start-color: var(--color-accent-primary);
} }
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
button[selected] { 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 { button[selected]:focus-visible {
outline: var(--focus-outline); outline: var(--focus-outline);
outline-offset: var(--focus-outline-offset); outline-offset: var(--focus-outline-offset);
border-radius: var(--border-radius-small);
} }
.category-icon { .page-nav-icon {
background-color: initial;
background-size: 20px;
background-repeat: no-repeat;
background-position: center;
height: 20px; height: 20px;
width: 20px; width: 20px;
-moz-context-properties: fill; -moz-context-properties: fill;
@ -90,7 +89,7 @@ button[selected]:focus-visible {
button { button {
transition: none; transition: none;
border-color: ButtonText; border-color: ButtonText;
background-color: var(--in-content-button-background); background-color: var(--button-background-color);
} }
button:hover { button:hover {
@ -105,8 +104,7 @@ button[selected]:focus-visible {
} }
slot { slot {
font-size: 1.13em; font-size: var(--font-size-large);
line-height: 1.4;
margin: 0; margin: 0;
padding-inline-start: 0; padding-inline-start: 0;
user-select: none; user-select: none;

View file

@ -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);
}
}

View file

@ -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`
<link
rel="stylesheet"
href="chrome://global/content/elements/moz-page-nav.css"
/>
<nav>
<h1 class="page-nav-header" id="page-nav-header">${this.heading}</h1>
<div
class="primary-nav-group"
role="tablist"
aria-orientation="vertical"
aria-labelledby="page-nav-header"
>
<slot
@change-view=${this.onChangeView}
@keydown=${this.handleFocus}
></slot>
</div>
<div id="secondary-nav-group" role="group">
<slot name="secondary-nav" @keydown=${this.handleFocus}></slot>
</div>
</nav>
`;
}
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`
<link
rel="stylesheet"
href="chrome://global/content/elements/moz-page-nav-button.css"
/>
<button
aria-selected=${this.selected}
tabindex=${this.selected ? 0 : -1}
role="tab"
?selected=${this.selected}
@click=${this.activate}
>
<img class="page-nav-icon" src=${this.iconSrc} />
<slot></slot>
</button>
`;
}
}
customElements.define("moz-page-nav-button", MozPageNavButton);

View file

@ -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`
<style>
#page {
height: 100%;
display: flex;
@media (max-width: 52rem) {
grid-template-columns: 82px 1fr;
}
}
moz-page-nav {
margin-inline-start: 10px;
--page-nav-margin-top: 10px;
@media (max-width: 52rem) {
margin-inline-start: 0;
}
}
</style>
<div id="page">
<moz-page-nav data-l10n-id="moz-page-nav-heading" data-l10n-attrs="heading">
<moz-page-nav-button
view="view-one"
data-l10n-id="moz-page-nav-button-one"
iconSrc="chrome://browser/skin/preferences/category-general.svg"
>
</moz-page-nav-button>
<moz-page-nav-button
view="view-two"
data-l10n-id="moz-page-nav-button-two"
iconSrc="chrome://browser/skin/preferences/category-general.svg"
>
</moz-page-nav-button>
<moz-page-nav-button
view="view-three"
data-l10n-id="moz-page-nav-button-three"
iconSrc="chrome://browser/skin/preferences/category-general.svg"
>
</moz-page-nav-button>
</moz-page-nav>
<main></main>
</div>
`;
export const Default = Template.bind({});
Default.args = {};