summaryrefslogtreecommitdiffstats
path: root/browser/components/firefoxview
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:13:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:13:27 +0000
commit40a355a42d4a9444dc753c04c6608dade2f06a23 (patch)
tree871fc667d2de662f171103ce5ec067014ef85e61 /browser/components/firefoxview
parentAdding upstream version 124.0.1. (diff)
downloadfirefox-40a355a42d4a9444dc753c04c6608dade2f06a23.tar.xz
firefox-40a355a42d4a9444dc753c04c6608dade2f06a23.zip
Adding upstream version 125.0.1.upstream/125.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/firefoxview')
-rw-r--r--browser/components/firefoxview/OpenTabs.sys.mjs18
-rw-r--r--browser/components/firefoxview/content/view-history.svg (renamed from browser/components/firefoxview/content/category-history.svg)0
-rw-r--r--browser/components/firefoxview/content/view-opentabs.svg (renamed from browser/components/firefoxview/content/category-opentabs.svg)0
-rw-r--r--browser/components/firefoxview/content/view-recentbrowsing.svg (renamed from browser/components/firefoxview/content/category-recentbrowsing.svg)0
-rw-r--r--browser/components/firefoxview/content/view-recentlyclosed.svg (renamed from browser/components/firefoxview/content/category-recentlyclosed.svg)0
-rw-r--r--browser/components/firefoxview/content/view-syncedtabs.svg (renamed from browser/components/firefoxview/content/category-syncedtabs.svg)0
-rw-r--r--browser/components/firefoxview/firefoxview.css46
-rw-r--r--browser/components/firefoxview/firefoxview.html59
-rw-r--r--browser/components/firefoxview/firefoxview.mjs56
-rw-r--r--browser/components/firefoxview/fxview-category-button.css125
-rw-r--r--browser/components/firefoxview/fxview-category-navigation.css60
-rw-r--r--browser/components/firefoxview/fxview-category-navigation.mjs150
-rw-r--r--browser/components/firefoxview/fxview-tab-list.css29
-rw-r--r--browser/components/firefoxview/fxview-tab-list.mjs496
-rw-r--r--browser/components/firefoxview/fxview-tab-row.css91
-rw-r--r--browser/components/firefoxview/history.mjs6
-rw-r--r--browser/components/firefoxview/jar.mn13
-rw-r--r--browser/components/firefoxview/opentabs.mjs252
-rw-r--r--browser/components/firefoxview/recentlyclosed.mjs1
-rw-r--r--browser/components/firefoxview/syncedtabs.mjs3
-rw-r--r--browser/components/firefoxview/tests/browser/FirefoxViewTestUtils.sys.mjs17
-rw-r--r--browser/components/firefoxview/tests/browser/browser.toml45
-rw-r--r--browser/components/firefoxview/tests/browser/browser_dragDrop_after_opening_fxViewTab.js1
-rw-r--r--browser/components/firefoxview/tests/browser/browser_firefoxview.js5
-rw-r--r--browser/components/firefoxview/tests/browser/browser_firefoxview_general_telemetry.js42
-rw-r--r--browser/components/firefoxview/tests/browser/browser_firefoxview_navigation.js54
-rw-r--r--browser/components/firefoxview/tests/browser/browser_firefoxview_paused.js106
-rw-r--r--browser/components/firefoxview/tests/browser/browser_firefoxview_search_telemetry.js18
-rw-r--r--browser/components/firefoxview/tests/browser/browser_firefoxview_tab.js6
-rw-r--r--browser/components/firefoxview/tests/browser/browser_firefoxview_virtual_list.js2
-rw-r--r--browser/components/firefoxview/tests/browser/browser_history_firefoxview.js75
-rw-r--r--browser/components/firefoxview/tests/browser/browser_opentabs_cards.js431
-rw-r--r--browser/components/firefoxview/tests/browser/browser_opentabs_changes.js7
-rw-r--r--browser/components/firefoxview/tests/browser/browser_opentabs_firefoxview.js159
-rw-r--r--browser/components/firefoxview/tests/browser/browser_opentabs_more.js151
-rw-r--r--browser/components/firefoxview/tests/browser/browser_opentabs_pinned_tabs.js481
-rw-r--r--browser/components/firefoxview/tests/browser/browser_opentabs_recency.js31
-rw-r--r--browser/components/firefoxview/tests/browser/browser_opentabs_search.js161
-rw-r--r--browser/components/firefoxview/tests/browser/browser_opentabs_tab_indicators.js128
-rw-r--r--browser/components/firefoxview/tests/browser/browser_recentlyclosed_firefoxview.js18
-rw-r--r--browser/components/firefoxview/tests/browser/browser_syncedtabs_errors_firefoxview.js4
-rw-r--r--browser/components/firefoxview/tests/browser/browser_syncedtabs_firefoxview.js20
-rw-r--r--browser/components/firefoxview/tests/browser/browser_tab_list_keyboard_navigation.js334
-rw-r--r--browser/components/firefoxview/tests/browser/head.js83
-rw-r--r--browser/components/firefoxview/tests/chrome/chrome.toml2
-rw-r--r--browser/components/firefoxview/tests/chrome/test_fxview_category_navigation.html322
-rw-r--r--browser/components/firefoxview/tests/chrome/test_fxview_tab_list.html4
-rw-r--r--browser/components/firefoxview/viewpage.mjs28
48 files changed, 2535 insertions, 1605 deletions
diff --git a/browser/components/firefoxview/OpenTabs.sys.mjs b/browser/components/firefoxview/OpenTabs.sys.mjs
index ac247f5e8f..0771bf9e65 100644
--- a/browser/components/firefoxview/OpenTabs.sys.mjs
+++ b/browser/components/firefoxview/OpenTabs.sys.mjs
@@ -36,6 +36,8 @@ const TAB_RECENCY_CHANGE_EVENTS = Object.freeze([
"TabAttrModified",
"TabClose",
"TabOpen",
+ "TabPinned",
+ "TabUnpinned",
"TabSelect",
"TabAttrModified",
]);
@@ -329,17 +331,21 @@ class OpenTabsTarget extends EventTarget {
return [];
}
+ /**
+ * Get an aggregated list of tabs from all the same-privateness browser windows.
+ *
+ * @returns {MozTabbrowserTab[]}
+ */
+ getAllTabs() {
+ return this.currentWindows.flatMap(win => this.getTabsForWindow(win));
+ }
+
/*
* @returns {Array<Tab>}
* A by-recency-sorted, aggregated list of tabs from all the same-privateness browser windows.
*/
getRecentTabs() {
- const tabs = [];
- for (let win of this.currentWindows) {
- tabs.push(...this.getTabsForWindow(win));
- }
- tabs.sort(lastSeenActiveSort);
- return tabs;
+ return this.getAllTabs().sort(lastSeenActiveSort);
}
handleEvent({ detail, target, type }) {
diff --git a/browser/components/firefoxview/content/category-history.svg b/browser/components/firefoxview/content/view-history.svg
index a6dc259483..a6dc259483 100644
--- a/browser/components/firefoxview/content/category-history.svg
+++ b/browser/components/firefoxview/content/view-history.svg
diff --git a/browser/components/firefoxview/content/category-opentabs.svg b/browser/components/firefoxview/content/view-opentabs.svg
index 2172558a42..2172558a42 100644
--- a/browser/components/firefoxview/content/category-opentabs.svg
+++ b/browser/components/firefoxview/content/view-opentabs.svg
diff --git a/browser/components/firefoxview/content/category-recentbrowsing.svg b/browser/components/firefoxview/content/view-recentbrowsing.svg
index f4c523dafa..f4c523dafa 100644
--- a/browser/components/firefoxview/content/category-recentbrowsing.svg
+++ b/browser/components/firefoxview/content/view-recentbrowsing.svg
diff --git a/browser/components/firefoxview/content/category-recentlyclosed.svg b/browser/components/firefoxview/content/view-recentlyclosed.svg
index 7cac65ac58..7cac65ac58 100644
--- a/browser/components/firefoxview/content/category-recentlyclosed.svg
+++ b/browser/components/firefoxview/content/view-recentlyclosed.svg
diff --git a/browser/components/firefoxview/content/category-syncedtabs.svg b/browser/components/firefoxview/content/view-syncedtabs.svg
index bd9749743c..bd9749743c 100644
--- a/browser/components/firefoxview/content/category-syncedtabs.svg
+++ b/browser/components/firefoxview/content/view-syncedtabs.svg
diff --git a/browser/components/firefoxview/firefoxview.css b/browser/components/firefoxview/firefoxview.css
index 48cf5a9490..6811ca54c4 100644
--- a/browser/components/firefoxview/firefoxview.css
+++ b/browser/components/firefoxview/firefoxview.css
@@ -16,6 +16,7 @@
--fxview-text-color-hover: var(--newtab-text-primary-color);
--fxview-primary-action-background: var(--newtab-primary-action-background, #0061e0);
--fxview-border: var(--fc-border-light, #CFCFD8);
+ --fxview-indicator-stroke-color-hover: #DEDDDE;
/* ensure utility button hover states match those of the rest of the page */
--in-content-button-background-hover: var(--fxview-element-background-hover);
@@ -37,6 +38,7 @@
--fxview-element-background-hover: color-mix(in srgb, var(--fxview-background-color) 80%, white);
--fxview-element-background-active: color-mix(in srgb, var(--fxview-background-color) 60%, white);
--fxview-border: #8F8F9D;
+ --fxview-indicator-stroke-color-hover:#5D5C66;
/* copy over newtab colors from activity-stream-[os].css files */
--newtab-background-color: #2B2A33;
@@ -69,6 +71,10 @@ body {
grid-template-columns: var(--fxview-sidebar-width) 1fr;
background-color: var(--fxview-background-color);
color: var(--fxview-text-primary-color);
+
+ @media (max-width: 52rem) {
+ display: flex;
+ }
}
.main-container {
@@ -88,34 +94,6 @@ body {
margin: 0;
}
-fxview-category-button:focus-visible {
- outline-offset: var(--in-content-focus-outline-inset);
-}
-
-fxview-category-button[name="recentbrowsing"]::part(icon) {
- background-image: url("chrome://browser/content/firefoxview/category-recentbrowsing.svg");
-}
-fxview-category-button[name="opentabs"]::part(icon) {
- background-image: url("chrome://browser/content/firefoxview/category-opentabs.svg");
-}
-fxview-category-button[name="recentlyclosed"]::part(icon) {
- background-image: url("chrome://browser/content/firefoxview/category-recentlyclosed.svg");
-}
-fxview-category-button[name="syncedtabs"]::part(icon) {
- background-image: url("chrome://browser/content/firefoxview/category-syncedtabs.svg");
-}
-fxview-category-button[name="history"]::part(icon) {
- background-image: url("chrome://browser/content/firefoxview/category-history.svg");
-}
-
-fxview-tab-list.with-dismiss-button::part(secondary-button) {
- background-image: url("chrome://global/skin/icons/close.svg");
-}
-
-fxview-tab-list.with-context-menu::part(secondary-button) {
- background-image: url("chrome://global/skin/icons/more.svg");
-}
-
.sticky-container {
position: sticky;
top: 0;
@@ -170,18 +148,6 @@ panel-item::part(button):hover:active {
background-color: var(--fxview-element-background-active);
}
-panel-list {
- overflow-y: visible;
-}
-
-fxview-category-navigation {
- overflow-y: auto;
-}
-
-fxview-category-navigation h1 {
- margin-block: 0;
-}
-
fxview-empty-state:not([isSelectedTab]) button[slot="primary-action"] {
margin-inline-start: 0;
}
diff --git a/browser/components/firefoxview/firefoxview.html b/browser/components/firefoxview/firefoxview.html
index 1f53a1d0c9..6fa0f59a8f 100644
--- a/browser/components/firefoxview/firefoxview.html
+++ b/browser/components/firefoxview/firefoxview.html
@@ -15,8 +15,6 @@
<link rel="localization" href="branding/brand.ftl" />
<link rel="localization" href="toolkit/branding/accounts.ftl" />
<link rel="localization" href="browser/firefoxView.ftl" />
- <link rel="localization" href="branding/brand.ftl" />
- <link rel="localization" href="toolkit/branding/accounts.ftl" />
<link rel="localization" href="toolkit/branding/brandings.ftl" />
<link rel="localization" href="browser/migrationWizard.ftl" />
<link
@@ -41,54 +39,51 @@
></script>
<script
type="module"
- src="chrome://browser/content/firefoxview/fxview-category-navigation.mjs"
+ src="chrome://browser/content/firefoxview/syncedtabs.mjs"
></script>
<script
type="module"
- src="chrome://browser/content/firefoxview/syncedtabs.mjs"
+ src="chrome://global/content/elements/moz-page-nav.mjs"
></script>
<script src="chrome://browser/content/contentTheme.js"></script>
</head>
<body>
- <fxview-category-navigation>
- <h1 slot="category-nav-header" data-l10n-id="firefoxview-page-title"></h1>
- <fxview-category-button
- class="category"
- slot="category-button"
- name="recentbrowsing"
+ <moz-page-nav
+ data-l10n-id="firefoxview-page-heading"
+ data-l10n-attrs="heading"
+ >
+ <moz-page-nav-button
+ view="recentbrowsing"
data-l10n-id="firefoxview-overview-nav"
+ iconSrc="chrome://browser/content/firefoxview/view-recentbrowsing.svg"
>
- </fxview-category-button>
- <fxview-category-button
- class="category"
- slot="category-button"
- name="opentabs"
+ </moz-page-nav-button>
+ <moz-page-nav-button
+ view="opentabs"
data-l10n-id="firefoxview-opentabs-nav"
+ iconSrc="chrome://browser/content/firefoxview/view-opentabs.svg"
>
- </fxview-category-button>
- <fxview-category-button
- class="category"
- slot="category-button"
- name="recentlyclosed"
+ </moz-page-nav-button>
+ <moz-page-nav-button
+ view="recentlyclosed"
data-l10n-id="firefoxview-recently-closed-nav"
+ iconSrc="chrome://browser/content/firefoxview/view-recentlyclosed.svg"
>
- </fxview-category-button>
- <fxview-category-button
- class="category"
- slot="category-button"
- name="syncedtabs"
+ </moz-page-nav-button>
+ <moz-page-nav-button
+ view="syncedtabs"
data-l10n-id="firefoxview-synced-tabs-nav"
+ iconSrc="chrome://browser/content/firefoxview/view-syncedtabs.svg"
>
- </fxview-category-button>
- <fxview-category-button
- class="category"
- slot="category-button"
- name="history"
+ </moz-page-nav-button>
+ <moz-page-nav-button
+ view="history"
data-l10n-id="firefoxview-history-nav"
+ iconSrc="chrome://browser/content/firefoxview/view-history.svg"
>
- </fxview-category-button>
- </fxview-category-navigation>
+ </moz-page-nav-button>
+ </moz-page-nav>
<main id="pages" role="application" data-l10n-id="firefoxview-page-label">
<div class="main-container">
<named-deck>
diff --git a/browser/components/firefoxview/firefoxview.mjs b/browser/components/firefoxview/firefoxview.mjs
index 77f4c06cc7..3e61482cc0 100644
--- a/browser/components/firefoxview/firefoxview.mjs
+++ b/browser/components/firefoxview/firefoxview.mjs
@@ -3,35 +3,29 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
let pageList = [];
-let categoryPagesDeck = null;
-let categoryNavigation = null;
+let viewsDeck = null;
+let pageNav = null;
let activeComponent = null;
let searchKeyboardShortcut = null;
const { topChromeWindow } = window.browsingContext;
function onHashChange() {
- let page = document.location?.hash.substring(1);
- if (!page || !pageList.includes(page)) {
- page = "recentbrowsing";
+ let view = document.location?.hash.substring(1);
+ if (!view || !pageList.includes(view)) {
+ view = "recentbrowsing";
}
- changePage(page);
+ changeView(view);
}
-function changePage(page) {
- categoryPagesDeck.selectedViewName = page;
- categoryNavigation.currentCategory = page;
- if (categoryNavigation.categoryButtons.includes(document.activeElement)) {
- let currentCategoryButton = categoryNavigation.categoryButtons.find(
- categoryButton => categoryButton.name === page
- );
- (currentCategoryButton || categoryNavigation.categoryButtons[0]).focus();
- }
+function changeView(view) {
+ viewsDeck.selectedViewName = view;
+ pageNav.currentView = view;
}
-function onPagesDeckViewChange() {
- for (const child of categoryPagesDeck.children) {
- if (child.getAttribute("name") == categoryPagesDeck.selectedViewName) {
+function onViewsDeckViewChange() {
+ for (const child of viewsDeck.children) {
+ if (child.getAttribute("name") == viewsDeck.selectedViewName) {
child.enter();
activeComponent = child;
} else {
@@ -41,11 +35,11 @@ function onPagesDeckViewChange() {
}
function recordNavigationTelemetry(source, eventTarget) {
- let page = "recentbrowsing";
+ let view = "recentbrowsing";
if (source === "category-navigation") {
- page = eventTarget.parentNode.currentCategory;
+ view = eventTarget.parentNode.currentView;
} else if (source === "view-all") {
- page = eventTarget.shortPageName;
+ view = eventTarget.shortPageName;
}
// Record telemetry
Services.telemetry.recordEvent(
@@ -54,7 +48,7 @@ function recordNavigationTelemetry(source, eventTarget) {
"navigation",
null,
{
- page,
+ page: view,
source,
}
);
@@ -73,7 +67,7 @@ async function updateSearchTextboxSize() {
const placeholder = msg.attributes[0].value;
maxLength = Math.max(maxLength, placeholder.length);
}
- for (const child of categoryPagesDeck.children) {
+ for (const child of viewsDeck.children) {
child.searchTextboxSize = maxLength;
}
}
@@ -89,15 +83,15 @@ async function updateSearchKeyboardShortcut() {
window.addEventListener("DOMContentLoaded", async () => {
recordEnteredTelemetry();
- categoryNavigation = document.querySelector("fxview-category-navigation");
- categoryPagesDeck = document.querySelector("named-deck");
+ pageNav = document.querySelector("moz-page-nav");
+ viewsDeck = document.querySelector("named-deck");
- for (const item of categoryNavigation.categoryButtons) {
- pageList.push(item.getAttribute("name"));
+ for (const item of pageNav.pageNavButtons) {
+ pageList.push(item.getAttribute("view"));
}
window.addEventListener("hashchange", onHashChange);
- window.addEventListener("change-category", function (event) {
- location.hash = event.target.getAttribute("name");
+ window.addEventListener("change-view", function (event) {
+ location.hash = event.target.getAttribute("view");
window.scrollTo(0, 0);
recordNavigationTelemetry("category-navigation", event.target);
});
@@ -105,11 +99,11 @@ window.addEventListener("DOMContentLoaded", async () => {
recordNavigationTelemetry("view-all", event.originalTarget);
});
- categoryPagesDeck.addEventListener("view-changed", onPagesDeckViewChange);
+ viewsDeck.addEventListener("view-changed", onViewsDeckViewChange);
// set the initial state
onHashChange();
- onPagesDeckViewChange();
+ onViewsDeckViewChange();
await updateSearchTextboxSize();
await updateSearchKeyboardShortcut();
diff --git a/browser/components/firefoxview/fxview-category-button.css b/browser/components/firefoxview/fxview-category-button.css
deleted file mode 100644
index 1bce29f343..0000000000
--- a/browser/components/firefoxview/fxview-category-button.css
+++ /dev/null
@@ -1,125 +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 {
- border-radius: 4px;
-}
-
-button {
- background-color: initial;
- border: 1px solid var(--in-content-primary-button-border-color);
- -moz-context-properties: fill, fill-opacity;
- fill: currentColor;
- display: grid;
- grid-template-columns: min-content 1fr;
- gap: 12px;
- align-items: center;
- font-size: inherit;
- width: 100%;
- font-weight: normal;
- border-radius: 4px;
- color: inherit;
- text-align: start;
- transition: background-color 150ms;
- padding: var(--fxviewcategorynav-button-padding);
-}
-
-button:hover {
- cursor: pointer;
-}
-
-@media not (prefers-contrast) {
- button {
- border-inline-start: 2px solid transparent;
- border-inline-end: none;
- border-block: none;
- }
-
- button:hover,
- button[selected]:hover {
- background-color: var(--in-content-button-background-hover);
- border-color: var(--in-content-button-border-color-hover);
- }
-
- button[selected]:hover {
- border-inline-start-color: inherit;
- }
-
- button[selected],
- button[selected]:hover {
- border-inline-start: 2px solid;
- }
-
- button[selected]:not(:focus-visible) {
- border-start-start-radius: 0;
- border-end-start-radius: 0;
- }
-
- button[selected]:not(:hover) {
- color: var(--in-content-accent-color);
- background-color: color-mix(in srgb, var(--fxview-primary-action-background) 5%, transparent);
- border-inline-start-color: var(--in-content-accent-color);
- }
-}
-
-@media (prefers-color-scheme: dark) {
- button[selected] {
- background-color: color-mix(in srgb, var(--fxview-primary-action-background) 12%, transparent);
- }
-}
-
-button:focus-visible,
-button[selected]:focus-visible {
- outline: var(--focus-outline);
- outline-offset: var(--focus-outline-offset);
-}
-
-.category-icon {
- background-color: initial;
- background-size: 20px;
- background-repeat: no-repeat;
- background-position: center;
- height: 20px;
- width: 20px;
- -moz-context-properties: fill;
- fill: currentColor;
-}
-
-@media (prefers-contrast) {
- button {
- transition: none;
- border-color: ButtonText;
- background-color: var(--in-content-button-background);
- }
-
- button:hover {
- color: SelectedItem;
- }
-
- button[selected] {
- color: SelectedItemText;
- background-color: SelectedItem;
- border-color: SelectedItem;
- }
-}
-
-slot {
- font-size: 1.13em;
- line-height: 1.4;
- margin: 0;
- padding-inline-start: 0;
- user-select: none;
-}
-
-@media (max-width: 52rem) {
- button {
- grid-template-columns: min-content;
- justify-content: center;
- margin-inline: 0;
- }
-
- slot {
- display: none;
- }
-}
diff --git a/browser/components/firefoxview/fxview-category-navigation.css b/browser/components/firefoxview/fxview-category-navigation.css
deleted file mode 100644
index 571059699b..0000000000
--- a/browser/components/firefoxview/fxview-category-navigation.css
+++ /dev/null
@@ -1,60 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-:host {
- --fxviewcategorynav-button-padding: 8px;
- margin-inline-start: 42px;
- position: sticky;
- top: 0;
- height: 100vh;
-}
-
-nav {
- display: grid;
- grid-template-rows: min-content 1fr auto;
- gap: 25px;
- margin-block-start: var(--fxview-margin-top);
-}
-
-.category-nav-header {
- /* Align the header text/icon with the category button icons */
- margin-inline-start: var(--fxviewcategorynav-button-padding);
-}
-
-.category-nav-buttons,
-::slotted(.category-nav-footer) {
- display: grid;
- grid-template-columns: 1fr;
- grid-auto-rows: min-content;
- gap: 4px;
-}
-
-@media (prefers-contrast) {
- .category-nav-buttons {
- gap: 8px;
- }
-}
-
-@media (prefers-reduced-motion) {
- /* (See Bug 1610081) Setting border-inline-end to add clear differentiation between side navigation and main content area */
- :host {
- border-inline-end: 1px solid var(--in-content-border-color);
- }
-}
-
-@media (max-width: 52rem) {
- :host {
- grid-template-rows: 1fr auto;
- }
-
- .category-nav-header {
- display: none;
- }
-
- .category-nav-buttons,
- ::slotted(.category-nav-footer) {
- justify-content: center;
- grid-template-columns: min-content;
- }
-}
diff --git a/browser/components/firefoxview/fxview-category-navigation.mjs b/browser/components/firefoxview/fxview-category-navigation.mjs
deleted file mode 100644
index abacd17df1..0000000000
--- a/browser/components/firefoxview/fxview-category-navigation.mjs
+++ /dev/null
@@ -1,150 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { html } from "chrome://global/content/vendor/lit.all.mjs";
-import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
-
-export default class FxviewCategoryNavigation extends MozLitElement {
- static properties = {
- currentCategory: { type: String },
- };
-
- static queries = {
- categoryButtonsSlot: "slot[name=category-button]",
- };
-
- get categoryButtons() {
- return this.categoryButtonsSlot
- .assignedNodes()
- .filter(node => !node.hidden);
- }
-
- onChangeCategory(e) {
- this.currentCategory = e.target.name;
- }
-
- handleFocus(e) {
- if (e.key == "ArrowDown" || e.key == "ArrowRight") {
- e.preventDefault();
- this.focusNextCategory();
- } else if (e.key == "ArrowUp" || e.key == "ArrowLeft") {
- e.preventDefault();
- this.focusPreviousCategory();
- }
- }
-
- focusPreviousCategory() {
- let categoryButtons = this.categoryButtons;
- let currentIndex = categoryButtons.findIndex(b => b.selected);
- let prev = categoryButtons[currentIndex - 1];
- if (prev) {
- prev.activate();
- prev.focus();
- }
- }
-
- focusNextCategory() {
- let categoryButtons = this.categoryButtons;
- let currentIndex = categoryButtons.findIndex(b => b.selected);
- let next = categoryButtons[currentIndex + 1];
- if (next) {
- next.activate();
- next.focus();
- }
- }
-
- render() {
- return html`
- <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);
diff --git a/browser/components/firefoxview/fxview-tab-list.css b/browser/components/firefoxview/fxview-tab-list.css
index d32d9c9c08..5a4bff023a 100644
--- a/browser/components/firefoxview/fxview-tab-list.css
+++ b/browser/components/firefoxview/fxview-tab-list.css
@@ -2,14 +2,33 @@
* 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/. */
- .fxview-tab-list {
+:host {
display: grid;
- grid-template-columns: min-content 3fr min-content 2fr 1fr 1fr min-content min-content;
- gap: 6px;
+ row-gap: var(--space-xsmall);
}
-:host([compactRows]) .fxview-tab-list {
- grid-template-columns: min-content 1fr min-content min-content min-content;
+.fxview-tab-list {
+ display: grid;
+ grid-template-columns: min-content 3fr min-content 2fr 1fr 1fr min-content min-content;
+ gap: var(--space-xsmall);
+
+ &.pinned {
+ display: flex;
+ flex-wrap: wrap;
+
+ > virtual-list {
+ display: block;
+ }
+
+ > fxview-tab-row {
+ display: block;
+ margin-block-end: var(--space-xsmall);
+ }
+ }
+
+ :host([compactRows]) & {
+ grid-template-columns: min-content 1fr min-content min-content min-content;
+ }
}
virtual-list {
diff --git a/browser/components/firefoxview/fxview-tab-list.mjs b/browser/components/firefoxview/fxview-tab-list.mjs
index 055540722a..978ab79724 100644
--- a/browser/components/firefoxview/fxview-tab-list.mjs
+++ b/browser/components/firefoxview/fxview-tab-list.mjs
@@ -45,8 +45,11 @@ if (!window.IS_STORYBOOK) {
* @property {string} dateTimeFormat - Expected format for date and/or time
* @property {string} hasPopup - The aria-haspopup attribute for the secondary action, if required
* @property {number} maxTabsLength - The max number of tabs for the list
+ * @property {boolean} pinnedTabsGridView - Whether to show pinned tabs in a grid view
* @property {Array} tabItems - Items to show in the tab list
* @property {string} searchQuery - The query string to highlight, if provided.
+ * @property {string} secondaryActionClass - The class used to style the secondary action element
+ * @property {string} tertiaryActionClass - The class used to style the tertiary action element
*/
export default class FxviewTabList extends MozLitElement {
constructor() {
@@ -59,6 +62,9 @@ export default class FxviewTabList extends MozLitElement {
this.dateTimeFormat = "relative";
this.maxTabsLength = 25;
this.tabItems = [];
+ this.pinnedTabs = [];
+ this.pinnedTabsGridView = false;
+ this.unpinnedTabs = [];
this.compactRows = false;
this.updatesPaused = true;
this.#register();
@@ -71,9 +77,12 @@ export default class FxviewTabList extends MozLitElement {
dateTimeFormat: { type: String },
hasPopup: { type: String },
maxTabsLength: { type: Number },
+ pinnedTabsGridView: { type: Boolean },
tabItems: { type: Array },
updatesPaused: { type: Boolean },
searchQuery: { type: String },
+ secondaryActionClass: { type: String },
+ tertiaryActionClass: { type: String },
};
static queries = {
@@ -99,8 +108,20 @@ export default class FxviewTabList extends MozLitElement {
}
}
- if (this.maxTabsLength > 0) {
+ // Move pinned tabs to the beginning of the list
+ if (this.pinnedTabsGridView) {
// Can set maxTabsLength to -1 to have no max
+ this.unpinnedTabs = this.tabItems.filter(
+ tab => !tab.indicators?.includes("pinned")
+ );
+ this.pinnedTabs = this.tabItems.filter(tab =>
+ tab.indicators?.includes("pinned")
+ );
+ if (this.maxTabsLength > 0) {
+ this.unpinnedTabs = this.unpinnedTabs.slice(0, this.maxTabsLength);
+ }
+ this.tabItems = [...this.pinnedTabs, ...this.unpinnedTabs];
+ } else if (this.maxTabsLength > 0) {
this.tabItems = this.tabItems.slice(0, this.maxTabsLength);
}
}
@@ -176,56 +197,93 @@ export default class FxviewTabList extends MozLitElement {
if (e.code == "ArrowUp") {
// Focus either the link or button of the previous row based on this.currentActiveElementId
e.preventDefault();
- this.focusPrevRow();
+ if (
+ (this.pinnedTabsGridView &&
+ this.activeIndex >= this.pinnedTabs.length) ||
+ !this.pinnedTabsGridView
+ ) {
+ this.focusPrevRow();
+ }
} else if (e.code == "ArrowDown") {
// Focus either the link or button of the next row based on this.currentActiveElementId
e.preventDefault();
- this.focusNextRow();
+ if (
+ this.pinnedTabsGridView &&
+ this.activeIndex < this.pinnedTabs.length
+ ) {
+ this.focusIndex(this.pinnedTabs.length);
+ } else {
+ this.focusNextRow();
+ }
} else if (e.code == "ArrowRight") {
// Focus either the link or the button in the current row and
// set this.currentActiveElementId to that element's ID
e.preventDefault();
if (document.dir == "rtl") {
- if (
- (fxviewTabRow.soundPlaying || fxviewTabRow.muted) &&
- this.currentActiveElementId === "fxview-tab-row-secondary-button"
- ) {
- this.currentActiveElementId = fxviewTabRow.focusMediaButton();
- } else {
- this.currentActiveElementId = fxviewTabRow.focusLink();
- }
- } else if (
- (fxviewTabRow.soundPlaying || fxviewTabRow.muted) &&
- this.currentActiveElementId === "fxview-tab-row-main"
- ) {
- this.currentActiveElementId = fxviewTabRow.focusMediaButton();
+ this.moveFocusLeft(fxviewTabRow);
} else {
- this.currentActiveElementId = fxviewTabRow.focusButton();
+ this.moveFocusRight(fxviewTabRow);
}
} else if (e.code == "ArrowLeft") {
// Focus either the link or the button in the current row and
// set this.currentActiveElementId to that element's ID
e.preventDefault();
if (document.dir == "rtl") {
- if (
- (fxviewTabRow.soundPlaying || fxviewTabRow.muted) &&
- this.currentActiveElementId === "fxview-tab-row-main"
- ) {
- this.currentActiveElementId = fxviewTabRow.focusMediaButton();
- } else {
- this.currentActiveElementId = fxviewTabRow.focusButton();
- }
- } else if (
- (fxviewTabRow.soundPlaying || fxviewTabRow.muted) &&
- this.currentActiveElementId === "fxview-tab-row-secondary-button"
- ) {
- this.currentActiveElementId = fxviewTabRow.focusMediaButton();
+ this.moveFocusRight(fxviewTabRow);
} else {
- this.currentActiveElementId = fxviewTabRow.focusLink();
+ this.moveFocusLeft(fxviewTabRow);
}
}
}
+ moveFocusRight(fxviewTabRow) {
+ if (
+ this.pinnedTabsGridView &&
+ fxviewTabRow.indicators?.includes("pinned")
+ ) {
+ this.focusNextRow();
+ } else if (
+ (fxviewTabRow.indicators?.includes("soundplaying") ||
+ fxviewTabRow.indicators?.includes("muted")) &&
+ this.currentActiveElementId === "fxview-tab-row-main"
+ ) {
+ this.currentActiveElementId = fxviewTabRow.focusMediaButton();
+ } else if (
+ this.currentActiveElementId === "fxview-tab-row-media-button" ||
+ this.currentActiveElementId === "fxview-tab-row-main"
+ ) {
+ this.currentActiveElementId = fxviewTabRow.focusSecondaryButton();
+ } else if (
+ fxviewTabRow.tertiaryButtonEl &&
+ this.currentActiveElementId === "fxview-tab-row-secondary-button"
+ ) {
+ this.currentActiveElementId = fxviewTabRow.focusTertiaryButton();
+ }
+ }
+
+ moveFocusLeft(fxviewTabRow) {
+ if (
+ this.pinnedTabsGridView &&
+ (fxviewTabRow.indicators?.includes("pinned") ||
+ (this.currentActiveElementId === "fxview-tab-row-main" &&
+ this.activeIndex === this.pinnedTabs.length))
+ ) {
+ this.focusPrevRow();
+ } else if (
+ this.currentActiveElementId === "fxview-tab-row-tertiary-button"
+ ) {
+ this.currentActiveElementId = fxviewTabRow.focusSecondaryButton();
+ } else if (
+ (fxviewTabRow.indicators?.includes("soundplaying") ||
+ fxviewTabRow.indicators?.includes("muted")) &&
+ this.currentActiveElementId === "fxview-tab-row-secondary-button"
+ ) {
+ this.currentActiveElementId = fxviewTabRow.focusMediaButton();
+ } else {
+ this.currentActiveElementId = fxviewTabRow.focusLink();
+ }
+ }
+
focusPrevRow() {
this.focusIndex(this.activeIndex - 1);
}
@@ -236,12 +294,18 @@ export default class FxviewTabList extends MozLitElement {
async focusIndex(index) {
// Focus link or button of item
- if (lazy.virtualListEnabledPref) {
- let row = this.rootVirtualListEl.getItem(index);
+ if (
+ ((this.pinnedTabsGridView && index > this.pinnedTabs.length) ||
+ !this.pinnedTabsGridView) &&
+ lazy.virtualListEnabledPref
+ ) {
+ let row = this.rootVirtualListEl.getItem(index - this.pinnedTabs.length);
if (!row) {
return;
}
- let subList = this.rootVirtualListEl.getSubListForItem(index);
+ let subList = this.rootVirtualListEl.getSubListForItem(
+ index - this.pinnedTabs.length
+ );
if (!subList) {
return;
}
@@ -286,23 +350,30 @@ export default class FxviewTabList extends MozLitElement {
return html`
<fxview-tab-row
exportparts="secondary-button"
+ class=${classMap({
+ pinned:
+ this.pinnedTabsGridView && tabItem.indicators?.includes("pinned"),
+ })}
?active=${i == this.activeIndex}
?compact=${this.compactRows}
.hasPopup=${this.hasPopup}
- .containerObj=${tabItem.containerObj}
+ .containerObj=${ifDefined(tabItem.containerObj)}
.currentActiveElementId=${this.currentActiveElementId}
.dateTimeFormat=${this.dateTimeFormat}
.favicon=${tabItem.icon}
- .isBookmark=${ifDefined(tabItem.isBookmark)}
- .muted=${ifDefined(tabItem.muted)}
- .pinned=${ifDefined(tabItem.pinned)}
+ .indicators=${ifDefined(tabItem.indicators)}
+ .pinnedTabsGridView=${ifDefined(this.pinnedTabsGridView)}
.primaryL10nId=${tabItem.primaryL10nId}
.primaryL10nArgs=${ifDefined(tabItem.primaryL10nArgs)}
- role="listitem"
+ role=${this.pinnedTabsGridView && tabItem.indicators?.includes("pinned")
+ ? "none"
+ : "listitem"}
.secondaryL10nId=${tabItem.secondaryL10nId}
.secondaryL10nArgs=${ifDefined(tabItem.secondaryL10nArgs)}
- .attention=${ifDefined(tabItem.attention)}
- .soundPlaying=${ifDefined(tabItem.soundPlaying)}
+ .tertiaryL10nId=${ifDefined(tabItem.tertiaryL10nId)}
+ .tertiaryL10nArgs=${ifDefined(tabItem.tertiaryL10nArgs)}
+ .secondaryActionClass=${this.secondaryActionClass}
+ .tertiaryActionClass=${ifDefined(this.tertiaryActionClass)}
.sourceClosedId=${ifDefined(tabItem.sourceClosedId)}
.sourceWindowId=${ifDefined(tabItem.sourceWindowId)}
.closedId=${ifDefined(tabItem.closedId || tabItem.closedId)}
@@ -311,7 +382,6 @@ export default class FxviewTabList extends MozLitElement {
.time=${ifDefined(time)}
.timeMsPref=${ifDefined(this.timeMsPref)}
.title=${tabItem.title}
- .titleChanged=${ifDefined(tabItem.titleChanged)}
.url=${tabItem.url}
></fxview-tab-row>
`;
@@ -326,9 +396,26 @@ export default class FxviewTabList extends MozLitElement {
rel="stylesheet"
href="chrome://browser/content/firefoxview/fxview-tab-list.css"
/>
+ ${when(
+ this.pinnedTabsGridView && this.pinnedTabs.length,
+ () => html`
+ <div
+ id="fxview-tab-list"
+ class="fxview-tab-list pinned"
+ data-l10n-id="firefoxview-pinned-tabs"
+ role="tablist"
+ @keydown=${this.handleFocusElementInRow}
+ >
+ ${this.pinnedTabs.map((tabItem, i) =>
+ this.itemTemplate(tabItem, i)
+ )}
+ </div>
+ `
+ )}
<div
id="fxview-tab-list"
class="fxview-tab-list"
+ data-l10n-id="firefoxview-tabs"
role="list"
@keydown=${this.handleFocusElementInRow}
>
@@ -337,7 +424,12 @@ export default class FxviewTabList extends MozLitElement {
() => html`
<virtual-list
.activeIndex=${this.activeIndex}
- .items=${this.tabItems}
+ .pinnedTabsIndexOffset=${this.pinnedTabsGridView
+ ? this.pinnedTabs.length
+ : 0}
+ .items=${this.pinnedTabsGridView
+ ? this.unpinnedTabs
+ : this.tabItems}
.template=${this.itemTemplate}
></virtual-list>
`
@@ -374,23 +466,23 @@ customElements.define("fxview-tab-list", FxviewTabList);
* @property {string} currentActiveElementId - ID of currently focused element within each tab item
* @property {string} dateTimeFormat - Expected format for date and/or time
* @property {string} hasPopup - The aria-haspopup attribute for the secondary action, if required
- * @property {boolean} isBookmark - Whether an open tab is bookmarked
+ * @property {string} indicators - An array of tab indicators if any are present
* @property {number} closedId - The tab ID for when the tab item was closed.
* @property {number} sourceClosedId - The closedId of the closed window its from if applicable
* @property {number} sourceWindowId - The sessionstore id of the window its from if applicable
* @property {string} favicon - The favicon for the tab item.
- * @property {boolean} muted - Whether an open tab is muted
- * @property {boolean} pinned - Whether an open tab is pinned
+ * @property {boolean} pinnedTabsGridView - Whether the show pinned tabs in a grid view
* @property {string} primaryL10nId - The l10n id used for the primary action element
* @property {string} primaryL10nArgs - The l10n args used for the primary action element
* @property {string} secondaryL10nId - The l10n id used for the secondary action button
* @property {string} secondaryL10nArgs - The l10n args used for the secondary action element
- * @property {boolean} attention - Whether to show a notification dot
- * @property {boolean} soundPlaying - Whether an open tab has soundPlaying
+ * @property {string} secondaryActionClass - The class used to style the secondary action element
+ * @property {string} tertiaryL10nId - The l10n id used for the tertiary action button
+ * @property {string} tertiaryL10nArgs - The l10n args used for the tertiary action element
+ * @property {string} tertiaryActionClass - The class used to style the tertiary action element
* @property {object} tabElement - The MozTabbrowserTab element for the tab item.
* @property {number} time - The timestamp for when the tab was last accessed.
* @property {string} title - The title for the tab item.
- * @property {boolean} titleChanged - Whether the title has changed for an open tab
* @property {string} url - The url for the tab item.
* @property {number} timeMsPref - The frequency in milliseconds of updates to relative time
* @property {string} searchQuery - The query string to highlight, if provided.
@@ -410,31 +502,33 @@ export class FxviewTabRow extends MozLitElement {
dateTimeFormat: { type: String },
favicon: { type: String },
hasPopup: { type: String },
- isBookmark: { type: Boolean },
- muted: { type: Boolean },
- pinned: { type: Boolean },
+ indicators: { type: Array },
+ pinnedTabsGridView: { type: Boolean },
primaryL10nId: { type: String },
primaryL10nArgs: { type: String },
secondaryL10nId: { type: String },
secondaryL10nArgs: { type: String },
- soundPlaying: { type: Boolean },
+ secondaryActionClass: { type: String },
+ tertiaryL10nId: { type: String },
+ tertiaryL10nArgs: { type: String },
+ tertiaryActionClass: { type: String },
closedId: { type: Number },
sourceClosedId: { type: Number },
sourceWindowId: { type: String },
tabElement: { type: Object },
time: { type: Number },
title: { type: String },
- titleChanged: { type: Boolean },
- attention: { type: Boolean },
timeMsPref: { type: Number },
url: { type: String },
searchQuery: { type: String },
};
static queries = {
- mainEl: ".fxview-tab-row-main",
- buttonEl: "#fxview-tab-row-secondary-button:not([hidden])",
+ mainEl: "#fxview-tab-row-main",
+ secondaryButtonEl: "#fxview-tab-row-secondary-button:not([hidden])",
+ tertiaryButtonEl: "#fxview-tab-row-tertiary-button",
mediaButtonEl: "#fxview-tab-row-media-button",
+ pinnedTabButtonEl: "button#fxview-tab-row-main",
};
get currentFocusable() {
@@ -445,13 +539,40 @@ export class FxviewTabRow extends MozLitElement {
return focusItem;
}
+ connectedCallback() {
+ super.connectedCallback();
+ this.addEventListener("keydown", this.handleKeydown);
+ }
+
+ disconnectedCallback() {
+ super.disconnectedCallback();
+ this.removeEventListener("keydown", this.handleKeydown);
+ }
+
+ handleKeydown(e) {
+ if (
+ this.active &&
+ this.pinnedTabsGridView &&
+ this.indicators?.includes("pinned") &&
+ e.key === "m" &&
+ e.ctrlKey
+ ) {
+ this.muteOrUnmuteTab();
+ }
+ }
+
focus() {
this.currentFocusable.focus();
}
- focusButton() {
- this.buttonEl.focus();
- return this.buttonEl.id;
+ focusSecondaryButton() {
+ this.secondaryButtonEl.focus();
+ return this.secondaryButtonEl.id;
+ }
+
+ focusTertiaryButton() {
+ this.tertiaryButtonEl.focus();
+ return this.tertiaryButtonEl.id;
}
focusMediaButton() {
@@ -562,6 +683,9 @@ export class FxviewTabRow extends MozLitElement {
secondaryActionHandler(event) {
if (
+ (this.pinnedTabsGridView &&
+ this.indicators?.includes("pinned") &&
+ event.type == "contextmenu") ||
(event.type == "click" && event.detail && !event.altKey) ||
// detail=0 is from keyboard
(event.type == "click" && !event.detail)
@@ -577,12 +701,108 @@ export class FxviewTabRow extends MozLitElement {
}
}
- muteOrUnmuteTab() {
+ tertiaryActionHandler(event) {
+ if (
+ (event.type == "click" && event.detail && !event.altKey) ||
+ // detail=0 is from keyboard
+ (event.type == "click" && !event.detail)
+ ) {
+ event.preventDefault();
+ this.dispatchEvent(
+ new CustomEvent("fxview-tab-list-tertiary-action", {
+ bubbles: true,
+ composed: true,
+ detail: { originalEvent: event, item: this },
+ })
+ );
+ }
+ }
+
+ muteOrUnmuteTab(e) {
+ e?.preventDefault();
+ // If the tab has no sound playing, the mute/unmute button will be removed when toggled.
+ // We should move the focus to the right in that case. This does not apply to pinned tabs
+ // on the Open Tabs page.
+ let shouldMoveFocus =
+ (!this.pinnedTabsGridView ||
+ (!this.indicators.includes("pinned") && this.pinnedTabsGridView)) &&
+ this.mediaButtonEl &&
+ !this.indicators.includes("soundplaying") &&
+ this.currentActiveElementId === "fxview-tab-row-media-button";
+
+ // detail=0 is from keyboard
+ if (e?.type == "click" && !e?.detail && shouldMoveFocus) {
+ let tabList = this.getRootNode().host;
+ if (document.dir == "rtl") {
+ tabList.moveFocusLeft(this);
+ } else {
+ tabList.moveFocusRight(this);
+ }
+ }
this.tabElement.toggleMuteAudio();
- this.muted = !this.muted;
}
- render() {
+ #faviconTemplate() {
+ return html`<span
+ class="${classMap({
+ "fxview-tab-row-favicon-wrapper": true,
+ pinned: this.indicators?.includes("pinned"),
+ pinnedOnNewTab: this.indicators?.includes("pinnedOnNewTab"),
+ attention: this.indicators?.includes("attention"),
+ bookmark: this.indicators?.includes("bookmark"),
+ })}"
+ >
+ <span
+ class="fxview-tab-row-favicon icon"
+ id="fxview-tab-row-favicon"
+ style=${styleMap({
+ backgroundImage: `url(${this.getImageUrl(this.favicon, this.url)})`,
+ })}
+ ></span>
+ ${when(
+ this.pinnedTabsGridView &&
+ this.indicators?.includes("pinned") &&
+ (this.indicators?.includes("muted") ||
+ this.indicators?.includes("soundplaying")),
+ () => html`
+ <button
+ class="fxview-tab-row-pinned-media-button ghost-button icon-button"
+ id="fxview-tab-row-media-button"
+ tabindex="-1"
+ data-l10n-id=${this.indicators?.includes("muted")
+ ? "fxviewtabrow-unmute-tab-button-no-context"
+ : "fxviewtabrow-mute-tab-button-no-context"}
+ muted=${this.indicators?.includes("muted")}
+ soundplaying=${this.indicators?.includes("soundplaying") &&
+ !this.indicators?.includes("muted")}
+ @click=${this.muteOrUnmuteTab}
+ ></button>
+ `
+ )}
+ </span>`;
+ }
+
+ #pinnedTabItemTemplate() {
+ return html` <button
+ class="fxview-tab-row-main ghost-button semi-transparent"
+ id="fxview-tab-row-main"
+ aria-haspopup=${ifDefined(this.hasPopup)}
+ data-l10n-id=${ifDefined(this.primaryL10nId)}
+ data-l10n-args=${ifDefined(this.primaryL10nArgs)}
+ tabindex=${this.active &&
+ this.currentActiveElementId === "fxview-tab-row-main"
+ ? "0"
+ : "-1"}
+ role="tab"
+ @click=${this.primaryActionHandler}
+ @keydown=${this.primaryActionHandler}
+ @contextmenu=${this.secondaryActionHandler}
+ >
+ ${this.#faviconTemplate()}
+ </button>`;
+ }
+
+ #unpinnedTabItemTemplate() {
const title = this.title;
const relativeString = this.relativeTime(
this.time,
@@ -598,25 +818,8 @@ export class FxviewTabRow extends MozLitElement {
const timeString = this.timeFluentId(this.dateTimeFormat);
const time = this.time;
const timeArgs = JSON.stringify({ time });
- return html`
- ${when(
- this.containerObj,
- () => html`
- <link
- rel="stylesheet"
- href="chrome://browser/content/usercontext/usercontext.css"
- />
- `
- )}
- <link
- rel="stylesheet"
- href="chrome://global/skin/in-content/common.css"
- />
- <link
- rel="stylesheet"
- href="chrome://browser/content/firefoxview/fxview-tab-row.css"
- />
- <a
+
+ return html`<a
href=${ifDefined(this.url)}
class="fxview-tab-row-main"
id="fxview-tab-row-main"
@@ -628,29 +831,9 @@ export class FxviewTabRow extends MozLitElement {
data-l10n-args=${ifDefined(this.primaryL10nArgs)}
@click=${this.primaryActionHandler}
@keydown=${this.primaryActionHandler}
+ title=${!this.primaryL10nId ? this.url : null}
>
- <span
- class="${classMap({
- "fxview-tab-row-favicon-wrapper": true,
- bookmark: this.isBookmark && !this.attention,
- notification: this.pinned
- ? this.attention || this.titleChanged
- : this.attention,
- soundplaying: this.soundPlaying && !this.muted && this.pinned,
- muted: this.muted && this.pinned,
- })}"
- >
- <span
- class="fxview-tab-row-favicon icon"
- id="fxview-tab-row-favicon"
- style=${styleMap({
- backgroundImage: `url(${this.getImageUrl(
- this.favicon,
- this.url
- )})`,
- })}
- ></span>
- </span>
+ ${this.#faviconTemplate()}
<span
class="fxview-tab-row-title text-truncated-ellipsis"
id="fxview-tab-row-title"
@@ -701,34 +884,42 @@ export class FxviewTabRow extends MozLitElement {
</span>
</a>
${when(
- (this.soundPlaying || this.muted) && !this.pinned,
+ this.indicators?.includes("soundplaying") ||
+ this.indicators?.includes("muted"),
() => html`<button
- class=fxview-tab-row-button ghost-button icon-button semi-transparent"
- id="fxview-tab-row-media-button"
- data-l10n-id=${
- this.muted
- ? "fxviewtabrow-unmute-tab-button"
- : "fxviewtabrow-mute-tab-button"
- }
- data-l10n-args=${JSON.stringify({ tabTitle: title })}
- muted=${ifDefined(this.muted)}
- soundplaying=${this.soundPlaying && !this.muted}
- @click=${this.muteOrUnmuteTab}
- tabindex="${
- this.active &&
- this.currentActiveElementId === "fxview-tab-row-media-button"
- ? "0"
- : "-1"
- }"
- ></button>`,
+ class=fxview-tab-row-button ghost-button icon-button semi-transparent"
+ id="fxview-tab-row-media-button"
+ data-l10n-id=${
+ this.indicators?.includes("muted")
+ ? "fxviewtabrow-unmute-tab-button-no-context"
+ : "fxviewtabrow-mute-tab-button-no-context"
+ }
+ muted=${this.indicators?.includes("muted")}
+ soundplaying=${
+ this.indicators?.includes("soundplaying") &&
+ !this.indicators?.includes("muted")
+ }
+ @click=${this.muteOrUnmuteTab}
+ tabindex="${
+ this.active &&
+ this.currentActiveElementId === "fxview-tab-row-media-button"
+ ? "0"
+ : "-1"
+ }"
+ ></button>`,
() => html`<span></span>`
)}
${when(
this.secondaryL10nId && this.secondaryActionHandler,
() => html`<button
- class="fxview-tab-row-button ghost-button icon-button semi-transparent"
+ class=${classMap({
+ "fxview-tab-row-button": true,
+ "ghost-button": true,
+ "icon-button": true,
+ "semi-transparent": true,
+ [this.secondaryActionClass]: this.secondaryActionClass,
+ })}
id="fxview-tab-row-secondary-button"
- part="secondary-button"
data-l10n-id=${this.secondaryL10nId}
data-l10n-args=${ifDefined(this.secondaryL10nArgs)}
aria-haspopup=${ifDefined(this.hasPopup)}
@@ -739,6 +930,53 @@ export class FxviewTabRow extends MozLitElement {
: "-1"}"
></button>`
)}
+ ${when(
+ this.tertiaryL10nId && this.tertiaryActionHandler,
+ () => html`<button
+ class=${classMap({
+ "fxview-tab-row-button": true,
+ "ghost-button": true,
+ "icon-button": true,
+ "semi-transparent": true,
+ [this.tertiaryActionClass]: this.tertiaryActionClass,
+ })}
+ id="fxview-tab-row-tertiary-button"
+ data-l10n-id=${this.tertiaryL10nId}
+ data-l10n-args=${ifDefined(this.tertiaryL10nArgs)}
+ aria-haspopup=${ifDefined(this.hasPopup)}
+ @click=${this.tertiaryActionHandler}
+ tabindex="${this.active &&
+ this.currentActiveElementId === "fxview-tab-row-tertiary-button"
+ ? "0"
+ : "-1"}"
+ ></button>`
+ )}`;
+ }
+
+ render() {
+ return html`
+ ${when(
+ this.containerObj,
+ () => html`
+ <link
+ rel="stylesheet"
+ href="chrome://browser/content/usercontext/usercontext.css"
+ />
+ `
+ )}
+ <link
+ rel="stylesheet"
+ href="chrome://global/skin/in-content/common.css"
+ />
+ <link
+ rel="stylesheet"
+ href="chrome://browser/content/firefoxview/fxview-tab-row.css"
+ />
+ ${when(
+ this.pinnedTabsGridView && this.indicators?.includes("pinned"),
+ this.#pinnedTabItemTemplate.bind(this),
+ this.#unpinnedTabItemTemplate.bind(this)
+ )}
`;
}
@@ -780,6 +1018,7 @@ export class VirtualList extends MozLitElement {
isAlwaysVisible: { type: Boolean },
isVisible: { type: Boolean, state: true },
isSubList: { type: Boolean },
+ pinnedTabsIndexOffset: { type: Number },
};
createRenderRoot() {
@@ -790,6 +1029,7 @@ export class VirtualList extends MozLitElement {
super();
this.activeIndex = 0;
this.itemOffset = 0;
+ this.pinnedTabsIndexOffset = 0;
this.items = [];
this.subListItems = [];
this.itemHeightEstimate = FXVIEW_ROW_HEIGHT_PX;
@@ -893,14 +1133,16 @@ export class VirtualList extends MozLitElement {
.template=${this.template}
.items=${data}
.itemHeightEstimate=${this.itemHeightEstimate}
- .itemOffset=${i * this.maxRenderCountEstimate}
+ .itemOffset=${i * this.maxRenderCountEstimate +
+ this.pinnedTabsIndexOffset}
.isAlwaysVisible=${i ==
parseInt(this.activeIndex / this.maxRenderCountEstimate, 10)}
isSubList
></virtual-list>`;
};
- itemTemplate = (data, i) => this.template(data, this.itemOffset + i);
+ itemTemplate = (data, i) =>
+ this.template(data, this.itemOffset + i + this.pinnedTabsIndexOffset);
render() {
if (this.isAlwaysVisible || this.isVisible) {
diff --git a/browser/components/firefoxview/fxview-tab-row.css b/browser/components/firefoxview/fxview-tab-row.css
index ceb059a33b..219d7e8aa2 100644
--- a/browser/components/firefoxview/fxview-tab-row.css
+++ b/browser/components/firefoxview/fxview-tab-row.css
@@ -31,6 +31,12 @@
user-select: none;
cursor: pointer;
text-decoration: none;
+
+ :host(.pinned) & {
+ padding: var(--space-small);
+ min-width: unset;
+ margin: 0;
+ }
}
.fxview-tab-row-main,
@@ -44,6 +50,10 @@
.fxview-tab-row-button.ghost-button.icon-button:enabled:hover {
background-color: var(--fxviewtabrow-element-background-hover);
color: var(--fxviewtabrow-text-color-hover);
+
+ & .fxview-tab-row-favicon-wrapper .fxview-tab-row-favicon::after {
+ stroke: var(--fxview-indicator-stroke-color-hover);
+ }
}
.fxview-tab-row-main:hover:active,
@@ -52,15 +62,16 @@
}
@media (prefers-contrast) {
- .fxview-tab-row-main,
- .fxview-tab-row-main:hover,
- .fxview-tab-row-main:active {
+ a.fxview-tab-row-main,
+ a.fxview-tab-row-main:hover,
+ a.fxview-tab-row-main:active {
background-color: transparent;
border: 1px solid LinkText;
color: LinkText;
}
- .fxview-tab-row-main:visited .fxview-tab-row-main:visited:hover {
+ a.fxview-tab-row-main:visited,
+ a.fxview-tab-row-main:visited:hover {
border: 1px solid VisitedText;
color: VisitedText;
}
@@ -68,14 +79,17 @@
.fxview-tab-row-favicon-wrapper {
height: 16px;
+ position: relative;
- .fxview-tab-row-favicon::after {
+ .fxview-tab-row-favicon::after,
+ .fxview-tab-row-button::after,
+ &.pinned .fxview-tab-row-pinned-media-button {
display: block;
content: "";
background-size: 12px;
background-position: center;
background-repeat: no-repeat;
- position: absolute;
+ position: relative;
height: 12px;
width: 12px;
-moz-context-properties: fill, stroke;
@@ -83,36 +97,47 @@
stroke: var(--fxview-background-color-secondary);
}
- &.bookmark .fxview-tab-row-favicon::after {
- background-image: url("chrome://browser/skin/bookmark-12.svg");
+ &:is(.pinnedOnNewTab, .bookmark):not(.attention) .fxview-tab-row-favicon::after {
inset-block-start: 9px;
inset-inline-end: -6px;
+ }
+
+ &.pinnedOnNewTab .fxview-tab-row-favicon::after,
+ &.pinnedOnNewTab .fxview-tab-row-button::after {
+ background-image: url("chrome://browser/skin/pin-12.svg");
+ }
+
+ &.bookmark .fxview-tab-row-favicon::after,
+ &.bookmark .fxview-tab-row-button::after {
+ background-image: url("chrome://browser/skin/bookmark-12.svg");
fill: var(--fxview-primary-action-background);
}
- &.notification .fxview-tab-row-favicon::after {
+ &.attention .fxview-tab-row-favicon::after,
+ &.attention .fxview-tab-row-button::after {
background-image: radial-gradient(circle, light-dark(rgb(42, 195, 162), rgb(84, 255, 189)), light-dark(rgb(42, 195, 162), rgb(84, 255, 189)) 2px, transparent 2px);
height: 4px;
width: 100%;
inset-block-start: 20px;
}
- &.soundplaying .fxview-tab-row-favicon::after {
- background-image: url("chrome://global/skin/media/audio.svg");
- inset-block-start: -5px;
- inset-inline-end: -7px;
+ &.pinned .fxview-tab-row-pinned-media-button {
+ inset-block-start: -10px;
+ inset-inline-end: -10px;
border-radius: 100%;
background-color: var(--fxview-background-color-secondary);
- padding: 2px;
- }
+ padding: 6px;
+ min-width: 0;
+ min-height: 0;
+ position: absolute;
- &.muted .fxview-tab-row-favicon::after {
- background-image: url("chrome://global/skin/media/audio-muted.svg");
- inset-block-start: -5px;
- inset-inline-end: -7px;
- border-radius: 100%;
- background-color: var(--fxview-background-color-secondary);
- padding: 2px;
+ &[muted="true"] {
+ background-image: url("chrome://global/skin/media/audio-muted.svg");
+ }
+
+ &[soundplaying="true"] {
+ background-image: url("chrome://global/skin/media/audio.svg");
+ }
}
}
@@ -179,26 +204,40 @@
&[soundplaying="true"] {
background-image: url("chrome://global/skin/media/audio.svg");
}
+
+ &.dismiss-button {
+ background-image: url("chrome://global/skin/icons/close.svg");
+ }
+
+ &.options-button {
+ background-image: url("chrome://global/skin/icons/more.svg");
+ }
}
@media (prefers-contrast) {
- .fxview-tab-row-button {
+ .fxview-tab-row-button,
+ button.fxview-tab-row-main {
border: 1px solid ButtonText;
color: ButtonText;
}
- .fxview-tab-row-button.ghost-button.icon-button:enabled:hover {
+ .fxview-tab-row-button.ghost-button.icon-button:enabled:hover,
+ button.fxview-tab-row-main:enabled:hover {
border: 1px solid SelectedItem;
color: SelectedItem;
}
- .fxview-tab-row-button.ghost-button.icon-button:enabled:active {
+ .fxview-tab-row-button.ghost-button.icon-button:enabled:active,
+ button.fxview-tab-row-main:enabled:active {
color: SelectedItem;
}
.fxview-tab-row-button.ghost-button.icon-button:enabled,
.fxview-tab-row-button.ghost-button.icon-button:enabled:hover,
- .fxview-tab-row-button.ghost-button.icon-button:enabled:active {
+ .fxview-tab-row-button.ghost-button.icon-button:enabled:active
+ button.fxview-tab-row-main:enabled,
+ button.fxview-tab-row-main:enabled:hover,
+ button.fxview-tab-row-main:enabled:active {
background-color: ButtonFace;
}
}
diff --git a/browser/components/firefoxview/history.mjs b/browser/components/firefoxview/history.mjs
index 935cc037e9..1fe028449b 100644
--- a/browser/components/firefoxview/history.mjs
+++ b/browser/components/firefoxview/history.mjs
@@ -418,7 +418,7 @@ class HistoryInView extends ViewPage {
></h3>
<fxview-tab-list
slot="main"
- class="with-context-menu"
+ secondaryActionClass="options-button"
dateTimeFormat=${historyItem.l10nId.includes("prev-month")
? "dateTime"
: "time"}
@@ -442,7 +442,7 @@ class HistoryInView extends ViewPage {
</h3>
<fxview-tab-list
slot="main"
- class="with-context-menu"
+ secondaryActionClass="options-button"
dateTimeFormat="dateTime"
hasPopup="menu"
maxTabsLength=${this.maxTabsLength}
@@ -520,7 +520,7 @@ class HistoryInView extends ViewPage {
)}
<fxview-tab-list
slot="main"
- class="with-context-menu"
+ secondaryActionClass="options-button"
dateTimeFormat="dateTime"
hasPopup="menu"
maxTabsLength="-1"
diff --git a/browser/components/firefoxview/jar.mn b/browser/components/firefoxview/jar.mn
index 27eeaaef80..1e5cc3e690 100644
--- a/browser/components/firefoxview/jar.mn
+++ b/browser/components/firefoxview/jar.mn
@@ -15,9 +15,6 @@ browser.jar:
content/browser/firefoxview/view-syncedtabs.css
content/browser/firefoxview/recentbrowsing.mjs
content/browser/firefoxview/firefoxview.css
- content/browser/firefoxview/fxview-category-button.css
- content/browser/firefoxview/fxview-category-navigation.css
- content/browser/firefoxview/fxview-category-navigation.mjs
content/browser/firefoxview/fxview-empty-state.css
content/browser/firefoxview/fxview-empty-state.mjs
content/browser/firefoxview/helpers.mjs
@@ -29,12 +26,12 @@ browser.jar:
content/browser/firefoxview/recentlyclosed.mjs
content/browser/firefoxview/viewpage.mjs
content/browser/firefoxview/history-empty.svg (content/history-empty.svg)
- content/browser/firefoxview/category-history.svg (content/category-history.svg)
- content/browser/firefoxview/category-opentabs.svg (content/category-opentabs.svg)
- content/browser/firefoxview/category-recentbrowsing.svg (content/category-recentbrowsing.svg)
- content/browser/firefoxview/category-recentlyclosed.svg (content/category-recentlyclosed.svg)
- content/browser/firefoxview/category-syncedtabs.svg (content/category-syncedtabs.svg)
content/browser/firefoxview/recentlyclosed-empty.svg (content/recentlyclosed-empty.svg)
content/browser/firefoxview/synced-tabs-error.svg (content/synced-tabs-error.svg)
content/browser/callout-tab-pickup.svg (content/callout-tab-pickup.svg)
content/browser/callout-tab-pickup-dark.svg (content/callout-tab-pickup-dark.svg)
+ content/browser/firefoxview/view-history.svg (content/view-history.svg)
+ content/browser/firefoxview/view-opentabs.svg (content/view-opentabs.svg)
+ content/browser/firefoxview/view-recentbrowsing.svg (content/view-recentbrowsing.svg)
+ content/browser/firefoxview/view-recentlyclosed.svg (content/view-recentlyclosed.svg)
+ content/browser/firefoxview/view-syncedtabs.svg (content/view-syncedtabs.svg)
diff --git a/browser/components/firefoxview/opentabs.mjs b/browser/components/firefoxview/opentabs.mjs
index 6ac63a4b3f..8d7723e931 100644
--- a/browser/components/firefoxview/opentabs.mjs
+++ b/browser/components/firefoxview/opentabs.mjs
@@ -21,8 +21,10 @@ import { ViewPage, ViewPageContent } from "./viewpage.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
+ BookmarkList: "resource://gre/modules/BookmarkList.sys.mjs",
ContextualIdentityService:
"resource://gre/modules/ContextualIdentityService.sys.mjs",
+ NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs",
NonPrivateTabs: "resource:///modules/OpenTabs.sys.mjs",
getTabsTargetForWindow: "resource:///modules/OpenTabs.sys.mjs",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
@@ -106,6 +108,10 @@ class OpenTabsInView extends ViewPage {
this
);
}
+
+ this.bookmarkList = new lazy.BookmarkList(this.#getAllTabUrls(), () =>
+ this.viewCards.forEach(card => card.requestUpdate())
+ );
}
shouldUpdate(changedProperties) {
@@ -141,6 +147,8 @@ class OpenTabsInView extends ViewPage {
this
);
}
+
+ this.bookmarkList.removeListeners();
}
viewVisibleCallback() {
@@ -161,6 +169,13 @@ class OpenTabsInView extends ViewPage {
}
}
+ #getAllTabUrls() {
+ return this.openTabsTarget
+ .getAllTabs()
+ .map(({ linkedBrowser }) => linkedBrowser?.currentURI?.spec)
+ .filter(Boolean);
+ }
+
render() {
if (this.recentBrowsing) {
return this.getRecentBrowsingTemplate();
@@ -268,6 +283,7 @@ class OpenTabsInView extends ViewPage {
winID: currentWindowIndex,
})}"
.searchQuery=${this.searchQuery}
+ .bookmarkList=${this.bookmarkList}
></view-opentabs-card>
`
)}
@@ -282,6 +298,7 @@ class OpenTabsInView extends ViewPage {
data-l10n-id="firefoxview-opentabs-window-header"
data-l10n-args="${JSON.stringify({ winID })}"
.searchQuery=${this.searchQuery}
+ .bookmarkList=${this.bookmarkList}
></view-opentabs-card>
`
)}
@@ -318,6 +335,7 @@ class OpenTabsInView extends ViewPage {
.recentBrowsing=${true}
.paused=${this.paused}
.searchQuery=${this.searchQuery}
+ .bookmarkList=${this.bookmarkList}
></view-opentabs-card>`;
}
@@ -330,13 +348,9 @@ class OpenTabsInView extends ViewPage {
switch (type) {
case "TabRecencyChange":
case "TabChange":
- // if we're switching away from our tab, we can halt any updates immediately
- if (!this.isSelectedBrowserTab) {
- this.stop();
- return;
- }
windowIds = detail.windowIds;
this._updateWindowList();
+ this.bookmarkList.setTrackedUrls(this.#getAllTabUrls());
break;
}
if (this.recentBrowsing) {
@@ -390,6 +404,7 @@ class OpenTabsInViewCard extends ViewPageContent {
searchResults: { type: Array },
showAll: { type: Boolean },
cumulativeSearches: { type: Number },
+ bookmarkList: { type: Object },
};
static MAX_TABS_FOR_COMPACT_HEIGHT = 7;
@@ -470,6 +485,14 @@ class OpenTabsInViewCard extends ViewPageContent {
}
onTabListRowClick(event) {
+ // Don't open pinned tab if mute/unmute indicator button selected
+ if (
+ Array.from(event.explicitOriginalTarget.classList).includes(
+ "fxview-tab-row-pinned-media-button"
+ )
+ ) {
+ return;
+ }
const tab = event.originalTarget.tabElement;
const browserWindow = tab.ownerGlobal;
browserWindow.focus();
@@ -497,6 +520,18 @@ class OpenTabsInViewCard extends ViewPageContent {
}
}
+ closeTab(event) {
+ const tab = event.originalTarget.tabElement;
+ tab?.ownerGlobal.gBrowser.removeTab(tab);
+
+ Services.telemetry.recordEvent(
+ "firefoxview_next",
+ "close_open_tab",
+ "tabs",
+ null
+ );
+ }
+
viewVisibleCallback() {
this.getRootNode().host.toggleVisibilityInCardContainer(true);
}
@@ -531,15 +566,18 @@ class OpenTabsInViewCard extends ViewPageContent {
)}
<div class="fxview-tab-list-container" slot="main">
<fxview-tab-list
- class="with-context-menu"
.hasPopup=${"menu"}
?compactRows=${this.classList.contains("width-limited")}
@fxview-tab-list-primary-action=${this.onTabListRowClick}
@fxview-tab-list-secondary-action=${this.openContextMenu}
+ @fxview-tab-list-tertiary-action=${this.closeTab}
+ secondaryActionClass="options-button"
+ tertiaryActionClass="dismiss-button"
.maxTabsLength=${this.getMaxTabsLength()}
- .tabItems=${this.searchResults || getTabListItems(this.tabs)}
+ .tabItems=${this.searchResults ||
+ getTabListItems(this.tabs, this.recentBrowsing)}
.searchQuery=${this.searchQuery}
- .showTabIndicators=${true}
+ .pinnedTabsGridView=${!this.recentBrowsing}
><view-opentabs-contextmenu slot="menu"></view-opentabs-contextmenu>
</fxview-tab-list>
</div>
@@ -590,6 +628,28 @@ class OpenTabsInViewCard extends ViewPageContent {
? searchTabList(this.searchQuery, getTabListItems(this.tabs))
: null;
}
+
+ updated() {
+ this.updateBookmarkStars();
+ }
+
+ async updateBookmarkStars() {
+ const tabItems = [...this.tabList.tabItems];
+ for (const row of tabItems) {
+ const isBookmark = await this.bookmarkList.isBookmark(row.url);
+ if (isBookmark && !row.indicators.includes("bookmark")) {
+ row.indicators.push("bookmark");
+ }
+ if (!isBookmark && row.indicators.includes("bookmark")) {
+ row.indicators = row.indicators.filter(i => i !== "bookmark");
+ }
+ row.primaryL10nId = getPrimaryL10nId(
+ this.isRecentBrowsing,
+ row.indicators
+ );
+ }
+ this.tabList.tabItems = tabItems;
+ }
}
customElements.define("view-opentabs-card", OpenTabsInViewCard);
@@ -655,6 +715,29 @@ class OpenTabsContextMenu extends MozLitElement {
this.ownerViewPage.recordContextMenuTelemetry("close-tab", e);
}
+ pinTab(e) {
+ const tab = this.triggerNode.tabElement;
+ tab?.ownerGlobal.gBrowser.pinTab(tab);
+ this.ownerViewPage.recordContextMenuTelemetry("pin-tab", e);
+ }
+
+ unpinTab(e) {
+ const tab = this.triggerNode.tabElement;
+ tab?.ownerGlobal.gBrowser.unpinTab(tab);
+ this.ownerViewPage.recordContextMenuTelemetry("unpin-tab", e);
+ }
+
+ toggleAudio(e) {
+ const tab = this.triggerNode.tabElement;
+ tab.toggleMuteAudio();
+ this.ownerViewPage.recordContextMenuTelemetry(
+ `${
+ this.triggerNode.indicators.includes("muted") ? "unmute" : "mute"
+ }-tab`,
+ e
+ );
+ }
+
moveTabsToStart(e) {
const tab = this.triggerNode.tabElement;
tab?.ownerGlobal.gBrowser.moveTabsToStart(tab);
@@ -749,16 +832,25 @@ class OpenTabsContextMenu extends MozLitElement {
/>
<panel-list data-tab-type="opentabs">
<panel-item
- data-l10n-id="fxviewtabrow-close-tab"
- data-l10n-attrs="accesskey"
- @click=${this.closeTab}
- ></panel-item>
- <panel-item
data-l10n-id="fxviewtabrow-move-tab"
data-l10n-attrs="accesskey"
submenu="move-tab-menu"
>${this.moveMenuTemplate()}</panel-item
>
+ <panel-item
+ data-l10n-id=${tab.pinned
+ ? "fxviewtabrow-unpin-tab"
+ : "fxviewtabrow-pin-tab"}
+ data-l10n-attrs="accesskey"
+ @click=${tab.pinned ? this.unpinTab : this.pinTab}
+ ></panel-item>
+ <panel-item
+ data-l10n-id=${tab.hasAttribute("muted")
+ ? "fxviewtabrow-unmute-tab"
+ : "fxviewtabrow-mute-tab"}
+ data-l10n-attrs="accesskey"
+ @click=${this.toggleAudio}
+ ></panel-item>
<hr />
<panel-item
data-l10n-id="fxviewtabrow-copy-link"
@@ -798,36 +890,140 @@ function getContainerObj(tab) {
}
/**
+ * Gets an array of tab indicators (if any) when normalizing for fxview-tab-list
+ *
+ * @param {MozTabbrowserTab[]} tab
+ * Tab to fetch container info on.
+ * @returns {Array[]}
+ * Array of named tab indicators
+ */
+function getIndicatorsForTab(tab) {
+ const url = tab.linkedBrowser?.currentURI?.spec || "";
+ let tabIndicators = [];
+ let hasAttention =
+ (tab.pinned &&
+ (tab.hasAttribute("attention") || tab.hasAttribute("titlechanged"))) ||
+ (!tab.pinned && tab.hasAttribute("attention"));
+
+ if (tab.pinned) {
+ tabIndicators.push("pinned");
+ }
+ if (getContainerObj(tab)) {
+ tabIndicators.push("container");
+ }
+ if (hasAttention) {
+ tabIndicators.push("attention");
+ }
+ if (tab.hasAttribute("soundplaying") && !tab.hasAttribute("muted")) {
+ tabIndicators.push("soundplaying");
+ }
+ if (tab.hasAttribute("muted")) {
+ tabIndicators.push("muted");
+ }
+ if (checkIfPinnedNewTab(url)) {
+ tabIndicators.push("pinnedOnNewTab");
+ }
+
+ return tabIndicators;
+}
+/**
+ * Gets the primary l10n id for a tab when normalizing for fxview-tab-list
+ *
+ * @param {boolean} isRecentBrowsing
+ * Whether the tabs are going to be displayed on the Recent Browsing page or not
+ * @param {Array[]} tabIndicators
+ * Array of tab indicators for the given tab
+ * @returns {string}
+ * L10n ID string
+ */
+function getPrimaryL10nId(isRecentBrowsing, tabIndicators) {
+ let indicatorL10nId = null;
+ if (!isRecentBrowsing) {
+ if (
+ tabIndicators?.includes("pinned") &&
+ tabIndicators?.includes("bookmark")
+ ) {
+ indicatorL10nId = "firefoxview-opentabs-bookmarked-pinned-tab";
+ } else if (tabIndicators?.includes("pinned")) {
+ indicatorL10nId = "firefoxview-opentabs-pinned-tab";
+ } else if (tabIndicators?.includes("bookmark")) {
+ indicatorL10nId = "firefoxview-opentabs-bookmarked-tab";
+ }
+ }
+ return indicatorL10nId;
+}
+
+/**
+ * Gets the primary l10n args for a tab when normalizing for fxview-tab-list
+ *
+ * @param {MozTabbrowserTab[]} tab
+ * Tab to fetch container info on.
+ * @param {boolean} isRecentBrowsing
+ * Whether the tabs are going to be displayed on the Recent Browsing page or not
+ * @param {string} url
+ * URL for the given tab
+ * @returns {string}
+ * L10n ID args
+ */
+function getPrimaryL10nArgs(tab, isRecentBrowsing, url) {
+ return JSON.stringify({ tabTitle: tab.label, url });
+}
+
+/**
+ * Check if a given url is pinned on the new tab page
+ *
+ * @param {string} url
+ * url to check
+ * @returns {boolean}
+ * is tabbed pinned on new tab page
+ */
+function checkIfPinnedNewTab(url) {
+ return url && lazy.NewTabUtils.pinnedLinks.isPinned({ url });
+}
+
+/**
* Convert a list of tabs into the format expected by the fxview-tab-list
* component.
*
* @param {MozTabbrowserTab[]} tabs
* Tabs to format.
+ * @param {boolean} isRecentBrowsing
+ * Whether the tabs are going to be displayed on the Recent Browsing page or not
* @returns {object[]}
* Formatted objects.
*/
-function getTabListItems(tabs) {
- let filtered = tabs?.filter(
- tab => !tab.closing && !tab.hidden && !tab.pinned
- );
+function getTabListItems(tabs, isRecentBrowsing) {
+ let filtered = tabs?.filter(tab => !tab.closing && !tab.hidden);
return filtered.map(tab => {
- const url = tab.linkedBrowser?.currentURI?.spec || "";
+ let tabIndicators = getIndicatorsForTab(tab);
+ let containerObj = getContainerObj(tab);
+ const url = tab?.linkedBrowser?.currentURI?.spec || "";
return {
- attention: tab.hasAttribute("attention"),
- containerObj: getContainerObj(tab),
+ containerObj,
+ indicators: tabIndicators,
icon: tab.getAttribute("image"),
- muted: tab.hasAttribute("muted"),
- pinned: tab.pinned,
- primaryL10nId: "firefoxview-opentabs-tab-row",
- primaryL10nArgs: JSON.stringify({ url }),
- secondaryL10nId: "fxviewtabrow-options-menu-button",
- secondaryL10nArgs: JSON.stringify({ tabTitle: tab.label }),
- soundPlaying: tab.hasAttribute("soundplaying"),
+ primaryL10nId: getPrimaryL10nId(isRecentBrowsing, tabIndicators),
+ primaryL10nArgs: getPrimaryL10nArgs(tab, isRecentBrowsing, url),
+ secondaryL10nId:
+ isRecentBrowsing || (!isRecentBrowsing && !tab.pinned)
+ ? "fxviewtabrow-options-menu-button"
+ : null,
+ secondaryL10nArgs:
+ isRecentBrowsing || (!isRecentBrowsing && !tab.pinned)
+ ? JSON.stringify({ tabTitle: tab.label })
+ : null,
+ tertiaryL10nId:
+ isRecentBrowsing || (!isRecentBrowsing && !tab.pinned)
+ ? "fxviewtabrow-close-tab-button"
+ : null,
+ tertiaryL10nArgs:
+ isRecentBrowsing || (!isRecentBrowsing && !tab.pinned)
+ ? JSON.stringify({ tabTitle: tab.label })
+ : null,
tabElement: tab,
time: tab.lastAccessed,
title: tab.label,
- titleChanged: tab.hasAttribute("titlechanged"),
url,
};
});
diff --git a/browser/components/firefoxview/recentlyclosed.mjs b/browser/components/firefoxview/recentlyclosed.mjs
index 6e7e06c1f4..83c323256c 100644
--- a/browser/components/firefoxview/recentlyclosed.mjs
+++ b/browser/components/firefoxview/recentlyclosed.mjs
@@ -398,6 +398,7 @@ class RecentlyClosedTabsInView extends ViewPage {
.tabItems=${this.searchResults || this.recentlyClosedTabs}
@fxview-tab-list-secondary-action=${this.onDismissTab}
@fxview-tab-list-primary-action=${this.onReopenTab}
+ secondaryActionClass="dismiss-button"
></fxview-tab-list>
`
)}
diff --git a/browser/components/firefoxview/syncedtabs.mjs b/browser/components/firefoxview/syncedtabs.mjs
index 5320f8cb41..d64da45a30 100644
--- a/browser/components/firefoxview/syncedtabs.mjs
+++ b/browser/components/firefoxview/syncedtabs.mjs
@@ -419,13 +419,14 @@ class SyncedTabsInView extends ViewPage {
</h3>
<fxview-tab-list
slot="main"
- class="with-context-menu"
+ secondaryActionClass="options-button"
hasPopup="menu"
.tabItems=${ifDefined(tabItems)}
.searchQuery=${this.searchQuery}
maxTabsLength=${this.showAll ? -1 : this.maxTabsLength}
@fxview-tab-list-primary-action=${this.onOpenLink}
@fxview-tab-list-secondary-action=${this.onContextMenu}
+ secondaryActionClass="options-button"
>
${this.panelListTemplate()}
</fxview-tab-list>`;
diff --git a/browser/components/firefoxview/tests/browser/FirefoxViewTestUtils.sys.mjs b/browser/components/firefoxview/tests/browser/FirefoxViewTestUtils.sys.mjs
index 3fd2bf95e3..e1285c0396 100644
--- a/browser/components/firefoxview/tests/browser/FirefoxViewTestUtils.sys.mjs
+++ b/browser/components/firefoxview/tests/browser/FirefoxViewTestUtils.sys.mjs
@@ -21,6 +21,20 @@ function getFirefoxViewURL() {
return "about:firefoxview";
}
+/**
+ * Make the given window focused and active
+ */
+async function switchToWindow(win) {
+ await testScope.SimpleTest.promiseFocus(win);
+ if (Services.focus.activeWindow !== win) {
+ testScope.info("switchToWindow, waiting for activate event on the window");
+ await BrowserTestUtils.waitForEvent(win, "activate");
+ } else {
+ testScope.info("switchToWindow, win is already the activeWindow");
+ }
+ testScope.info("switchToWindow, done");
+}
+
function assertFirefoxViewTab(win) {
Assert.ok(win.FirefoxViewHandler.tab, "Firefox View tab exists");
Assert.ok(win.FirefoxViewHandler.tab?.hidden, "Firefox View tab is hidden");
@@ -48,7 +62,7 @@ async function openFirefoxViewTab(win) {
"Must initialize FirefoxViewTestUtils with a test scope which has a SimpleTest property"
);
}
- await testScope.SimpleTest.promiseFocus(win);
+ await switchToWindow(win);
let fxviewTab = win.FirefoxViewHandler.tab;
let alreadyLoaded =
fxviewTab?.linkedBrowser.currentURI.spec.includes(getFirefoxViewURL()) &&
@@ -167,6 +181,7 @@ function isFirefoxViewTabSelectedInWindow(win) {
export {
init,
+ switchToWindow,
withFirefoxView,
assertFirefoxViewTab,
assertFirefoxViewTabSelected,
diff --git a/browser/components/firefoxview/tests/browser/browser.toml b/browser/components/firefoxview/tests/browser/browser.toml
index 8e2005760b..9f9c1c0176 100644
--- a/browser/components/firefoxview/tests/browser/browser.toml
+++ b/browser/components/firefoxview/tests/browser/browser.toml
@@ -27,48 +27,57 @@ skip-if = ["true"] # Bug 1869605 and # Bug 1870296
["browser_firefoxview.js"]
-["browser_firefoxview_tab.js"]
-
-["browser_notification_dot.js"]
-skip-if = ["true"] # Bug 1851453
-
-["browser_opentabs_changes.js"]
-
-["browser_reload_firefoxview.js"]
-
-["browser_tab_close_last_tab.js"]
-
-["browser_tab_on_close_warning.js"]
-
-["browser_firefoxview_paused.js"]
-
["browser_firefoxview_general_telemetry.js"]
["browser_firefoxview_navigation.js"]
+["browser_firefoxview_paused.js"]
+
["browser_firefoxview_search_telemetry.js"]
+["browser_firefoxview_tab.js"]
+
["browser_firefoxview_virtual_list.js"]
["browser_history_firefoxview.js"]
-skip-if = ["true"] # Bug 1877594
-["browser_opentabs_firefoxview.js"]
+["browser_notification_dot.js"]
+skip-if = ["true"] # Bug 1851453
["browser_opentabs_cards.js"]
+
+["browser_opentabs_changes.js"]
+
+["browser_opentabs_firefoxview.js"]
+
+["browser_opentabs_more.js"]
fail-if = ["a11y_checks"] # Bugs 1858041, 1854625, and 1872174 clicked Show all link is not accessible because it is "hidden" when clicked
+skip-if = ["verify"] # Bug 1886017
+
+["browser_opentabs_pinned_tabs.js"]
["browser_opentabs_recency.js"]
skip-if = [
"os == 'win'",
"os == 'mac' && verify",
"os == 'linux'"
-] # macos times out, see bug 1857293, skipped for windows, see bug 1858460, skipped for linux, see bug 1875877
+] # macos times out, see bug 1857293, skipped for windows, see bug 1858460, Bug 1875877 - frequent fails on linux.
+
+["browser_opentabs_search.js"]
+fail-if = ["a11y_checks"] # Bug 1850591 clicked moz-page-nav-button button is not focusable
["browser_opentabs_tab_indicators.js"]
["browser_recentlyclosed_firefoxview.js"]
+["browser_reload_firefoxview.js"]
+
["browser_syncedtabs_errors_firefoxview.js"]
["browser_syncedtabs_firefoxview.js"]
+
+["browser_tab_close_last_tab.js"]
+
+["browser_tab_list_keyboard_navigation.js"]
+
+["browser_tab_on_close_warning.js"]
diff --git a/browser/components/firefoxview/tests/browser/browser_dragDrop_after_opening_fxViewTab.js b/browser/components/firefoxview/tests/browser/browser_dragDrop_after_opening_fxViewTab.js
index 9ce547238a..0376a3886d 100644
--- a/browser/components/firefoxview/tests/browser/browser_dragDrop_after_opening_fxViewTab.js
+++ b/browser/components/firefoxview/tests/browser/browser_dragDrop_after_opening_fxViewTab.js
@@ -15,6 +15,7 @@ add_task(async function () {
// window.RTL_UI doesn't update in existing windows when this pref is changed,
// so we need to test in a new window.
let win = await BrowserTestUtils.openNewBrowserWindow();
+ await switchToWindow(win);
const TEST_ROOT = getRootDirectory(gTestPath).replace(
"chrome://mochitests/content",
diff --git a/browser/components/firefoxview/tests/browser/browser_firefoxview.js b/browser/components/firefoxview/tests/browser/browser_firefoxview.js
index 33467941a4..1a51d61f42 100644
--- a/browser/components/firefoxview/tests/browser/browser_firefoxview.js
+++ b/browser/components/firefoxview/tests/browser/browser_firefoxview.js
@@ -6,10 +6,7 @@ add_task(async function about_firefoxview_smoke_test() {
const { document } = browser.contentWindow;
// sanity check the important regions exist on this page
- ok(
- document.querySelector("fxview-category-navigation"),
- "fxview-category-navigation element exists"
- );
+ ok(document.querySelector("moz-page-nav"), "moz-page-nav element exists");
ok(document.querySelector("named-deck"), "named-deck element exists");
});
});
diff --git a/browser/components/firefoxview/tests/browser/browser_firefoxview_general_telemetry.js b/browser/components/firefoxview/tests/browser/browser_firefoxview_general_telemetry.js
index 51d5caa032..b70e6b938e 100644
--- a/browser/components/firefoxview/tests/browser/browser_firefoxview_general_telemetry.js
+++ b/browser/components/firefoxview/tests/browser/browser_firefoxview_general_telemetry.js
@@ -39,7 +39,7 @@ add_task(async function firefox_view_entered_telemetry() {
enteredTelemetry[4] = { page: "recentlyclosed" };
enteredAndTabSelectedEvents = [tabSelectedTelemetry, enteredTelemetry];
- navigateToCategory(document, "recentlyclosed");
+ await navigateToViewAndWait(document, "recentlyclosed");
await clearAllParentTelemetryEvents();
await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots");
is(
@@ -107,9 +107,9 @@ add_task(async function test_change_page_telemetry() {
],
];
await clearAllParentTelemetryEvents();
- navigateToCategory(document, "recentlyclosed");
+ await navigateToViewAndWait(document, "recentlyclosed");
await telemetryEvent(changePageEvent);
- navigateToCategory(document, "recentbrowsing");
+ await navigateToViewAndWait(document, "recentbrowsing");
let openTabsComponent = document.querySelector(
"view-opentabs[slot=opentabs]"
@@ -189,7 +189,7 @@ add_task(async function test_context_menu_new_window_telemetry() {
);
// Test history context menu options
- await navigateToCategoryAndWait(document, "history");
+ await navigateToViewAndWait(document, "history");
let historyComponent = document.querySelector("view-history");
await TestUtils.waitForCondition(() => historyComponent.fullyUpdated);
await TestUtils.waitForCondition(
@@ -198,7 +198,11 @@ add_task(async function test_context_menu_new_window_telemetry() {
let firstTabList = historyComponent.lists[0];
let firstItem = firstTabList.rowEls[0];
let panelList = historyComponent.panelList;
- EventUtils.synthesizeMouseAtCenter(firstItem.buttonEl, {}, content);
+ EventUtils.synthesizeMouseAtCenter(
+ firstItem.secondaryButtonEl,
+ {},
+ content
+ );
await BrowserTestUtils.waitForEvent(panelList, "shown");
await clearAllParentTelemetryEvents();
let panelItems = Array.from(panelList.children).filter(
@@ -245,7 +249,7 @@ add_task(async function test_context_menu_private_window_telemetry() {
);
// Test history context menu options
- await navigateToCategoryAndWait(document, "history");
+ await navigateToViewAndWait(document, "history");
let historyComponent = document.querySelector("view-history");
await TestUtils.waitForCondition(() => historyComponent.fullyUpdated);
await TestUtils.waitForCondition(
@@ -254,14 +258,22 @@ add_task(async function test_context_menu_private_window_telemetry() {
let firstTabList = historyComponent.lists[0];
let firstItem = firstTabList.rowEls[0];
let panelList = historyComponent.panelList;
- EventUtils.synthesizeMouseAtCenter(firstItem.buttonEl, {}, content);
+ EventUtils.synthesizeMouseAtCenter(
+ firstItem.secondaryButtonEl,
+ {},
+ content
+ );
await BrowserTestUtils.waitForEvent(panelList, "shown");
await clearAllParentTelemetryEvents();
let panelItems = Array.from(panelList.children).filter(
panelItem => panelItem.nodeName === "PANEL-ITEM"
);
- EventUtils.synthesizeMouseAtCenter(firstItem.buttonEl, {}, content);
+ EventUtils.synthesizeMouseAtCenter(
+ firstItem.secondaryButtonEl,
+ {},
+ content
+ );
info("Context menu button clicked.");
await BrowserTestUtils.waitForEvent(panelList, "shown");
info("Context menu shown.");
@@ -314,7 +326,7 @@ add_task(async function test_context_menu_delete_from_history_telemetry() {
);
// Test history context menu options
- await navigateToCategoryAndWait(document, "history");
+ await navigateToViewAndWait(document, "history");
let historyComponent = document.querySelector("view-history");
await TestUtils.waitForCondition(() => historyComponent.fullyUpdated);
await TestUtils.waitForCondition(
@@ -323,14 +335,22 @@ add_task(async function test_context_menu_delete_from_history_telemetry() {
let firstTabList = historyComponent.lists[0];
let firstItem = firstTabList.rowEls[0];
let panelList = historyComponent.panelList;
- EventUtils.synthesizeMouseAtCenter(firstItem.buttonEl, {}, content);
+ EventUtils.synthesizeMouseAtCenter(
+ firstItem.secondaryButtonEl,
+ {},
+ content
+ );
await BrowserTestUtils.waitForEvent(panelList, "shown");
await clearAllParentTelemetryEvents();
let panelItems = Array.from(panelList.children).filter(
panelItem => panelItem.nodeName === "PANEL-ITEM"
);
- EventUtils.synthesizeMouseAtCenter(firstItem.buttonEl, {}, content);
+ EventUtils.synthesizeMouseAtCenter(
+ firstItem.secondaryButtonEl,
+ {},
+ content
+ );
info("Context menu button clicked.");
await BrowserTestUtils.waitForEvent(panelList, "shown");
info("Context menu shown.");
diff --git a/browser/components/firefoxview/tests/browser/browser_firefoxview_navigation.js b/browser/components/firefoxview/tests/browser/browser_firefoxview_navigation.js
index 80206dd945..281d969b39 100644
--- a/browser/components/firefoxview/tests/browser/browser_firefoxview_navigation.js
+++ b/browser/components/firefoxview/tests/browser/browser_firefoxview_navigation.js
@@ -3,15 +3,15 @@
const URL_BASE = `${getFirefoxViewURL()}#`;
-function assertCorrectPage(document, name, event) {
+function assertCorrectPage(document, view, event) {
is(
document.location.hash,
- `#${name}`,
- `Navigation button for ${name} navigates to ${URL_BASE + name} on ${event}.`
+ `#${view}`,
+ `Navigation button for ${view} navigates to ${URL_BASE + view} on ${event}.`
);
is(
document.querySelector("named-deck").selectedViewName,
- name,
+ view,
"The correct deck child is selected"
);
}
@@ -22,21 +22,21 @@ add_task(async function test_side_component_navigation_by_click() {
const { document } = browser.contentWindow;
let win = browser.ownerGlobal;
- const categoryButtons = document.querySelectorAll("fxview-category-button");
+ const pageNavButtons = document.querySelectorAll("moz-page-nav-button");
- for (let element of categoryButtons) {
- const name = element.name;
+ for (let element of pageNavButtons) {
+ const view = element.view;
let buttonClicked = BrowserTestUtils.waitForEvent(
element.buttonEl,
"click",
win
);
- info(`Clicking navigation button for ${name}`);
+ info(`Clicking navigation button for ${view}`);
EventUtils.synthesizeMouseAtCenter(element.buttonEl, {}, content);
await buttonClicked;
- assertCorrectPage(document, name, "click");
+ assertCorrectPage(document, view, "click");
}
});
});
@@ -47,49 +47,49 @@ add_task(async function test_side_component_navigation_by_keyboard() {
const { document } = browser.contentWindow;
let win = browser.ownerGlobal;
- const categoryButtons = document.querySelectorAll("fxview-category-button");
- const firstButton = categoryButtons[0];
+ const pageNavButtons = document.querySelectorAll("moz-page-nav-button");
+ const firstButton = pageNavButtons[0].buttonEl;
firstButton.focus();
is(
- document.activeElement,
+ document.activeElement.shadowRoot.activeElement,
firstButton,
- "The first category button has focus"
+ "The first page nav button has focus"
);
- for (let element of Array.from(categoryButtons).slice(1)) {
- const name = element.name;
+ for (let element of Array.from(pageNavButtons).slice(1)) {
+ const view = element.view;
let buttonFocused = BrowserTestUtils.waitForEvent(element, "focus", win);
- info(`Focus is on ${document.activeElement.name}`);
- info(`Arrow down on navigation to ${name}`);
+ info(`Focus is on ${document.activeElement.view}`);
+ info(`Arrow down on navigation to ${view}`);
EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
await buttonFocused;
- assertCorrectPage(document, name, "key press");
+ assertCorrectPage(document, view, "key press");
}
});
});
-add_task(async function test_direct_navigation_to_correct_category() {
+add_task(async function test_direct_navigation_to_correct_view() {
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- const categoryButtons = document.querySelectorAll("fxview-category-button");
+ const pageNavButtons = document.querySelectorAll("moz-page-nav-button");
const namedDeck = document.querySelector("named-deck");
- for (let element of categoryButtons) {
- const name = element.name;
+ for (let element of pageNavButtons) {
+ const view = element.view;
- info(`Navigating to ${URL_BASE + name}`);
- document.location.assign(URL_BASE + name);
+ info(`Navigating to ${URL_BASE + view}`);
+ document.location.assign(URL_BASE + view);
await BrowserTestUtils.waitForCondition(() => {
- return namedDeck.selectedViewName === name;
+ return namedDeck.selectedViewName === view;
}, "Wait for navigation to complete");
is(
namedDeck.selectedViewName,
- name,
- `The correct deck child for category ${name} is selected`
+ view,
+ `The correct deck child for view ${view} is selected`
);
}
});
diff --git a/browser/components/firefoxview/tests/browser/browser_firefoxview_paused.js b/browser/components/firefoxview/tests/browser/browser_firefoxview_paused.js
index c95ac4fcf5..e61b48b472 100644
--- a/browser/components/firefoxview/tests/browser/browser_firefoxview_paused.js
+++ b/browser/components/firefoxview/tests/browser/browser_firefoxview_paused.js
@@ -5,9 +5,6 @@ const tabURL1 = "data:,Tab1";
const tabURL2 = "data:,Tab2";
const tabURL3 = "data:,Tab3";
-const { NonPrivateTabs } = ChromeUtils.importESModule(
- "resource:///modules/OpenTabs.sys.mjs"
-);
const TestTabs = {};
function getTopLevelViewElements(document) {
@@ -194,6 +191,42 @@ async function checkFxRenderCalls(browser, elements, selectedView) {
sandbox.restore();
}
+function dragAndDrop(
+ tab1,
+ tab2,
+ initialWindow = window,
+ destWindow = window,
+ afterTab = true,
+ context
+) {
+ let rect = tab2.getBoundingClientRect();
+ let event = {
+ ctrlKey: false,
+ altKey: false,
+ clientX: rect.left + rect.width / 2 + 10 * (afterTab ? 1 : -1),
+ clientY: rect.top + rect.height / 2,
+ };
+
+ if (destWindow != initialWindow) {
+ // Make sure that both tab1 and tab2 are visible
+ initialWindow.focus();
+ initialWindow.moveTo(rect.left, rect.top + rect.height * 3);
+ }
+
+ EventUtils.synthesizeDrop(
+ tab1,
+ tab2,
+ null,
+ "move",
+ initialWindow,
+ destWindow,
+ event
+ );
+
+ // Ensure dnd suppression is cleared.
+ EventUtils.synthesizeMouseAtCenter(tab2, { type: "mouseup" }, context);
+}
+
add_task(async function test_recentbrowsing() {
await setupOpenAndClosedTabs();
@@ -322,7 +355,7 @@ add_task(async function test_opentabs() {
const document = browser.contentDocument;
const { openTabsView } = getTopLevelViewElements(document);
- await navigateToCategoryAndWait(document, "opentabs");
+ await navigateToViewAndWait(document, "opentabs");
const { openTabsList } = await getElements(document);
ok(openTabsView, "Found the open tabs view");
@@ -387,7 +420,7 @@ add_task(async function test_recentlyclosed() {
await withFirefoxView({}, async browser => {
const document = browser.contentDocument;
const { recentlyClosedView } = getTopLevelViewElements(document);
- await navigateToCategoryAndWait(document, "recentlyclosed");
+ await navigateToViewAndWait(document, "recentlyclosed");
const { recentlyClosedList } = await getElements(document);
ok(recentlyClosedView, "Found the recently-closed view");
@@ -405,3 +438,66 @@ add_task(async function test_recentlyclosed() {
});
await BrowserTestUtils.removeTab(TestTabs.tab2);
});
+
+add_task(async function test_drag_drop_pinned_tab() {
+ await setupOpenAndClosedTabs();
+ await withFirefoxView({}, async browser => {
+ const { document } = browser.contentWindow;
+ let win1 = browser.ownerGlobal;
+ await navigateToViewAndWait(document, "opentabs");
+
+ let openTabs = document.querySelector("view-opentabs[name=opentabs]");
+ await openTabs.updateComplete;
+ await TestUtils.waitForCondition(
+ () => openTabs.viewCards[0].tabList.rowEls.length
+ );
+ await openTabs.openTabsTarget.readyWindowsPromise;
+ let card = openTabs.viewCards[0];
+ let tabRows = card.tabList.rowEls;
+ let tabChangeRaised;
+
+ // Pin first two tabs
+ for (var i = 0; i < 2; i++) {
+ tabChangeRaised = BrowserTestUtils.waitForEvent(
+ NonPrivateTabs,
+ "TabChange"
+ );
+ let currentTabEl = tabRows[i];
+ let currentTab = currentTabEl.tabElement;
+ info(`Pinning tab ${i + 1} with label: ${currentTab.label}`);
+ win1.gBrowser.pinTab(currentTab);
+ await tabChangeRaised;
+ await openTabs.updateComplete;
+ tabRows = card.tabList.rowEls;
+ currentTabEl = tabRows[i];
+
+ await TestUtils.waitForCondition(
+ () => currentTabEl.indicators.includes("pinned"),
+ `Tab ${i + 1} is pinned.`
+ );
+ }
+
+ info(`First two tabs are pinned.`);
+
+ let win2 = await BrowserTestUtils.openNewBrowserWindow();
+
+ await openTabs.updateComplete;
+ await TestUtils.waitForCondition(
+ () => openTabs.viewCards.length === 2,
+ "Two windows are shown for Open Tabs in in Fx View."
+ );
+
+ let pinnedTab = win1.gBrowser.visibleTabs[0];
+ let newWindowTab = win2.gBrowser.visibleTabs[0];
+
+ dragAndDrop(newWindowTab, pinnedTab, win2, win1, true, content);
+
+ await switchToFxViewTab();
+ await openTabs.updateComplete;
+ await TestUtils.waitForCondition(
+ () => openTabs.viewCards.length === 1,
+ "One window is shown for Open Tabs in in Fx View."
+ );
+ });
+ cleanupTabs();
+});
diff --git a/browser/components/firefoxview/tests/browser/browser_firefoxview_search_telemetry.js b/browser/components/firefoxview/tests/browser/browser_firefoxview_search_telemetry.js
index 2ea2429c15..c76a11d3ad 100644
--- a/browser/components/firefoxview/tests/browser/browser_firefoxview_search_telemetry.js
+++ b/browser/components/firefoxview/tests/browser/browser_firefoxview_search_telemetry.js
@@ -56,7 +56,7 @@ add_task(async function test_search_initiated_telemetry() {
EventUtils.sendString("example.com", content);
await telemetryEvent(searchEvent("recentbrowsing"));
- await navigateToCategoryAndWait(document, "opentabs");
+ await navigateToViewAndWait(document, "opentabs");
await clearAllParentTelemetryEvents();
is(document.location.hash, "#opentabs", "Searching within open tabs.");
const openTabs = document.querySelector("named-deck > view-opentabs");
@@ -65,7 +65,7 @@ add_task(async function test_search_initiated_telemetry() {
EventUtils.sendString("example.com", content);
await telemetryEvent(searchEvent("opentabs"));
- await navigateToCategoryAndWait(document, "recentlyclosed");
+ await navigateToViewAndWait(document, "recentlyclosed");
await clearAllParentTelemetryEvents();
is(
document.location.hash,
@@ -84,7 +84,7 @@ add_task(async function test_search_initiated_telemetry() {
EventUtils.sendString("example.com", content);
await telemetryEvent(searchEvent("recentlyclosed"));
- await navigateToCategoryAndWait(document, "syncedtabs");
+ await navigateToViewAndWait(document, "syncedtabs");
await clearAllParentTelemetryEvents();
is(document.location.hash, "#syncedtabs", "Searching within synced tabs.");
const syncedTabs = document.querySelector("named-deck > view-syncedtabs");
@@ -93,7 +93,7 @@ add_task(async function test_search_initiated_telemetry() {
EventUtils.sendString("example.com", content);
await telemetryEvent(searchEvent("syncedtabs"));
- await navigateToCategoryAndWait(document, "history");
+ await navigateToViewAndWait(document, "history");
await clearAllParentTelemetryEvents();
is(document.location.hash, "#history", "Searching within history.");
const history = document.querySelector("named-deck > view-history");
@@ -316,7 +316,7 @@ add_task(async function test_sort_history_search_telemetry() {
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "history");
+ await navigateToViewAndWait(document, "history");
const historyComponent = document.querySelector("view-history");
const searchTextbox = await TestUtils.waitForCondition(
@@ -432,7 +432,7 @@ add_task(async function test_cumulative_searches_recently_closed_telemetry() {
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "recentlyclosed");
+ await navigateToViewAndWait(document, "recentlyclosed");
is(
document.location.hash,
"#recentlyclosed",
@@ -477,7 +477,7 @@ add_task(async function test_cumulative_searches_open_tabs_telemetry() {
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "opentabs");
+ await navigateToViewAndWait(document, "opentabs");
is(document.location.hash, "#opentabs", "Searching within open tabs.");
const openTabs = document.querySelector("named-deck > view-opentabs");
@@ -523,7 +523,7 @@ add_task(async function test_cumulative_searches_history_telemetry() {
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "history");
+ await navigateToViewAndWait(document, "history");
is(document.location.hash, "#history", "Searching within history.");
const history = document.querySelector("named-deck > view-history");
const searchTextbox = await TestUtils.waitForCondition(() => {
@@ -585,7 +585,7 @@ add_task(async function test_cumulative_searches_syncedtabs_telemetry() {
const { document } = browser.contentWindow;
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
- await navigateToCategoryAndWait(document, "syncedtabs");
+ await navigateToViewAndWait(document, "syncedtabs");
is(document.location.hash, "#syncedtabs", "Searching within synced tabs.");
let syncedTabs = document.querySelector(
"view-syncedtabs:not([slot=syncedtabs])"
diff --git a/browser/components/firefoxview/tests/browser/browser_firefoxview_tab.js b/browser/components/firefoxview/tests/browser/browser_firefoxview_tab.js
index f1ac7d6742..037729ea7d 100644
--- a/browser/components/firefoxview/tests/browser/browser_firefoxview_tab.js
+++ b/browser/components/firefoxview/tests/browser/browser_firefoxview_tab.js
@@ -55,12 +55,6 @@ function triggerClickOn(target, options) {
return promise;
}
-async function add_new_tab(URL) {
- let tab = BrowserTestUtils.addTab(gBrowser, URL);
- await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
- return tab;
-}
-
add_task(async function aria_attributes() {
let win = await BrowserTestUtils.openNewBrowserWindow();
is(
diff --git a/browser/components/firefoxview/tests/browser/browser_firefoxview_virtual_list.js b/browser/components/firefoxview/tests/browser/browser_firefoxview_virtual_list.js
index 501deb8e68..bf53796ef7 100644
--- a/browser/components/firefoxview/tests/browser/browser_firefoxview_virtual_list.js
+++ b/browser/components/firefoxview/tests/browser/browser_firefoxview_virtual_list.js
@@ -29,7 +29,7 @@ add_task(async function test_max_render_count_on_win_resize() {
"Firefox View is loaded to the Recent Browsing page."
);
- await navigateToCategoryAndWait(document, "history");
+ await navigateToViewAndWait(document, "history");
let historyComponent = document.querySelector("view-history");
let tabList = historyComponent.lists[0];
diff --git a/browser/components/firefoxview/tests/browser/browser_history_firefoxview.js b/browser/components/firefoxview/tests/browser/browser_history_firefoxview.js
index a6c697e398..c4c096acff 100644
--- a/browser/components/firefoxview/tests/browser/browser_history_firefoxview.js
+++ b/browser/components/firefoxview/tests/browser/browser_history_firefoxview.js
@@ -15,8 +15,8 @@ const HISTORY_EVENT = [["firefoxview_next", "history", "visits", undefined]];
const SHOW_ALL_HISTORY_EVENT = [
["firefoxview_next", "show_all_history", "tabs", undefined],
];
-
const NEVER_REMEMBER_HISTORY_PREF = "browser.privatebrowsing.autostart";
+
const DAY_MS = 24 * 60 * 60 * 1000;
const today = new Date();
const yesterday = new Date(Date.now() - DAY_MS);
@@ -24,6 +24,14 @@ const twoDaysAgo = new Date(Date.now() - DAY_MS * 2);
const threeDaysAgo = new Date(Date.now() - DAY_MS * 3);
const fourDaysAgo = new Date(Date.now() - DAY_MS * 4);
const oneMonthAgo = new Date(today);
+const dates = [
+ today,
+ yesterday,
+ twoDaysAgo,
+ threeDaysAgo,
+ fourDaysAgo,
+ oneMonthAgo,
+];
// Set the date for the first day of the last month
oneMonthAgo.setDate(1);
@@ -47,13 +55,14 @@ function isElInViewport(element) {
);
}
-async function historyComponentReady(historyComponent) {
+async function historyComponentReady(historyComponent, expectedHistoryItems) {
await TestUtils.waitForCondition(
() =>
[...historyComponent.allHistoryItems.values()].reduce(
(acc, { length }) => acc + length,
0
- ) === 24
+ ) === expectedHistoryItems,
+ "History component ready"
);
let expected = historyComponent.historyMapByDate.length;
@@ -148,6 +157,18 @@ async function addHistoryItems(dateAdded) {
});
}
+function createHistoryEntries() {
+ let historyEntries = [];
+ for (let i = 0; i < 4; i++) {
+ historyEntries.push({
+ url: URLs[i],
+ title: `Example Domain ${i}`,
+ visits: dates.map(date => [{ date }]),
+ });
+ }
+ return historyEntries;
+}
+
add_setup(async () => {
await SpecialPowers.pushPrefEnv({
set: [["browser.firefox-view.search.enabled", true]],
@@ -160,21 +181,20 @@ add_setup(async () => {
add_task(async function test_list_ordering() {
await PlacesUtils.history.clear();
- await addHistoryItems(today);
- await addHistoryItems(yesterday);
- await addHistoryItems(twoDaysAgo);
- await addHistoryItems(threeDaysAgo);
- await addHistoryItems(fourDaysAgo);
- await addHistoryItems(oneMonthAgo);
+ const historyEntries = createHistoryEntries();
+ await PlacesUtils.history.insertMany(historyEntries);
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "history");
+ await navigateToViewAndWait(document, "history");
- let historyComponent = document.querySelector("view-history");
+ let historyComponent = await TestUtils.waitForCondition(
+ () => document.querySelector("view-history"),
+ "History component rendered"
+ );
historyComponent.profileAge = 8;
- await historyComponentReady(historyComponent);
+ await historyComponentReady(historyComponent, historyEntries.length);
let firstCard = historyComponent.cards[0];
@@ -262,7 +282,7 @@ add_task(async function test_empty_states() {
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "history");
+ await navigateToViewAndWait(document, "history");
let historyComponent = document.querySelector("view-history");
historyComponent.profileAge = 8;
@@ -350,7 +370,7 @@ add_task(async function test_observers_removed_when_view_is_hidden() {
);
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "history");
+ await navigateToViewAndWait(document, "history");
const historyComponent = document.querySelector("view-history");
historyComponent.profileAge = 8;
let visitList = await TestUtils.waitForCondition(() =>
@@ -390,20 +410,16 @@ add_task(async function test_observers_removed_when_view_is_hidden() {
add_task(async function test_show_all_history_telemetry() {
await PlacesUtils.history.clear();
- await addHistoryItems(today);
- await addHistoryItems(yesterday);
- await addHistoryItems(twoDaysAgo);
- await addHistoryItems(threeDaysAgo);
- await addHistoryItems(fourDaysAgo);
- await addHistoryItems(oneMonthAgo);
+ const historyEntries = createHistoryEntries();
+ await PlacesUtils.history.insertMany(historyEntries);
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "history");
+ await navigateToViewAndWait(document, "history");
let historyComponent = document.querySelector("view-history");
historyComponent.profileAge = 8;
- await historyComponentReady(historyComponent);
+ await historyComponentReady(historyComponent, historyEntries.length);
await clearAllParentTelemetryEvents();
let showAllHistoryBtn = historyComponent.showAllHistoryBtn;
@@ -422,12 +438,15 @@ add_task(async function test_show_all_history_telemetry() {
});
add_task(async function test_search_history() {
+ await PlacesUtils.history.clear();
+ const historyEntries = createHistoryEntries();
+ await PlacesUtils.history.insertMany(historyEntries);
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "history");
+ await navigateToViewAndWait(document, "history");
const historyComponent = document.querySelector("view-history");
historyComponent.profileAge = 8;
- await historyComponentReady(historyComponent);
+ await historyComponentReady(historyComponent, historyEntries.length);
const searchTextbox = await TestUtils.waitForCondition(
() => historyComponent.searchTextbox,
"The search textbox is displayed."
@@ -447,7 +466,7 @@ add_task(async function test_search_history() {
);
await TestUtils.waitForCondition(() => {
const { rowEls } = historyComponent.lists[0];
- return rowEls.length === 1 && rowEls[0].mainEl.href === URLs[0];
+ return rowEls.length === 1 && rowEls[0].mainEl.href === URLs[1];
}, "There is one matching search result.");
info("Input a bogus search query.");
@@ -504,7 +523,7 @@ add_task(async function test_persist_collapse_card_after_view_change() {
await addHistoryItems(today);
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "history");
+ await navigateToViewAndWait(document, "history");
const historyComponent = document.querySelector("view-history");
historyComponent.profileAge = 8;
await TestUtils.waitForCondition(
@@ -529,8 +548,8 @@ add_task(async function test_persist_collapse_card_after_view_change() {
);
// Switch to a new view and then back to History
- await navigateToCategoryAndWait(document, "syncedtabs");
- await navigateToCategoryAndWait(document, "history");
+ await navigateToViewAndWait(document, "syncedtabs");
+ await navigateToViewAndWait(document, "history");
// Check that first history card is still collapsed after changing view
ok(
diff --git a/browser/components/firefoxview/tests/browser/browser_opentabs_cards.js b/browser/components/firefoxview/tests/browser/browser_opentabs_cards.js
index d57aa3cad1..d4de3ae5a9 100644
--- a/browser/components/firefoxview/tests/browser/browser_opentabs_cards.js
+++ b/browser/components/firefoxview/tests/browser/browser_opentabs_cards.js
@@ -7,36 +7,16 @@ const ROW_DATE_ID = "fxview-tab-row-date";
let gInitialTab;
let gInitialTabURL;
-const { NonPrivateTabs } = ChromeUtils.importESModule(
- "resource:///modules/OpenTabs.sys.mjs"
-);
add_setup(function () {
// This test opens a lot of windows and tabs and might run long on slower configurations
- requestLongerTimeout(2);
+ requestLongerTimeout(3);
gInitialTab = gBrowser.selectedTab;
gInitialTabURL = gBrowser.selectedBrowser.currentURI.spec;
});
-async function navigateToOpenTabs(browser) {
- const document = browser.contentDocument;
- if (document.querySelector("named-deck").selectedViewName != "opentabs") {
- await navigateToCategoryAndWait(browser.contentDocument, "opentabs");
- }
-}
-
-function getOpenTabsComponent(browser) {
- return browser.contentDocument.querySelector("named-deck > view-opentabs");
-}
-
-function getCards(browser) {
- return getOpenTabsComponent(browser).shadowRoot.querySelectorAll(
- "view-opentabs-card"
- );
-}
-
async function cleanup() {
- await SimpleTest.promiseFocus(window);
+ await switchToWindow(window);
await promiseAllButPrimaryWindowClosed();
await BrowserTestUtils.switchTab(gBrowser, gInitialTab);
await closeFirefoxViewTab(window);
@@ -58,11 +38,6 @@ async function cleanup() {
);
}
-async function getRowsForCard(card) {
- await TestUtils.waitForCondition(() => card.tabList.rowEls.length);
- return card.tabList.rowEls;
-}
-
/**
* Verify that there are the expected number of cards, and that each card has
* the expected URLs in order.
@@ -73,10 +48,10 @@ async function getRowsForCard(card) {
* The expected URLs for each card.
*/
async function checkTabLists(browser, expected) {
- const cards = getCards(browser);
+ const cards = getOpenTabsCards(getOpenTabsComponent(browser));
is(cards.length, expected.length, `There are ${expected.length} windows.`);
for (let i = 0; i < cards.length; i++) {
- const tabItems = await getRowsForCard(cards[i]);
+ const tabItems = await getTabRowsForCard(cards[i]);
const actual = Array.from(tabItems).map(({ url }) => url);
Assert.deepEqual(
actual,
@@ -87,11 +62,12 @@ async function checkTabLists(browser, expected) {
}
add_task(async function open_tab_same_window() {
+ let tabChangeRaised;
await openFirefoxViewTab(window).then(async viewTab => {
const browser = viewTab.linkedBrowser;
await navigateToOpenTabs(browser);
const openTabs = getOpenTabsComponent(browser);
- await openTabs.openTabsTarget.readyWindowsPromise;
+ await NonPrivateTabs.readyWindowsPromise;
await openTabs.updateComplete;
await checkTabLists(browser, [[gInitialTabURL]]);
@@ -99,7 +75,7 @@ add_task(async function open_tab_same_window() {
browser.contentDocument,
"visibilitychange"
);
- let tabChangeRaised = BrowserTestUtils.waitForEvent(
+ tabChangeRaised = BrowserTestUtils.waitForEvent(
NonPrivateTabs,
"TabChange"
);
@@ -114,7 +90,7 @@ add_task(async function open_tab_same_window() {
const browser = viewTab.linkedBrowser;
const openTabs = getOpenTabsComponent(browser);
setSortOption(openTabs, "tabStripOrder");
- await openTabs.openTabsTarget.readyWindowsPromise;
+ await NonPrivateTabs.readyWindowsPromise;
await openTabs.updateComplete;
await checkTabLists(browser, [[gInitialTabURL, TEST_URL]]);
@@ -122,8 +98,8 @@ add_task(async function open_tab_same_window() {
browser.contentDocument,
"visibilitychange"
);
- const cards = getCards(browser);
- const tabItems = await getRowsForCard(cards[0]);
+ const cards = getOpenTabsCards(openTabs);
+ const tabItems = await getTabRowsForCard(cards[0]);
tabItems[0].mainEl.click();
await promiseHidden;
});
@@ -135,8 +111,11 @@ add_task(async function open_tab_same_window() {
await openFirefoxViewTab(window).then(async viewTab => {
const browser = viewTab.linkedBrowser;
- const cards = getCards(browser);
- let tabItems = await getRowsForCard(cards[0]);
+ const openTabs = getOpenTabsComponent(browser);
+ await openTabs.updateComplete;
+
+ const cards = getOpenTabsCards(openTabs);
+ let tabItems = await getTabRowsForCard(cards[0]);
let promiseHidden = BrowserTestUtils.waitForEvent(
browser.contentDocument,
@@ -154,7 +133,13 @@ add_task(async function open_tab_same_window() {
await openFirefoxViewTab(window).then(async viewTab => {
const browser = viewTab.linkedBrowser;
- let tabChangeRaised = BrowserTestUtils.waitForEvent(
+ const openTabs = getOpenTabsComponent(browser);
+ await openTabs.updateComplete;
+
+ // sanity-check current tab order before we change it
+ await checkTabLists(browser, [[gInitialTabURL, TEST_URL]]);
+
+ tabChangeRaised = BrowserTestUtils.waitForEvent(
NonPrivateTabs,
"TabChange"
);
@@ -162,18 +147,24 @@ add_task(async function open_tab_same_window() {
info("Bring the new tab to the front.");
gBrowser.moveTabTo(newTab, 0);
+ info("Waiting for tabChangeRaised to resolve from the tab move");
await tabChangeRaised;
+ await openTabs.updateComplete;
+
await checkTabLists(browser, [[TEST_URL, gInitialTabURL]]);
tabChangeRaised = BrowserTestUtils.waitForEvent(
NonPrivateTabs,
"TabChange"
);
+ info("Remove the new tab");
await BrowserTestUtils.removeTab(newTab);
+ info("Waiting for tabChangeRaised to resolve from removing the tab");
await tabChangeRaised;
+ await openTabs.updateComplete;
await checkTabLists(browser, [[gInitialTabURL]]);
- const [card] = getCards(browser);
- const [row] = await getRowsForCard(card);
+ const [card] = getOpenTabsCards(getOpenTabsComponent(browser));
+ const [row] = await getTabRowsForCard(card);
ok(
!row.shadowRoot.getElementById("fxview-tab-row-url").hidden,
"The URL is displayed, since we have one window."
@@ -188,25 +179,29 @@ add_task(async function open_tab_same_window() {
});
add_task(async function open_tab_new_window() {
- const win = await BrowserTestUtils.openNewBrowserWindow();
- let winFocused;
- await BrowserTestUtils.openNewForegroundTab(win.gBrowser, TEST_URL);
+ const win2 = await BrowserTestUtils.openNewBrowserWindow();
+ let winBecameActive;
+ let tabChangeRaised;
+ await switchToWindow(win2);
+ await NonPrivateTabs.readyWindowsPromise;
+
+ await BrowserTestUtils.openNewForegroundTab(win2.gBrowser, TEST_URL);
info("Open fxview in new window");
- await openFirefoxViewTab(win).then(async viewTab => {
+ await openFirefoxViewTab(win2).then(async viewTab => {
const browser = viewTab.linkedBrowser;
await navigateToOpenTabs(browser);
const openTabs = getOpenTabsComponent(browser);
setSortOption(openTabs, "tabStripOrder");
- await openTabs.openTabsTarget.readyWindowsPromise;
+ await NonPrivateTabs.readyWindowsPromise;
await openTabs.updateComplete;
await checkTabLists(browser, [
[gInitialTabURL, TEST_URL],
[gInitialTabURL],
]);
- const cards = getCards(browser);
- const originalWinRows = await getRowsForCard(cards[1]);
+ const cards = getOpenTabsCards(openTabs);
+ const originalWinRows = await getTabRowsForCard(cards[1]);
const [row] = originalWinRows;
ok(
row.shadowRoot.getElementById("fxview-tab-row-url").hidden,
@@ -217,47 +212,60 @@ add_task(async function open_tab_new_window() {
"The date is hidden, since we have two windows."
);
info("Select a tab from the original window.");
- let tabChangeRaised = BrowserTestUtils.waitForEvent(
+ tabChangeRaised = BrowserTestUtils.waitForEvent(
NonPrivateTabs,
"TabRecencyChange"
);
- winFocused = BrowserTestUtils.waitForEvent(window, "focus", true);
- originalWinRows[0].mainEl.click();
+ winBecameActive = Promise.all([
+ BrowserTestUtils.waitForEvent(window, "focus", true),
+ BrowserTestUtils.waitForEvent(window, "activate"),
+ ]);
+ ok(row.tabElement, "The row has a tabElement property");
+ is(
+ row.tabElement.ownerGlobal,
+ window,
+ "The tabElement's ownerGlobal is our original window"
+ );
+ info(`Clicking on row with URL: ${row.url}`);
+ row.mainEl.click();
+ info("Waiting for TabRecencyChange event");
await tabChangeRaised;
});
- info("Wait for the original window to be focused");
- await winFocused;
+ info("Wait for the original window to be focused & active");
+ await winBecameActive;
await openFirefoxViewTab(window).then(async viewTab => {
const browser = viewTab.linkedBrowser;
await navigateToOpenTabs(browser);
const openTabs = getOpenTabsComponent(browser);
- await openTabs.openTabsTarget.readyWindowsPromise;
+ await NonPrivateTabs.readyWindowsPromise;
await openTabs.updateComplete;
- const cards = getCards(browser);
+ const cards = getOpenTabsCards(openTabs);
is(cards.length, 2, "There are two windows.");
- const newWinRows = await getRowsForCard(cards[1]);
+ const newWinRows = await getTabRowsForCard(cards[1]);
info("Select a tab from the new window.");
- winFocused = BrowserTestUtils.waitForEvent(win, "focus", true);
- let tabChangeRaised = BrowserTestUtils.waitForEvent(
+ winBecameActive = Promise.all([
+ BrowserTestUtils.waitForEvent(win2, "focus", true),
+ BrowserTestUtils.waitForEvent(win2, "activate"),
+ ]);
+ tabChangeRaised = BrowserTestUtils.waitForEvent(
NonPrivateTabs,
"TabRecencyChange"
);
newWinRows[0].mainEl.click();
await tabChangeRaised;
});
- info("Wait for the new window to be focused");
- await winFocused;
+ info("Wait for the new window to be focused & active");
+ await winBecameActive;
await cleanup();
});
add_task(async function open_tab_new_private_window() {
await BrowserTestUtils.openNewBrowserWindow({ private: true });
- await SimpleTest.promiseFocus(window);
await openFirefoxViewTab(window).then(async viewTab => {
const browser = viewTab.linkedBrowser;
await navigateToOpenTabs(browser);
@@ -265,7 +273,7 @@ add_task(async function open_tab_new_private_window() {
await openTabs.openTabsTarget.readyWindowsPromise;
await openTabs.updateComplete;
- const cards = getCards(browser);
+ const cards = getOpenTabsCards(openTabs);
is(cards.length, 1, "The private window is not displayed.");
});
await cleanup();
@@ -274,6 +282,7 @@ add_task(async function open_tab_new_private_window() {
add_task(async function open_tab_new_window_sort_by_recency() {
info("Open new tabs in a new window.");
const newWindow = await BrowserTestUtils.openNewBrowserWindow();
+ await switchToWindow(newWindow);
const tabs = [
newWindow.gBrowser.selectedTab,
await BrowserTestUtils.openNewForegroundTab(newWindow.gBrowser, URLs[0]),
@@ -285,7 +294,7 @@ add_task(async function open_tab_new_window_sort_by_recency() {
await navigateToOpenTabs(linkedBrowser);
const openTabs = getOpenTabsComponent(linkedBrowser);
setSortOption(openTabs, "recency");
- await openTabs.openTabsTarget.readyWindowsPromise;
+ await NonPrivateTabs.readyWindowsPromise;
await openTabs.updateComplete;
await checkTabLists(linkedBrowser, [
@@ -293,13 +302,13 @@ add_task(async function open_tab_new_window_sort_by_recency() {
[URLs[1], URLs[0], gInitialTabURL],
]);
info("Select tabs in the new window to trigger recency changes.");
- await SimpleTest.promiseFocus(newWindow);
+ await switchToWindow(newWindow);
await BrowserTestUtils.switchTab(newWindow.gBrowser, tabs[1]);
await BrowserTestUtils.switchTab(newWindow.gBrowser, tabs[0]);
- await SimpleTest.promiseFocus(window);
+ await switchToWindow(window);
await TestUtils.waitForCondition(async () => {
- const [, secondCard] = getCards(linkedBrowser);
- const tabItems = await getRowsForCard(secondCard);
+ const [, secondCard] = getOpenTabsCards(openTabs);
+ const tabItems = await getTabRowsForCard(secondCard);
return tabItems[0].url === gInitialTabURL;
});
await checkTabLists(linkedBrowser, [
@@ -311,12 +320,13 @@ add_task(async function open_tab_new_window_sort_by_recency() {
});
add_task(async function styling_for_multiple_windows() {
+ let tabChangeRaised;
await openFirefoxViewTab(window).then(async viewTab => {
const browser = viewTab.linkedBrowser;
await navigateToOpenTabs(browser);
const openTabs = getOpenTabsComponent(browser);
setSortOption(openTabs, "tabStripOrder");
- await openTabs.openTabsTarget.readyWindowsPromise;
+ await NonPrivateTabs.readyWindowsPromise;
await openTabs.updateComplete;
ok(
@@ -325,13 +335,10 @@ add_task(async function styling_for_multiple_windows() {
);
});
- await BrowserTestUtils.openNewBrowserWindow();
- let tabChangeRaised = BrowserTestUtils.waitForEvent(
- NonPrivateTabs,
- "TabChange"
- );
+ let win2 = await BrowserTestUtils.openNewBrowserWindow();
+ info("Switching to new window");
+ await switchToWindow(win2);
await NonPrivateTabs.readyWindowsPromise;
- await tabChangeRaised;
is(
NonPrivateTabs.currentWindows.length,
2,
@@ -339,290 +346,54 @@ add_task(async function styling_for_multiple_windows() {
);
info("switch to firefox view in the first window");
- SimpleTest.promiseFocus(window);
await openFirefoxViewTab(window).then(async viewTab => {
const browser = viewTab.linkedBrowser;
const openTabs = getOpenTabsComponent(browser);
- await openTabs.openTabsTarget.readyWindowsPromise;
- await openTabs.updateComplete;
+ const cardContainer = openTabs.shadowRoot.querySelector(
+ ".view-opentabs-card-container"
+ );
+ info("waiting for card-count to reflect 2 windows");
+ await BrowserTestUtils.waitForCondition(() => {
+ return cardContainer.getAttribute("card-count") == "two";
+ });
is(
openTabs.openTabsTarget.currentWindows.length,
2,
"There should be 2 current windows"
);
- ok(
- openTabs.shadowRoot.querySelector("[card-count=two]"),
- "The container shows two columns when two windows are open."
+ is(
+ cardContainer.getAttribute("card-count"),
+ "two",
+ "The container shows two columns when two windows are open"
);
});
- await BrowserTestUtils.openNewBrowserWindow();
+
tabChangeRaised = BrowserTestUtils.waitForEvent(NonPrivateTabs, "TabChange");
+ let win3 = await BrowserTestUtils.openNewBrowserWindow();
+ await switchToWindow(win3);
await NonPrivateTabs.readyWindowsPromise;
await tabChangeRaised;
is(
NonPrivateTabs.currentWindows.length,
3,
- "NonPrivateTabs now has 2 currentWindows"
+ "NonPrivateTabs now has 3 currentWindows"
);
- SimpleTest.promiseFocus(window);
- await openFirefoxViewTab(window).then(async viewTab => {
- const browser = viewTab.linkedBrowser;
- const openTabs = getOpenTabsComponent(browser);
- await openTabs.openTabsTarget.readyWindowsPromise;
- await openTabs.updateComplete;
-
- ok(
- openTabs.shadowRoot.querySelector("[card-count=three-or-more]"),
- "The container shows three columns when three windows are open."
- );
- });
- await cleanup();
-});
-
-add_task(async function toggle_show_more_link() {
- const tabEntry = url => ({
- entries: [{ url, triggeringPrincipal_base64 }],
- });
- const NUMBER_OF_WINDOWS = 4;
- const NUMBER_OF_TABS = 42;
- const browserState = { windows: [] };
- for (let windowIndex = 0; windowIndex < NUMBER_OF_WINDOWS; windowIndex++) {
- const winData = { tabs: [] };
- let tabCount = windowIndex == NUMBER_OF_WINDOWS - 1 ? NUMBER_OF_TABS : 1;
- for (let i = 0; i < tabCount; i++) {
- winData.tabs.push(tabEntry(`data:,Window${windowIndex}-Tab${i}`));
- }
- winData.selected = winData.tabs.length;
- browserState.windows.push(winData);
- }
- // use Session restore to batch-open windows and tabs
- await SessionStoreTestUtils.promiseBrowserState(browserState);
- // restoring this state requires an update to the initial tab globals
- // so cleanup expects the right thing
- gInitialTab = gBrowser.selectedTab;
- gInitialTabURL = gBrowser.selectedBrowser.currentURI.spec;
-
- const windows = Array.from(Services.wm.getEnumerator("navigator:browser"));
- is(windows.length, NUMBER_OF_WINDOWS, "There are four browser windows.");
-
- const tab = (win = window) => {
- info("Tab");
- EventUtils.synthesizeKey("KEY_Tab", {}, win);
- };
-
- const enter = (win = window) => {
- info("Enter");
- EventUtils.synthesizeKey("KEY_Enter", {}, win);
- };
-
- let lastCard;
-
- SimpleTest.promiseFocus(window);
+ // switch back to the original window
await openFirefoxViewTab(window).then(async viewTab => {
const browser = viewTab.linkedBrowser;
- await navigateToOpenTabs(browser);
const openTabs = getOpenTabsComponent(browser);
- await openTabs.openTabsTarget.readyWindowsPromise;
- await openTabs.updateComplete;
-
- const cards = getCards(browser);
- is(cards.length, NUMBER_OF_WINDOWS, "There are four windows.");
- lastCard = cards[NUMBER_OF_WINDOWS - 1];
- });
-
- await openFirefoxViewTab(window).then(async viewTab => {
- const browser = viewTab.linkedBrowser;
- const openTabs = getOpenTabsComponent(browser);
- await openTabs.openTabsTarget.readyWindowsPromise;
- await openTabs.updateComplete;
- Assert.less(
- (await getRowsForCard(lastCard)).length,
- NUMBER_OF_TABS,
- "Not all tabs are shown yet."
- );
- info("Toggle the Show More link.");
- lastCard.shadowRoot.querySelector("div[slot=footer]").click();
- await BrowserTestUtils.waitForMutationCondition(
- lastCard.shadowRoot,
- { childList: true, subtree: true },
- async () => (await getRowsForCard(lastCard)).length === NUMBER_OF_TABS
+ const cardContainer = openTabs.shadowRoot.querySelector(
+ ".view-opentabs-card-container"
);
- info("Toggle the Show Less link.");
- lastCard.shadowRoot.querySelector("div[slot=footer]").click();
- await BrowserTestUtils.waitForMutationCondition(
- lastCard.shadowRoot,
- { childList: true, subtree: true },
- async () => (await getRowsForCard(lastCard)).length < NUMBER_OF_TABS
- );
-
- // Setting this pref allows the test to run as expected with a keyboard on MacOS
- await SpecialPowers.pushPrefEnv({
- set: [["accessibility.tabfocus", 7]],
+ await BrowserTestUtils.waitForCondition(() => {
+ return cardContainer.getAttribute("card-count") == "three-or-more";
});
-
- info("Toggle the Show More link with keyboard.");
- lastCard.shadowRoot.querySelector("card-container").summaryEl.focus();
- // Tab to first item in the list
- tab();
- // Tab to the footer
- tab();
- enter();
- await BrowserTestUtils.waitForMutationCondition(
- lastCard.shadowRoot,
- { childList: true, subtree: true },
- async () => (await getRowsForCard(lastCard)).length === NUMBER_OF_TABS
- );
-
- info("Toggle the Show Less link with keyboard.");
- lastCard.shadowRoot.querySelector("card-container").summaryEl.focus();
- // Tab to first item in the list
- tab();
- // Tab to the footer
- tab();
- enter();
- await BrowserTestUtils.waitForMutationCondition(
- lastCard.shadowRoot,
- { childList: true, subtree: true },
- async () => (await getRowsForCard(lastCard)).length < NUMBER_OF_TABS
- );
-
- await SpecialPowers.popPrefEnv();
- });
- await cleanup();
-});
-
-add_task(async function search_open_tabs() {
- // Open a new window and navigate to TEST_URL. Then, when we search for
- // TEST_URL, it should show a search result in the new window's card.
- const win = await BrowserTestUtils.openNewBrowserWindow();
- await BrowserTestUtils.openNewForegroundTab(win.gBrowser, TEST_URL);
-
- await SpecialPowers.pushPrefEnv({
- set: [["browser.firefox-view.search.enabled", true]],
- });
- await openFirefoxViewTab(window).then(async viewTab => {
- const browser = viewTab.linkedBrowser;
- await navigateToOpenTabs(browser);
- const openTabs = getOpenTabsComponent(browser);
- await openTabs.openTabsTarget.readyWindowsPromise;
- await openTabs.updateComplete;
-
- const cards = getCards(browser);
- is(cards.length, 2, "There are two windows.");
- const winTabs = await getRowsForCard(cards[0]);
- const newWinTabs = await getRowsForCard(cards[1]);
-
- info("Input a search query.");
- EventUtils.synthesizeMouseAtCenter(openTabs.searchTextbox, {}, content);
- EventUtils.sendString(TEST_URL, content);
- await TestUtils.waitForCondition(
- () => openTabs.viewCards[0].tabList.rowEls.length === 0,
- "There are no matching search results in the original window."
- );
- await TestUtils.waitForCondition(
- () => openTabs.viewCards[1].tabList.rowEls.length === 1,
- "There is one matching search result in the new window."
- );
-
- info("Clear the search query.");
- EventUtils.synthesizeMouseAtCenter(
- openTabs.searchTextbox.clearButton,
- {},
- content
- );
- await TestUtils.waitForCondition(
- () => openTabs.viewCards[0].tabList.rowEls.length === winTabs.length,
- "The original window's list is restored."
- );
- await TestUtils.waitForCondition(
- () => openTabs.viewCards[1].tabList.rowEls.length === newWinTabs.length,
- "The new window's list is restored."
- );
- openTabs.searchTextbox.blur();
-
- info("Input a search query with keyboard.");
- EventUtils.synthesizeKey("f", { accelKey: true }, content);
- EventUtils.sendString(TEST_URL, content);
- await TestUtils.waitForCondition(
- () => openTabs.viewCards[0].tabList.rowEls.length === 0,
- "There are no matching search results in the original window."
- );
- await TestUtils.waitForCondition(
- () => openTabs.viewCards[1].tabList.rowEls.length === 1,
- "There is one matching search result in the new window."
- );
-
- info("Clear the search query with keyboard.");
- is(
- openTabs.shadowRoot.activeElement,
- openTabs.searchTextbox,
- "Search input is focused"
- );
- EventUtils.synthesizeKey("KEY_Tab", {}, content);
ok(
- openTabs.searchTextbox.clearButton.matches(":focus-visible"),
- "Clear Search button is focused"
- );
- EventUtils.synthesizeKey("KEY_Enter", {}, content);
- await TestUtils.waitForCondition(
- () => openTabs.viewCards[0].tabList.rowEls.length === winTabs.length,
- "The original window's list is restored."
- );
- await TestUtils.waitForCondition(
- () => openTabs.viewCards[1].tabList.rowEls.length === newWinTabs.length,
- "The new window's list is restored."
- );
- });
-
- await SpecialPowers.popPrefEnv();
- await cleanup();
-});
-
-add_task(async function search_open_tabs_recent_browsing() {
- const NUMBER_OF_TABS = 6;
- const win = await BrowserTestUtils.openNewBrowserWindow();
- for (let i = 0; i < NUMBER_OF_TABS; i++) {
- await BrowserTestUtils.openNewForegroundTab(win.gBrowser, TEST_URL);
- }
- await SpecialPowers.pushPrefEnv({
- set: [["browser.firefox-view.search.enabled", true]],
- });
- await openFirefoxViewTab(window).then(async viewTab => {
- const browser = viewTab.linkedBrowser;
- await navigateToCategoryAndWait(browser.contentDocument, "recentbrowsing");
- const recentBrowsing = browser.contentDocument.querySelector(
- "view-recentbrowsing"
- );
-
- info("Input a search query.");
- EventUtils.synthesizeMouseAtCenter(
- recentBrowsing.searchTextbox,
- {},
- content
- );
- EventUtils.sendString(TEST_URL, content);
- const slot = recentBrowsing.querySelector("[slot='opentabs']");
- await TestUtils.waitForCondition(
- () => slot.viewCards[0].tabList.rowEls.length === 5,
- "Not all search results are shown yet."
+ openTabs.shadowRoot.querySelector("[card-count=three-or-more]"),
+ "The container shows three columns when three windows are open."
);
-
- info("Click the Show All link.");
- const showAllLink = await TestUtils.waitForCondition(() => {
- const elt = slot.viewCards[0].shadowRoot.querySelector(
- "[data-l10n-id='firefoxview-show-all']"
- );
- EventUtils.synthesizeMouseAtCenter(elt, {}, content);
- if (slot.viewCards[0].tabList.rowEls.length === NUMBER_OF_TABS) {
- return elt;
- }
- return false;
- }, "All search results are shown.");
- is(showAllLink.role, "link", "The show all control is a link.");
- ok(BrowserTestUtils.isHidden(showAllLink), "The show all link is hidden.");
});
- await SpecialPowers.popPrefEnv();
await cleanup();
});
diff --git a/browser/components/firefoxview/tests/browser/browser_opentabs_changes.js b/browser/components/firefoxview/tests/browser/browser_opentabs_changes.js
index c293afa8cd..15aba26d74 100644
--- a/browser/components/firefoxview/tests/browser/browser_opentabs_changes.js
+++ b/browser/components/firefoxview/tests/browser/browser_opentabs_changes.js
@@ -1,4 +1,4 @@
-const { NonPrivateTabs, getTabsTargetForWindow } = ChromeUtils.importESModule(
+const { getTabsTargetForWindow } = ChromeUtils.importESModule(
"resource:///modules/OpenTabs.sys.mjs"
);
let privateTabsChanges;
@@ -505,8 +505,9 @@ add_task(async function test_tabsFromPrivateWindows() {
});
const private2TabsChanges = getTabsTargetForWindow(private2Win);
private2TabsChanges.addEventListener("TabChange", private2Listener);
- ok(
- privateTabsChanges !== getTabsTargetForWindow(private2Win),
+ Assert.notStrictEqual(
+ privateTabsChanges,
+ getTabsTargetForWindow(private2Win),
"getTabsTargetForWindow creates a distinct instance for a different private window"
);
diff --git a/browser/components/firefoxview/tests/browser/browser_opentabs_firefoxview.js b/browser/components/firefoxview/tests/browser/browser_opentabs_firefoxview.js
index 57d0f8d031..955c2363d7 100644
--- a/browser/components/firefoxview/tests/browser/browser_opentabs_firefoxview.js
+++ b/browser/components/firefoxview/tests/browser/browser_opentabs_firefoxview.js
@@ -1,6 +1,9 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
+// Test regularly times out - especially with verify
+requestLongerTimeout(2);
+
const TEST_URL1 = "about:robots";
const TEST_URL2 = "https://example.org/";
const TEST_URL3 = "about:mozilla";
@@ -20,27 +23,6 @@ const fxaDevicesWithCommands = [
},
];
-const { NonPrivateTabs } = ChromeUtils.importESModule(
- "resource:///modules/OpenTabs.sys.mjs"
-);
-
-async function getRowsForCard(card) {
- await TestUtils.waitForCondition(() => card.tabList.rowEls.length);
- return card.tabList.rowEls;
-}
-
-async function add_new_tab(URL) {
- let tabChangeRaised = BrowserTestUtils.waitForEvent(
- NonPrivateTabs,
- "TabChange"
- );
- let tab = BrowserTestUtils.addTab(gBrowser, URL);
- // wait so we can reliably compare the tab URL
- await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
- await tabChangeRaised;
- return tab;
-}
-
function getVisibleTabURLs(win = window) {
return win.gBrowser.visibleTabs.map(tab => tab.linkedBrowser.currentURI.spec);
}
@@ -78,7 +60,7 @@ async function waitUntilRowsMatch(openTabs, cardIndex, expectedURLs) {
card.shadowRoot,
{ characterData: true, childList: true, subtree: true },
async () => {
- let rows = await getRowsForCard(card);
+ let rows = await getTabRowsForCard(card);
return (
rows.length == expectedURLs.length &&
JSON.stringify(getTabRowURLs(rows)) == expectedURLsAsString
@@ -106,7 +88,7 @@ async function getContextMenuPanelListForCard(card) {
async function openContextMenuForItem(tabItem, card) {
// click on the item's button element (more menu)
// and wait for the panel list to be shown
- tabItem.buttonEl.click();
+ tabItem.secondaryButtonEl.click();
// NOTE: menu must populate with devices data before it can be rendered
// so the creation of the panel-list can be async
let panelList = await getContextMenuPanelListForCard(card);
@@ -122,7 +104,7 @@ async function moreMenuSetup() {
await clickFirefoxViewButton(window);
const document = window.FirefoxViewHandler.tab.linkedBrowser.contentDocument;
- await navigateToCategoryAndWait(document, "opentabs");
+ await navigateToViewAndWait(document, "opentabs");
let openTabs = document.querySelector("view-opentabs[name=opentabs]");
setSortOption(openTabs, "tabStripOrder");
@@ -134,7 +116,7 @@ async function moreMenuSetup() {
let cards = getOpenTabsCards(openTabs);
is(cards.length, 1, "There is one open window.");
- let rows = await getRowsForCard(cards[0]);
+ let rows = await getTabRowsForCard(cards[0]);
let firstTab = rows[0];
@@ -148,6 +130,44 @@ async function moreMenuSetup() {
return [cards, rows];
}
+add_task(async function test_close_open_tab() {
+ await withFirefoxView({}, async browser => {
+ const [cards, rows] = await moreMenuSetup();
+ const firstTab = rows[0];
+ const tertiaryButtonEl = firstTab.tertiaryButtonEl;
+
+ ok(tertiaryButtonEl, "Dismiss button exists");
+
+ await clearAllParentTelemetryEvents();
+ let closeTabEvent = [
+ ["firefoxview_next", "close_open_tab", "tabs", undefined],
+ ];
+
+ let tabsUpdated = BrowserTestUtils.waitForEvent(
+ NonPrivateTabs,
+ "TabChange"
+ );
+ EventUtils.synthesizeMouseAtCenter(tertiaryButtonEl, {}, content);
+ await tabsUpdated;
+ Assert.deepEqual(
+ getVisibleTabURLs(),
+ [TEST_URL2, TEST_URL3],
+ "First tab successfully removed"
+ );
+
+ await telemetryEvent(closeTabEvent);
+
+ const openTabs = cards[0].ownerDocument.querySelector(
+ "view-opentabs[name=opentabs]"
+ );
+ await waitUntilRowsMatch(openTabs, 0, [TEST_URL2, TEST_URL3]);
+
+ while (gBrowser.tabs.length > 1) {
+ BrowserTestUtils.removeTab(gBrowser.tabs[0]);
+ }
+ });
+});
+
add_task(async function test_more_menus() {
await withFirefoxView({}, async browser => {
let win = browser.ownerGlobal;
@@ -156,11 +176,11 @@ add_task(async function test_more_menus() {
gBrowser.selectedTab = gBrowser.visibleTabs[0];
Assert.equal(
gBrowser.selectedTab.linkedBrowser.currentURI.spec,
- "about:blank",
- "Selected tab is about:blank"
+ "about:mozilla",
+ "Selected tab is about:mozilla"
);
- info(`Loading ${TEST_URL1} into the selected about:blank tab`);
+ info(`Loading ${TEST_URL1} into the selected about:mozilla tab`);
let tabLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
win.gURLBar.focus();
@@ -176,64 +196,18 @@ add_task(async function test_more_menus() {
"Prepared 3 open tabs"
);
+ // Move Tab submenu item
let firstTab = rows[0];
// Open the panel list (more menu) from the first list item
let panelList = await openContextMenuForItem(firstTab, cards[0]);
- // Close Tab menu item
- info("Panel list shown. Clicking on panel-item");
- let panelItem = panelList.querySelector(
- "panel-item[data-l10n-id=fxviewtabrow-close-tab]"
- );
- let panelItemButton = panelItem.shadowRoot.querySelector(
- "button[role=menuitem]"
- );
- ok(panelItem, "Close Tab panel item exists");
- ok(
- panelItemButton,
- "Close Tab panel item button with role=menuitem exists"
- );
-
- await clearAllParentTelemetryEvents();
- let contextMenuEvent = [
- [
- "firefoxview_next",
- "context_menu",
- "tabs",
- undefined,
- { menu_action: "close-tab", data_type: "opentabs" },
- ],
- ];
-
- // close a tab via the menu
- let tabChangeRaised = BrowserTestUtils.waitForEvent(
- NonPrivateTabs,
- "TabChange"
- );
- menuHidden = BrowserTestUtils.waitForEvent(panelList, "hidden");
- panelItemButton.click();
- info("Waiting for result of closing a tab via the menu");
- await tabChangeRaised;
- await cards[0].getUpdateComplete();
- await menuHidden;
- await telemetryEvent(contextMenuEvent);
-
- Assert.deepEqual(
- getVisibleTabURLs(),
- [TEST_URL2, TEST_URL3],
- "Got the expected 2 open tabs"
- );
-
let openTabs = cards[0].ownerDocument.querySelector(
"view-opentabs[name=opentabs]"
);
- await waitUntilRowsMatch(openTabs, 0, [TEST_URL2, TEST_URL3]);
+ await waitUntilRowsMatch(openTabs, 0, [TEST_URL1, TEST_URL2, TEST_URL3]);
- // Move Tab submenu item
- firstTab = rows[0];
- is(firstTab.url, TEST_URL2, `First tab list item is ${TEST_URL2}`);
+ is(firstTab.url, TEST_URL1, `First tab list item is ${TEST_URL1}`);
- panelList = await openContextMenuForItem(firstTab, cards[0]);
let moveTabsPanelItem = panelList.querySelector(
"panel-item[data-l10n-id=fxviewtabrow-move-tab]"
);
@@ -243,15 +217,14 @@ add_task(async function test_more_menus() {
);
ok(moveTabsSubmenuList, "Move tabs submenu panel list exists");
- // navigate down to the "Move tabs" submenu option, and
+ // navigate to the "Move tabs" submenu option, and
// open it with the right arrow key
- EventUtils.synthesizeKey("KEY_ArrowDown", {});
shown = BrowserTestUtils.waitForEvent(moveTabsSubmenuList, "shown");
EventUtils.synthesizeKey("KEY_ArrowRight", {});
await shown;
await clearAllParentTelemetryEvents();
- contextMenuEvent = [
+ let contextMenuEvent = [
[
"firefoxview_next",
"context_menu",
@@ -264,7 +237,7 @@ add_task(async function test_more_menus() {
// click on the first option, which should be "Move to the end" since
// this is the first tab
menuHidden = BrowserTestUtils.waitForEvent(panelList, "hidden");
- tabChangeRaised = BrowserTestUtils.waitForEvent(
+ let tabChangeRaised = BrowserTestUtils.waitForEvent(
NonPrivateTabs,
"TabChange"
);
@@ -276,7 +249,7 @@ add_task(async function test_more_menus() {
Assert.deepEqual(
getVisibleTabURLs(),
- [TEST_URL3, TEST_URL2],
+ [TEST_URL2, TEST_URL3, TEST_URL1],
"The last tab became the first tab"
);
@@ -284,15 +257,15 @@ add_task(async function test_more_menus() {
// closing a tab since it very clearly reveals the issues
// outlined in bug 1852622 when there are 3 or more tabs open
// and one is moved via the more menus.
- await waitUntilRowsMatch(openTabs, 0, [TEST_URL3, TEST_URL2]);
+ await waitUntilRowsMatch(openTabs, 0, [TEST_URL2, TEST_URL3, TEST_URL1]);
// Copy Link menu item (copyLink function that's called is a member of Viewpage.mjs)
panelList = await openContextMenuForItem(firstTab, cards[0]);
firstTab = rows[0];
- panelItem = panelList.querySelector(
+ let panelItem = panelList.querySelector(
"panel-item[data-l10n-id=fxviewtabrow-copy-link]"
);
- panelItemButton = panelItem.shadowRoot.querySelector(
+ let panelItemButton = panelItem.shadowRoot.querySelector(
"button[role=menuitem]"
);
ok(panelItem, "Copy link panel item exists");
@@ -323,7 +296,7 @@ add_task(async function test_more_menus() {
"text/plain",
Ci.nsIClipboard.kGlobalClipboard
);
- is(copiedText, TEST_URL3, "The correct url has been copied and pasted");
+ is(copiedText, TEST_URL2, "The correct url has been copied and pasted");
while (gBrowser.tabs.length > 1) {
BrowserTestUtils.removeTab(gBrowser.tabs[0]);
@@ -349,11 +322,11 @@ add_task(async function test_send_device_submenu() {
.callsFake(() => fxaDevicesWithCommands);
await withFirefoxView({}, async browser => {
- // TEST_URL2 is our only tab, left over from previous test
+ // TEST_URL1 is our only tab, left over from previous test
Assert.deepEqual(
getVisibleTabURLs(),
- [TEST_URL2],
- `We initially have a single ${TEST_URL2} tab`
+ [TEST_URL1],
+ `We initially have a single ${TEST_URL1} tab`
);
let shown;
@@ -376,9 +349,7 @@ add_task(async function test_send_device_submenu() {
// navigate down to the "Send tabs" submenu option, and
// open it with the right arrow key
- EventUtils.synthesizeKey("KEY_ArrowDown", {});
- EventUtils.synthesizeKey("KEY_ArrowDown", {});
- EventUtils.synthesizeKey("KEY_ArrowDown", {});
+ EventUtils.synthesizeKey("KEY_ArrowDown", { repeat: 4 });
shown = BrowserTestUtils.waitForEvent(sendTabSubmenuList, "shown");
EventUtils.synthesizeKey("KEY_ArrowRight", {});
@@ -389,9 +360,9 @@ add_task(async function test_send_device_submenu() {
.expects("sendTabToDevice")
.once()
.withExactArgs(
- TEST_URL2,
+ TEST_URL1,
[fxaDevicesWithCommands[0]],
- "mochitest index /"
+ "Gort! Klaatu barada nikto!"
)
.returns(true);
diff --git a/browser/components/firefoxview/tests/browser/browser_opentabs_more.js b/browser/components/firefoxview/tests/browser/browser_opentabs_more.js
new file mode 100644
index 0000000000..fd25348699
--- /dev/null
+++ b/browser/components/firefoxview/tests/browser/browser_opentabs_more.js
@@ -0,0 +1,151 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let gInitialTab;
+let gInitialTabURL;
+
+// This test opens many tabs and regularly times out - especially with verify
+requestLongerTimeout(2);
+
+add_setup(function () {
+ gInitialTab = gBrowser.selectedTab;
+ gInitialTabURL = gBrowser.selectedBrowser.currentURI.spec;
+});
+
+async function cleanup() {
+ await SimpleTest.promiseFocus(window);
+ await promiseAllButPrimaryWindowClosed();
+ await BrowserTestUtils.switchTab(gBrowser, gInitialTab);
+ await closeFirefoxViewTab(window);
+
+ // clean up extra tabs
+ while (gBrowser.tabs.length > 1) {
+ BrowserTestUtils.removeTab(gBrowser.tabs.at(-1));
+ }
+
+ is(
+ BrowserWindowTracker.orderedWindows.length,
+ 1,
+ "One window at the end of test cleanup"
+ );
+ Assert.deepEqual(
+ gBrowser.tabs.map(tab => tab.linkedBrowser.currentURI.spec),
+ [gInitialTabURL],
+ "One about:blank tab open at the end up test cleanup"
+ );
+}
+
+add_task(async function toggle_show_more_link() {
+ const tabEntry = url => ({
+ entries: [{ url, triggeringPrincipal_base64 }],
+ });
+ const NUMBER_OF_WINDOWS = 4;
+ const NUMBER_OF_TABS = 42;
+ const browserState = { windows: [] };
+ for (let windowIndex = 0; windowIndex < NUMBER_OF_WINDOWS; windowIndex++) {
+ const winData = { tabs: [] };
+ let tabCount = windowIndex == NUMBER_OF_WINDOWS - 1 ? NUMBER_OF_TABS : 1;
+ for (let i = 0; i < tabCount; i++) {
+ winData.tabs.push(tabEntry(`data:,Window${windowIndex}-Tab${i}`));
+ }
+ winData.selected = winData.tabs.length;
+ browserState.windows.push(winData);
+ }
+ // use Session restore to batch-open windows and tabs
+ info(`Restoring to browserState: ${JSON.stringify(browserState, null, 2)}`);
+ await SessionStoreTestUtils.promiseBrowserState(browserState);
+ info("Windows and tabs opened, waiting for readyWindowsPromise");
+ await NonPrivateTabs.readyWindowsPromise;
+ info("readyWindowsPromise resolved");
+
+ // restoring this state requires an update to the initial tab globals
+ // so cleanup expects the right thing
+ gInitialTab = gBrowser.selectedTab;
+ gInitialTabURL = gBrowser.selectedBrowser.currentURI.spec;
+
+ const windows = Array.from(Services.wm.getEnumerator("navigator:browser"));
+ is(windows.length, NUMBER_OF_WINDOWS, "There are four browser windows.");
+
+ const tab = (win = window) => {
+ info("Tab");
+ EventUtils.synthesizeKey("KEY_Tab", {}, win);
+ };
+
+ const enter = (win = window) => {
+ info("Enter");
+ EventUtils.synthesizeKey("KEY_Enter", {}, win);
+ };
+
+ let lastCard;
+
+ await openFirefoxViewTab(window).then(async viewTab => {
+ const browser = viewTab.linkedBrowser;
+ await navigateToOpenTabs(browser);
+ const openTabs = getOpenTabsComponent(browser);
+ await openTabs.updateComplete;
+
+ let cards;
+ info(`Waiting for ${NUMBER_OF_WINDOWS} of window cards`);
+ await BrowserTestUtils.waitForCondition(() => {
+ cards = getOpenTabsCards(openTabs);
+ return cards.length == NUMBER_OF_WINDOWS;
+ });
+ is(cards.length, NUMBER_OF_WINDOWS, "There are four windows.");
+ lastCard = cards[NUMBER_OF_WINDOWS - 1];
+
+ Assert.less(
+ (await getTabRowsForCard(lastCard)).length,
+ NUMBER_OF_TABS,
+ "Not all tabs are shown yet."
+ );
+ info("Toggle the Show More link.");
+ lastCard.shadowRoot.querySelector("div[slot=footer]").click();
+ await BrowserTestUtils.waitForMutationCondition(
+ lastCard.shadowRoot,
+ { childList: true, subtree: true },
+ async () => (await getTabRowsForCard(lastCard)).length === NUMBER_OF_TABS
+ );
+
+ info("Toggle the Show Less link.");
+ lastCard.shadowRoot.querySelector("div[slot=footer]").click();
+ await BrowserTestUtils.waitForMutationCondition(
+ lastCard.shadowRoot,
+ { childList: true, subtree: true },
+ async () => (await getTabRowsForCard(lastCard)).length < NUMBER_OF_TABS
+ );
+
+ // Setting this pref allows the test to run as expected with a keyboard on MacOS
+ await SpecialPowers.pushPrefEnv({
+ set: [["accessibility.tabfocus", 7]],
+ });
+
+ info("Toggle the Show More link with keyboard.");
+ lastCard.shadowRoot.querySelector("card-container").summaryEl.focus();
+ // Tab to first item in the list
+ tab();
+ // Tab to the footer
+ tab();
+ enter();
+ await BrowserTestUtils.waitForMutationCondition(
+ lastCard.shadowRoot,
+ { childList: true, subtree: true },
+ async () => (await getTabRowsForCard(lastCard)).length === NUMBER_OF_TABS
+ );
+
+ info("Toggle the Show Less link with keyboard.");
+ lastCard.shadowRoot.querySelector("card-container").summaryEl.focus();
+ // Tab to first item in the list
+ tab();
+ // Tab to the footer
+ tab();
+ enter();
+ await BrowserTestUtils.waitForMutationCondition(
+ lastCard.shadowRoot,
+ { childList: true, subtree: true },
+ async () => (await getTabRowsForCard(lastCard)).length < NUMBER_OF_TABS
+ );
+
+ await SpecialPowers.popPrefEnv();
+ });
+ await cleanup();
+});
diff --git a/browser/components/firefoxview/tests/browser/browser_opentabs_pinned_tabs.js b/browser/components/firefoxview/tests/browser/browser_opentabs_pinned_tabs.js
new file mode 100644
index 0000000000..d74812bca5
--- /dev/null
+++ b/browser/components/firefoxview/tests/browser/browser_opentabs_pinned_tabs.js
@@ -0,0 +1,481 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let pageWithAlert =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/browser/browser/base/content/test/tabPrompts/openPromptOffTimeout.html";
+let pageWithSound =
+ "http://mochi.test:8888/browser/dom/base/test/file_audioLoop.html";
+
+function cleanup() {
+ // Cleanup
+ while (gBrowser.tabs.length > 1) {
+ BrowserTestUtils.removeTab(gBrowser.tabs[1]);
+ }
+}
+
+const arrowDown = async tabList => {
+ info("Arrow down");
+ EventUtils.synthesizeKey("KEY_ArrowDown", {});
+ await tabList.getUpdateComplete();
+};
+const arrowUp = async tabList => {
+ info("Arrow up");
+ EventUtils.synthesizeKey("KEY_ArrowUp", {});
+ await tabList.getUpdateComplete();
+};
+const arrowRight = async tabList => {
+ info("Arrow right");
+ EventUtils.synthesizeKey("KEY_ArrowRight", {});
+ await tabList.getUpdateComplete();
+};
+const arrowLeft = async tabList => {
+ info("Arrow left");
+ EventUtils.synthesizeKey("KEY_ArrowLeft", {});
+ await tabList.getUpdateComplete();
+};
+
+add_task(async function test_pin_unpin_open_tab() {
+ await withFirefoxView({}, async browser => {
+ const { document } = browser.contentWindow;
+ await navigateToViewAndWait(document, "opentabs");
+
+ let openTabs = document.querySelector("view-opentabs[name=opentabs]");
+ await openTabs.updateComplete;
+ await TestUtils.waitForCondition(
+ () => openTabs.viewCards[0].tabList.rowEls.length
+ );
+ await openTabs.openTabsTarget.readyWindowsPromise;
+ let card = openTabs.viewCards[0];
+ let openTabEl = card.tabList.rowEls[0];
+ let tabChangeRaised = BrowserTestUtils.waitForEvent(
+ NonPrivateTabs,
+ "TabChange"
+ );
+
+ // Pin tab
+ EventUtils.synthesizeMouseAtCenter(
+ openTabEl.secondaryButtonEl,
+ {},
+ content
+ );
+ await TestUtils.waitForCondition(() => card.tabContextMenu.panelList);
+ let panelList = card.tabContextMenu.panelList;
+ await BrowserTestUtils.waitForEvent(panelList, "shown");
+ info("The context menu is shown when clicking the tab's 'more' button");
+
+ let pinTabPanelItem = panelList.querySelector(
+ "panel-item[data-l10n-id=fxviewtabrow-pin-tab]"
+ );
+
+ await clearAllParentTelemetryEvents();
+ let contextMenuEvent = [
+ [
+ "firefoxview_next",
+ "context_menu",
+ "tabs",
+ undefined,
+ { menu_action: "pin-tab", data_type: "opentabs" },
+ ],
+ ];
+ tabChangeRaised = BrowserTestUtils.waitForEvent(
+ NonPrivateTabs,
+ "TabChange"
+ );
+
+ // Unpin tab
+ EventUtils.synthesizeMouseAtCenter(pinTabPanelItem, {}, content);
+ info("Pin Tab context menu option clicked.");
+
+ await tabChangeRaised;
+ await openTabs.updateComplete;
+
+ let pinnedTab = card.tabList.rowEls[0];
+ await TestUtils.waitForCondition(() =>
+ pinnedTab.indicators.includes("pinned")
+ );
+
+ // Check aria roles
+ let listWrapper = card.tabList.shadowRoot.querySelector(".fxview-tab-list");
+ ok(
+ Array.from(listWrapper.classList).includes("pinned"),
+ "The tab list has the 'pinned' class as expected."
+ );
+
+ Assert.strictEqual(
+ listWrapper.getAttribute("role"),
+ "tablist",
+ "The list wrapper has an aria-role of 'tablist'"
+ );
+ Assert.strictEqual(
+ pinnedTab.pinnedTabButtonEl.getAttribute("role"),
+ "tab",
+ "The pinned tab's button element has a role of 'tab'"
+ );
+
+ // Open context menu
+ EventUtils.synthesizeMouseAtCenter(
+ pinnedTab,
+ { type: "contextmenu" },
+ content
+ );
+ await TestUtils.waitForCondition(() => card.tabContextMenu.panelList);
+ panelList = card.tabContextMenu.panelList;
+ await BrowserTestUtils.waitForEvent(panelList, "shown");
+ info("The context menu is shown when right clicking the pinned tab");
+
+ let unpinTabPanelItem = panelList.querySelector(
+ "panel-item[data-l10n-id=fxviewtabrow-unpin-tab]"
+ );
+
+ await clearAllParentTelemetryEvents();
+ contextMenuEvent = [
+ [
+ "firefoxview_next",
+ "context_menu",
+ "tabs",
+ undefined,
+ { menu_action: "unpin-tab", data_type: "opentabs" },
+ ],
+ ];
+ tabChangeRaised = BrowserTestUtils.waitForEvent(
+ NonPrivateTabs,
+ "TabChange"
+ );
+
+ // Unpin tab
+ EventUtils.synthesizeMouseAtCenter(unpinTabPanelItem, {}, content);
+ info("Unpin Tab context menu option clicked.");
+
+ await tabChangeRaised;
+ await openTabs.updateComplete;
+ await telemetryEvent(contextMenuEvent);
+ });
+ cleanup();
+});
+
+add_task(async function test_indicator_pinned_tabs_with_keyboard() {
+ await add_new_tab(URLs[0]);
+ await add_new_tab(URLs[1]);
+ await add_new_tab(pageWithSound);
+ await withFirefoxView({}, async browser => {
+ const { document } = browser.contentWindow;
+ await navigateToViewAndWait(document, "opentabs");
+
+ let openTabs = document.querySelector("view-opentabs[name=opentabs]");
+ await openTabs.updateComplete;
+ await TestUtils.waitForCondition(
+ () => openTabs.viewCards[0].tabList.rowEls.length
+ );
+ await openTabs.openTabsTarget.readyWindowsPromise;
+ setSortOption(openTabs, "tabStripOrder");
+ let card = openTabs.viewCards[0];
+
+ let openedTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ pageWithAlert,
+ true
+ );
+ let openedTabGotAttentionPromise = BrowserTestUtils.waitForAttribute(
+ "attention",
+ openedTab
+ );
+
+ await switchToFxViewTab();
+
+ await openedTabGotAttentionPromise;
+
+ let tabChangeRaised = BrowserTestUtils.waitForEvent(
+ NonPrivateTabs,
+ "TabChange"
+ );
+
+ // Pin 2 of 5 tabs
+ browser.ownerGlobal.gBrowser.tabs.forEach((tab, i) => {
+ if (i > 2) {
+ browser.ownerGlobal.gBrowser.pinTab(tab);
+ }
+ });
+
+ await tabChangeRaised;
+ await openTabs.updateComplete;
+
+ let soundPlayingPinnedTab = card.tabList.rowEls[0];
+ let attentionPinnedTab = card.tabList.rowEls[1];
+ let firstUnpinnedTab = card.tabList.rowEls[2];
+ let secondUnpinnedTab = card.tabList.rowEls[3];
+
+ // Check soundplaying indicator
+ ok(
+ soundPlayingPinnedTab.indicators.includes("pinned") &&
+ soundPlayingPinnedTab.indicators.includes("soundplaying") &&
+ soundPlayingPinnedTab.mediaButtonEl,
+ "The first pinned tab has the 'sound playing' indicator."
+ );
+
+ soundPlayingPinnedTab.pinnedTabButtonEl.focus();
+ ok(
+ isActiveElement(soundPlayingPinnedTab.pinnedTabButtonEl),
+ "Focus should be on the first pinned tab's button element."
+ );
+ info("First pinned tab has focus");
+
+ // Test mute/unmute
+ EventUtils.synthesizeKey("m", { ctrlKey: true });
+ await TestUtils.waitForCondition(() =>
+ soundPlayingPinnedTab.indicators.includes("muted")
+ );
+ EventUtils.synthesizeKey("m", { ctrlKey: true });
+ await TestUtils.waitForCondition(
+ () => !soundPlayingPinnedTab.indicators.includes("muted")
+ );
+
+ await arrowRight(card.tabList);
+ ok(
+ isActiveElement(attentionPinnedTab.pinnedTabButtonEl),
+ "Focus should be on the second pinned tab's button element."
+ );
+
+ // Check notification dot indicator
+ ok(
+ attentionPinnedTab.indicators.includes("pinned") &&
+ attentionPinnedTab.indicators.includes("attention") &&
+ Array.from(
+ attentionPinnedTab.shadowRoot.querySelector(
+ ".fxview-tab-row-favicon-wrapper"
+ ).classList
+ ).includes("attention"),
+ "The second pinned tab has the 'attention' indicator."
+ );
+
+ await arrowDown(card.tabList);
+ ok(
+ isActiveElement(firstUnpinnedTab.mainEl),
+ "Focus should be on the first unpinned tab's main/link element."
+ );
+
+ await arrowRight(card.tabList);
+ ok(
+ isActiveElement(firstUnpinnedTab.secondaryButtonEl),
+ "Focus should be on the first unpinned tab's secondary/more button element."
+ );
+
+ await arrowUp(card.tabList);
+ ok(
+ isActiveElement(attentionPinnedTab.pinnedTabButtonEl),
+ "Focus should be on the second pinned tab's button element."
+ );
+
+ await arrowRight(card.tabList);
+ ok(
+ isActiveElement(firstUnpinnedTab.secondaryButtonEl),
+ "Focus should be on the first unpinned tab's secondary/more button element."
+ );
+
+ await arrowUp(card.tabList);
+ ok(
+ isActiveElement(attentionPinnedTab.pinnedTabButtonEl),
+ "Focus should be on the second pinned tab's button element."
+ );
+
+ await arrowLeft(card.tabList);
+ ok(
+ isActiveElement(soundPlayingPinnedTab.pinnedTabButtonEl),
+ "Focus should be on the first pinned tab's button element."
+ );
+
+ await arrowDown(card.tabList);
+ ok(
+ isActiveElement(firstUnpinnedTab.secondaryButtonEl),
+ "Focus should be on the first unpinned tab's secondary/more button element."
+ );
+
+ await arrowDown(card.tabList);
+ ok(
+ isActiveElement(secondUnpinnedTab.secondaryButtonEl),
+ "Focus should be on the second unpinned tab's secondary/more button element."
+ );
+
+ // Switch back to other tab to close prompt before cleanup
+ await BrowserTestUtils.switchTab(gBrowser, openedTab);
+ EventUtils.synthesizeKey("KEY_Enter", {});
+ });
+ cleanup();
+});
+
+add_task(async function test_mute_unmute_pinned_tab() {
+ await withFirefoxView({}, async browser => {
+ const { document } = browser.contentWindow;
+ await navigateToViewAndWait(document, "opentabs");
+
+ let openTabs = document.querySelector("view-opentabs[name=opentabs]");
+ await openTabs.updateComplete;
+ await TestUtils.waitForCondition(
+ () => openTabs.viewCards[0].tabList.rowEls.length
+ );
+ await openTabs.openTabsTarget.readyWindowsPromise;
+ let card = openTabs.viewCards[0];
+ let openTabEl = card.tabList.rowEls[0];
+ let tabChangeRaised = BrowserTestUtils.waitForEvent(
+ NonPrivateTabs,
+ "TabChange"
+ );
+
+ // Mute tab
+ // We intentionally turn off this a11y check, because the following click
+ // is purposefully targeting a not focusable button within a pinned tab
+ // control. A keyboard-only user could mute/unmute this pinned tab via the
+ // context menu, while we do not want to create an additional, unnecessary
+ // tabstop for this control, therefore this rule check shall be ignored by
+ // a11y_checks suite.
+ AccessibilityUtils.setEnv({ focusableRule: false });
+ EventUtils.synthesizeMouseAtCenter(openTabEl.mediaButtonEl, {}, content);
+ AccessibilityUtils.resetEnv();
+ info("Mute Tab button clicked.");
+
+ tabChangeRaised = BrowserTestUtils.waitForEvent(
+ NonPrivateTabs,
+ "TabChange"
+ );
+
+ await tabChangeRaised;
+ await openTabs.updateComplete;
+
+ let mutedTab = card.tabList.rowEls[0];
+ await TestUtils.waitForCondition(() =>
+ mutedTab.indicators.includes("muted")
+ );
+
+ // Unmute tab
+ // We intentionally turn off this a11y check, because the following click
+ // is purposefully targeting a not focusable button within a pinned tab
+ // control. A keyboard-only user could mute/unmute this pinned tab via the
+ // context menu, while we do not want to create an additional, unnecessary
+ // tabstop for this control, therefore this rule check shall be ignored by
+ // a11y_checks suite.
+ AccessibilityUtils.setEnv({ focusableRule: false });
+ EventUtils.synthesizeMouseAtCenter(openTabEl.mediaButtonEl, {}, content);
+ AccessibilityUtils.resetEnv();
+ info("Unmute Tab button clicked.");
+
+ tabChangeRaised = BrowserTestUtils.waitForEvent(
+ NonPrivateTabs,
+ "TabChange"
+ );
+
+ await tabChangeRaised;
+ await openTabs.updateComplete;
+
+ let unmutedTab = card.tabList.rowEls[0];
+ await TestUtils.waitForCondition(
+ () => !unmutedTab.indicators.includes("muted")
+ );
+ });
+ cleanup();
+});
+
+add_task(async function test_mute_unmute_with_context_menu() {
+ await withFirefoxView({}, async browser => {
+ const { document } = browser.contentWindow;
+ await navigateToViewAndWait(document, "opentabs");
+
+ let openTabs = document.querySelector("view-opentabs[name=opentabs]");
+ await openTabs.updateComplete;
+ await TestUtils.waitForCondition(
+ () => openTabs.viewCards[0].tabList.rowEls.length
+ );
+ await openTabs.openTabsTarget.readyWindowsPromise;
+ let card = openTabs.viewCards[0];
+ let openTabEl = card.tabList.rowEls[0];
+ let tabChangeRaised = BrowserTestUtils.waitForEvent(
+ NonPrivateTabs,
+ "TabChange"
+ );
+
+ // Mute tab
+ EventUtils.synthesizeMouseAtCenter(
+ openTabEl.pinnedTabButtonEl,
+ { type: "contextmenu" },
+ content
+ );
+ await TestUtils.waitForCondition(() => card.tabContextMenu.panelList);
+ let panelList = card.tabContextMenu.panelList;
+ await BrowserTestUtils.waitForEvent(panelList, "shown");
+ info("The context menu is shown when clicking the tab's 'more' button");
+
+ let pinTabPanelItem = panelList.querySelector(
+ "panel-item[data-l10n-id=fxviewtabrow-mute-tab]"
+ );
+
+ await clearAllParentTelemetryEvents();
+ let contextMenuEvent = [
+ [
+ "firefoxview_next",
+ "context_menu",
+ "tabs",
+ undefined,
+ { menu_action: "mute-tab", data_type: "opentabs" },
+ ],
+ ];
+ tabChangeRaised = BrowserTestUtils.waitForEvent(
+ NonPrivateTabs,
+ "TabChange"
+ );
+
+ // Mute tab
+ EventUtils.synthesizeMouseAtCenter(pinTabPanelItem, {}, content);
+ info("Mute Tab context menu option clicked.");
+
+ await tabChangeRaised;
+ await openTabs.updateComplete;
+
+ let mutedTab = card.tabList.rowEls[0];
+ await TestUtils.waitForCondition(() =>
+ mutedTab.indicators.includes("muted")
+ );
+
+ // Open context menu
+ EventUtils.synthesizeMouseAtCenter(
+ mutedTab,
+ { type: "contextmenu" },
+ content
+ );
+ await TestUtils.waitForCondition(() => card.tabContextMenu.panelList);
+ panelList = card.tabContextMenu.panelList;
+ await BrowserTestUtils.waitForEvent(panelList, "shown");
+ info("The context menu is shown when right clicking the pinned tab");
+
+ let unmuteTabPanelItem = panelList.querySelector(
+ "panel-item[data-l10n-id=fxviewtabrow-unmute-tab]"
+ );
+
+ await clearAllParentTelemetryEvents();
+ contextMenuEvent = [
+ [
+ "firefoxview_next",
+ "context_menu",
+ "tabs",
+ undefined,
+ { menu_action: "unmute-tab", data_type: "opentabs" },
+ ],
+ ];
+ tabChangeRaised = BrowserTestUtils.waitForEvent(
+ NonPrivateTabs,
+ "TabChange"
+ );
+
+ // Unpin tab
+ EventUtils.synthesizeMouseAtCenter(unmuteTabPanelItem, {}, content);
+ info("Unmute Tab context menu option clicked.");
+
+ await tabChangeRaised;
+ await openTabs.updateComplete;
+ await telemetryEvent(contextMenuEvent);
+
+ let unmutedTab = card.tabList.rowEls[0];
+ await TestUtils.waitForCondition(
+ () => !unmutedTab.indicators.includes("muted")
+ );
+ });
+ cleanup();
+});
diff --git a/browser/components/firefoxview/tests/browser/browser_opentabs_recency.js b/browser/components/firefoxview/tests/browser/browser_opentabs_recency.js
index e5beb4700a..ee3f9981e1 100644
--- a/browser/components/firefoxview/tests/browser/browser_opentabs_recency.js
+++ b/browser/components/firefoxview/tests/browser/browser_opentabs_recency.js
@@ -13,9 +13,6 @@ const tabURL4 = "data:,Tab4";
let gInitialTab;
let gInitialTabURL;
-const { NonPrivateTabs } = ChromeUtils.importESModule(
- "resource:///modules/OpenTabs.sys.mjs"
-);
add_setup(function () {
gInitialTab = gBrowser.selectedTab;
@@ -158,12 +155,12 @@ function getOpenTabsComponent(browser) {
async function checkTabList(browser, expected) {
const tabsView = getOpenTabsComponent(browser);
- const openTabsCard = tabsView.shadowRoot.querySelector("view-opentabs-card");
- await tabsView.getUpdateComplete();
- const tabList = openTabsCard.shadowRoot.querySelector("fxview-tab-list");
- Assert.ok(tabList, "Found the tab list element");
- await TestUtils.waitForCondition(() => tabList.rowEls.length);
- let actual = Array.from(tabList.rowEls).map(row => row.url);
+ const [openTabsCard] = getOpenTabsCards(tabsView);
+ await openTabsCard.updateComplete;
+
+ const tabListRows = await getTabRowsForCard(openTabsCard);
+ Assert.ok(tabListRows, "Found the tab list element");
+ let actual = Array.from(tabListRows).map(row => row.url);
Assert.deepEqual(
actual,
expected,
@@ -255,7 +252,7 @@ add_task(async function test_multiple_window_tabs() {
NonPrivateTabs,
"TabRecencyChange"
);
- await SimpleTest.promiseFocus(win1);
+ await switchToWindow(win1);
await tabChangeRaised;
Assert.equal(
tabUrl(win1.gBrowser.selectedTab),
@@ -308,17 +305,20 @@ add_task(async function test_windows_activation() {
await openFirefoxViewTab(win1).then(tab => (fxViewTab = tab));
const win2 = await BrowserTestUtils.openNewBrowserWindow();
+ await switchToWindow(win2);
await prepareOpenTabs([tabURL2], win2);
const win3 = await BrowserTestUtils.openNewBrowserWindow();
+ await switchToWindow(win3);
await prepareOpenTabs([tabURL3], win3);
- await tabChangeRaised;
tabChangeRaised = BrowserTestUtils.waitForEvent(
NonPrivateTabs,
"TabRecencyChange"
);
- await SimpleTest.promiseFocus(win1);
+ info("Switching back to win 1");
+ await switchToWindow(win1);
+ info("Waiting for tabChangeRaised to resolve");
await tabChangeRaised;
const browser = fxViewTab.linkedBrowser;
@@ -329,7 +329,7 @@ add_task(async function test_windows_activation() {
NonPrivateTabs,
"TabRecencyChange"
);
- await SimpleTest.promiseFocus(win2);
+ await switchToWindow(win2);
await tabChangeRaised;
await checkTabList(browser, [tabURL2, tabURL3, tabURL1]);
await cleanup(win2, win3);
@@ -341,6 +341,7 @@ add_task(async function test_minimize_restore_windows() {
await prepareOpenTabs([tabURL1, tabURL2]);
const win2 = await BrowserTestUtils.openNewBrowserWindow();
await prepareOpenTabs([tabURL3, tabURL4], win2);
+ await NonPrivateTabs.readyWindowsPromise;
// to avoid confusing the results by activating different windows,
// check fxview in the current window - which is win2
@@ -374,7 +375,7 @@ add_task(async function test_minimize_restore_windows() {
);
await minimizeWindow(win2);
info("Focusing win1, where tab2 is selected - making it most recent");
- await SimpleTest.promiseFocus(win1);
+ await switchToWindow(win1);
await tabChangeRaised;
Assert.equal(
@@ -395,7 +396,7 @@ add_task(async function test_minimize_restore_windows() {
"TabRecencyChange"
);
await restoreWindow(win2);
- await SimpleTest.promiseFocus(win2);
+ await switchToWindow(win2);
await tabChangeRaised;
info(
diff --git a/browser/components/firefoxview/tests/browser/browser_opentabs_search.js b/browser/components/firefoxview/tests/browser/browser_opentabs_search.js
new file mode 100644
index 0000000000..173bf1a623
--- /dev/null
+++ b/browser/components/firefoxview/tests/browser/browser_opentabs_search.js
@@ -0,0 +1,161 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_URL = "about:robots";
+let gInitialTab;
+let gInitialTabURL;
+
+add_setup(function () {
+ gInitialTab = gBrowser.selectedTab;
+ gInitialTabURL = gBrowser.selectedBrowser.currentURI.spec;
+});
+
+async function cleanup() {
+ await SimpleTest.promiseFocus(window);
+ await promiseAllButPrimaryWindowClosed();
+ await BrowserTestUtils.switchTab(gBrowser, gInitialTab);
+ await closeFirefoxViewTab(window);
+
+ // clean up extra tabs
+ while (gBrowser.tabs.length > 1) {
+ BrowserTestUtils.removeTab(gBrowser.tabs.at(-1));
+ }
+}
+
+add_task(async function search_open_tabs() {
+ // Open a new window and navigate to TEST_URL. Then, when we search for
+ // TEST_URL, it should show a search result in the new window's card.
+ const win2 = await BrowserTestUtils.openNewBrowserWindow();
+ await switchToWindow(win2);
+ await NonPrivateTabs.readyWindowsPromise;
+ await BrowserTestUtils.openNewForegroundTab(win2.gBrowser, TEST_URL);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.firefox-view.search.enabled", true]],
+ });
+ await openFirefoxViewTab(window).then(async viewTab => {
+ const browser = viewTab.linkedBrowser;
+ await navigateToOpenTabs(browser);
+ const openTabs = getOpenTabsComponent(browser);
+ await openTabs.updateComplete;
+
+ const cards = getOpenTabsCards(openTabs);
+ is(cards.length, 2, "There are two windows.");
+ const winTabs = await getTabRowsForCard(cards[0]);
+ const newWinTabs = await getTabRowsForCard(cards[1]);
+
+ info("Input a search query.");
+ EventUtils.synthesizeMouseAtCenter(openTabs.searchTextbox, {}, content);
+ EventUtils.sendString(TEST_URL, content);
+ await TestUtils.waitForCondition(
+ () => openTabs.viewCards[0].tabList.rowEls.length === 0,
+ "There are no matching search results in the original window."
+ );
+ await TestUtils.waitForCondition(
+ () => openTabs.viewCards[1].tabList.rowEls.length === 1,
+ "There is one matching search result in the new window."
+ );
+
+ info("Clear the search query.");
+ EventUtils.synthesizeMouseAtCenter(
+ openTabs.searchTextbox.clearButton,
+ {},
+ content
+ );
+ await TestUtils.waitForCondition(
+ () => openTabs.viewCards[0].tabList.rowEls.length === winTabs.length,
+ "The original window's list is restored."
+ );
+ await TestUtils.waitForCondition(
+ () => openTabs.viewCards[1].tabList.rowEls.length === newWinTabs.length,
+ "The new window's list is restored."
+ );
+ openTabs.searchTextbox.blur();
+
+ info("Input a search query with keyboard.");
+ EventUtils.synthesizeKey("f", { accelKey: true }, content);
+ EventUtils.sendString(TEST_URL, content);
+ await TestUtils.waitForCondition(
+ () => openTabs.viewCards[0].tabList.rowEls.length === 0,
+ "There are no matching search results in the original window."
+ );
+ await TestUtils.waitForCondition(
+ () => openTabs.viewCards[1].tabList.rowEls.length === 1,
+ "There is one matching search result in the new window."
+ );
+
+ info("Clear the search query with keyboard.");
+ is(
+ openTabs.shadowRoot.activeElement,
+ openTabs.searchTextbox,
+ "Search input is focused"
+ );
+ EventUtils.synthesizeKey("KEY_Tab", {}, content);
+ ok(
+ openTabs.searchTextbox.clearButton.matches(":focus-visible"),
+ "Clear Search button is focused"
+ );
+ EventUtils.synthesizeKey("KEY_Enter", {}, content);
+ await TestUtils.waitForCondition(
+ () => openTabs.viewCards[0].tabList.rowEls.length === winTabs.length,
+ "The original window's list is restored."
+ );
+ await TestUtils.waitForCondition(
+ () => openTabs.viewCards[1].tabList.rowEls.length === newWinTabs.length,
+ "The new window's list is restored."
+ );
+ });
+
+ await SpecialPowers.popPrefEnv();
+ await cleanup();
+});
+
+add_task(async function search_open_tabs_recent_browsing() {
+ const NUMBER_OF_TABS = 6;
+ const win2 = await BrowserTestUtils.openNewBrowserWindow();
+ await switchToWindow(win2);
+ await NonPrivateTabs.readyWindowsPromise;
+
+ for (let i = 0; i < NUMBER_OF_TABS; i++) {
+ await BrowserTestUtils.openNewForegroundTab(win2.gBrowser, TEST_URL);
+ }
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.firefox-view.search.enabled", true]],
+ });
+ await openFirefoxViewTab(window).then(async viewTab => {
+ const browser = viewTab.linkedBrowser;
+ await navigateToViewAndWait(browser.contentDocument, "recentbrowsing");
+ const recentBrowsing = browser.contentDocument.querySelector(
+ "view-recentbrowsing"
+ );
+
+ info("Input a search query.");
+ EventUtils.synthesizeMouseAtCenter(
+ recentBrowsing.searchTextbox,
+ {},
+ content
+ );
+ EventUtils.sendString(TEST_URL, content);
+ const slot = recentBrowsing.querySelector("[slot='opentabs']");
+ await TestUtils.waitForCondition(
+ () => slot.viewCards[0].tabList.rowEls.length === 5,
+ "Not all search results are shown yet."
+ );
+
+ info("Click the Show All link.");
+ const showAllLink = await TestUtils.waitForCondition(() => {
+ const elt = slot.viewCards[0].shadowRoot.querySelector(
+ "[data-l10n-id='firefoxview-show-all']"
+ );
+ EventUtils.synthesizeMouseAtCenter(elt, {}, content);
+ if (slot.viewCards[0].tabList.rowEls.length === NUMBER_OF_TABS) {
+ return elt;
+ }
+ return false;
+ }, "All search results are shown.");
+ is(showAllLink.role, "link", "The show all control is a link.");
+ ok(BrowserTestUtils.isHidden(showAllLink), "The show all link is hidden.");
+ });
+ await SpecialPowers.popPrefEnv();
+ await cleanup();
+});
diff --git a/browser/components/firefoxview/tests/browser/browser_opentabs_tab_indicators.js b/browser/components/firefoxview/tests/browser/browser_opentabs_tab_indicators.js
index 1375052125..78fab976ed 100644
--- a/browser/components/firefoxview/tests/browser/browser_opentabs_tab_indicators.js
+++ b/browser/components/firefoxview/tests/browser/browser_opentabs_tab_indicators.js
@@ -1,9 +1,7 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
-const { NonPrivateTabs } = ChromeUtils.importESModule(
- "resource:///modules/OpenTabs.sys.mjs"
-);
+requestLongerTimeout(2);
let pageWithAlert =
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
@@ -11,18 +9,12 @@ let pageWithAlert =
let pageWithSound =
"http://mochi.test:8888/browser/dom/base/test/file_audioLoop.html";
-function cleanup() {
- // Cleanup
- while (gBrowser.tabs.length > 1) {
- BrowserTestUtils.removeTab(gBrowser.tabs[0]);
- }
-}
-
add_task(async function test_notification_dot_indicator() {
+ clearHistory();
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
let win = browser.ownerGlobal;
- await navigateToCategoryAndWait(document, "opentabs");
+ await navigateToViewAndWait(document, "opentabs");
// load page that opens prompt when page is hidden
let openedTab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
@@ -47,9 +39,10 @@ add_task(async function test_notification_dot_indicator() {
await tabChangeRaised;
await openTabs.updateComplete;
- await TestUtils.waitForCondition(
- () => openTabs.viewCards[0].tabList.rowEls[1].attention,
- "The opened tab doesn't have the attention property, so no notification dot is shown."
+ await TestUtils.waitForCondition(() =>
+ Array.from(openTabs.viewCards[0].tabList.rowEls).some(rowEl => {
+ return rowEl.indicators.includes("attention");
+ })
);
info("The newly opened tab has a notification dot.");
@@ -58,11 +51,12 @@ add_task(async function test_notification_dot_indicator() {
await BrowserTestUtils.switchTab(gBrowser, openedTab);
EventUtils.synthesizeKey("KEY_Enter", {}, win);
- cleanup();
+ cleanupTabs();
});
});
add_task(async function test_container_indicator() {
+ clearHistory();
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
let win = browser.ownerGlobal;
@@ -79,7 +73,7 @@ add_task(async function test_container_indicator() {
URLs[0]
);
- await navigateToCategoryAndWait(document, "opentabs");
+ await navigateToViewAndWait(document, "opentabs");
let openTabs = document.querySelector("view-opentabs[name=opentabs]");
@@ -95,10 +89,14 @@ add_task(async function test_container_indicator() {
);
info("openTabs component has finished updating.");
- let containerTabElem = openTabs.viewCards[0].tabList.rowEls[1];
+ let containerTabElem;
await TestUtils.waitForCondition(
- () => containerTabElem.containerObj,
+ () =>
+ Array.from(openTabs.viewCards[0].tabList.rowEls).some(rowEl => {
+ containerTabElem = rowEl;
+ return rowEl.containerObj;
+ }),
"The container tab element isn't marked in Fx View."
);
@@ -111,14 +109,15 @@ add_task(async function test_container_indicator() {
info("The newly opened tab is marked as a container tab.");
- cleanup();
+ cleanupTabs();
});
});
add_task(async function test_sound_playing_muted_indicator() {
+ clearHistory();
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "opentabs");
+ await navigateToViewAndWait(document, "opentabs");
// Load a page in a container tab
let soundTab = await BrowserTestUtils.openNewForegroundTab(
@@ -146,9 +145,13 @@ add_task(async function test_sound_playing_muted_indicator() {
"The tab list hasn't rendered."
);
- let soundPlayingTabElem = openTabs.viewCards[0].tabList.rowEls[1];
-
- await TestUtils.waitForCondition(() => soundPlayingTabElem.soundPlaying);
+ let soundPlayingTabElem;
+ await TestUtils.waitForCondition(() =>
+ Array.from(openTabs.viewCards[0].tabList.rowEls).some(rowEl => {
+ soundPlayingTabElem = rowEl;
+ return rowEl.indicators.includes("soundplaying");
+ })
+ );
ok(
soundPlayingTabElem.mediaButtonEl,
@@ -174,7 +177,9 @@ add_task(async function test_sound_playing_muted_indicator() {
await tabChangeRaised;
await openTabs.updateComplete;
- await TestUtils.waitForCondition(() => soundPlayingTabElem.muted);
+ await TestUtils.waitForCondition(() =>
+ soundPlayingTabElem.indicators.includes("muted")
+ );
ok(
soundPlayingTabElem.mediaButtonEl,
@@ -185,7 +190,9 @@ add_task(async function test_sound_playing_muted_indicator() {
soundTab.toggleMuteAudio();
await tabChangeRaised;
await openTabs.updateComplete;
- await TestUtils.waitForCondition(() => soundPlayingTabElem.soundPlaying);
+ await TestUtils.waitForCondition(() =>
+ soundPlayingTabElem.indicators.includes("soundplaying")
+ );
ok(
soundPlayingTabElem.mediaButtonEl,
@@ -195,13 +202,80 @@ add_task(async function test_sound_playing_muted_indicator() {
soundTab.toggleMuteAudio();
await tabChangeRaised;
await openTabs.updateComplete;
- await TestUtils.waitForCondition(() => soundPlayingTabElem.muted);
+ await TestUtils.waitForCondition(() =>
+ soundPlayingTabElem.indicators.includes("muted")
+ );
ok(
soundPlayingTabElem.mediaButtonEl,
"The tab has the unmute button showing."
);
- cleanup();
+ cleanupTabs();
+ });
+});
+
+add_task(async function test_bookmark_indicator() {
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URLs[0]);
+ await withFirefoxView({}, async browser => {
+ const { document } = browser.contentWindow;
+ await navigateToViewAndWait(document, "opentabs");
+ const openTabs = document.querySelector("view-opentabs[name=opentabs]");
+ setSortOption(openTabs, "recency");
+ let rowEl = await TestUtils.waitForCondition(
+ () => openTabs.viewCards[0]?.tabList.rowEls[0]
+ );
+
+ info("Bookmark a tab while Firefox View is active.");
+ let bookmark = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: URLs[0],
+ });
+ await TestUtils.waitForCondition(
+ () => rowEl.shadowRoot.querySelector(".bookmark"),
+ "Tab shows the bookmark star."
+ );
+ await PlacesUtils.bookmarks.update({
+ guid: bookmark.guid,
+ url: URLs[1],
+ });
+ await TestUtils.waitForCondition(
+ () => !rowEl.shadowRoot.querySelector(".bookmark"),
+ "The bookmark star is removed."
+ );
+ bookmark = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: URLs[0],
+ });
+ await TestUtils.waitForCondition(
+ () => rowEl.shadowRoot.querySelector(".bookmark"),
+ "The bookmark star is restored."
+ );
+ await PlacesUtils.bookmarks.remove(bookmark.guid);
+ await TestUtils.waitForCondition(
+ () => !rowEl.shadowRoot.querySelector(".bookmark"),
+ "The bookmark star is removed again."
+ );
+
+ info("Bookmark a tab while Firefox View is inactive.");
+ await BrowserTestUtils.switchTab(gBrowser, tab);
+ bookmark = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: URLs[0],
+ });
+ await switchToFxViewTab();
+ await TestUtils.waitForCondition(
+ () => rowEl.shadowRoot.querySelector(".bookmark"),
+ "Tab shows the bookmark star."
+ );
+ await BrowserTestUtils.switchTab(gBrowser, tab);
+ await PlacesUtils.bookmarks.remove(bookmark.guid);
+ await switchToFxViewTab();
+ await TestUtils.waitForCondition(
+ () => !rowEl.shadowRoot.querySelector(".bookmark"),
+ "The bookmark star is removed."
+ );
});
+ await cleanupTabs();
+ await PlacesUtils.bookmarks.eraseEverything();
});
diff --git a/browser/components/firefoxview/tests/browser/browser_recentlyclosed_firefoxview.js b/browser/components/firefoxview/tests/browser/browser_recentlyclosed_firefoxview.js
index 313d86416e..fcfcf20562 100644
--- a/browser/components/firefoxview/tests/browser/browser_recentlyclosed_firefoxview.js
+++ b/browser/components/firefoxview/tests/browser/browser_recentlyclosed_firefoxview.js
@@ -196,7 +196,7 @@ add_task(async function test_initial_closed_tab() {
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
is(document.location.href, getFirefoxViewURL());
- await navigateToCategoryAndWait(document, "recentlyclosed");
+ await navigateToViewAndWait(document, "recentlyclosed");
let { cleanup } = await prepareSingleClosedTab();
await switchToFxViewTab(window);
let [listItems] = await waitForRecentlyClosedTabsList(document);
@@ -220,7 +220,7 @@ add_task(async function test_list_ordering() {
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
await clearAllParentTelemetryEvents();
- navigateToCategory(document, "recentlyclosed");
+ await navigateToViewAndWait(document, "recentlyclosed");
let [cardMainSlotNode, listItems] = await waitForRecentlyClosedTabsList(
document
);
@@ -248,7 +248,7 @@ add_task(async function test_list_updates() {
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- navigateToCategory(document, "recentlyclosed");
+ await navigateToViewAndWait(document, "recentlyclosed");
let [listElem, listItems] = await waitForRecentlyClosedTabsList(document);
Assert.deepEqual(
@@ -321,7 +321,7 @@ add_task(async function test_restore_tab() {
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- navigateToCategory(document, "recentlyclosed");
+ await navigateToViewAndWait(document, "recentlyclosed");
let [listElem, listItems] = await waitForRecentlyClosedTabsList(document);
Assert.deepEqual(
@@ -365,7 +365,7 @@ add_task(async function test_dismiss_tab() {
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- navigateToCategory(document, "recentlyclosed");
+ await navigateToViewAndWait(document, "recentlyclosed");
let [listElem, listItems] = await waitForRecentlyClosedTabsList(document);
await clearAllParentTelemetryEvents();
@@ -429,7 +429,7 @@ add_task(async function test_empty_states() {
const { document } = browser.contentWindow;
is(document.location.href, "about:firefoxview");
- navigateToCategory(document, "recentlyclosed");
+ await navigateToViewAndWait(document, "recentlyclosed");
let recentlyClosedComponent = document.querySelector(
"view-recentlyclosed:not([slot=recentlyclosed])"
);
@@ -479,7 +479,7 @@ add_task(async function test_observers_removed_when_view_is_hidden() {
await withFirefoxView({}, async function (browser) {
const { document } = browser.contentWindow;
- navigateToCategory(document, "recentlyclosed");
+ await navigateToViewAndWait(document, "recentlyclosed");
const [listElem] = await waitForRecentlyClosedTabsList(document);
is(listElem.rowEls.length, 1);
@@ -510,7 +510,7 @@ add_task(async function test_search() {
let { cleanup, expectedURLs } = await prepareClosedTabs();
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- navigateToCategory(document, "recentlyclosed");
+ await navigateToViewAndWait(document, "recentlyclosed");
const [listElem] = await waitForRecentlyClosedTabsList(document);
const recentlyClosedComponent = document.querySelector(
"view-recentlyclosed:not([slot=recentlyclosed])"
@@ -569,7 +569,7 @@ add_task(async function test_search_recent_browsing() {
const { document } = browser.contentWindow;
info("Input a search query.");
- await navigateToCategoryAndWait(document, "recentbrowsing");
+ await navigateToViewAndWait(document, "recentbrowsing");
const recentBrowsing = document.querySelector("view-recentbrowsing");
EventUtils.synthesizeMouseAtCenter(
recentBrowsing.searchTextbox,
diff --git a/browser/components/firefoxview/tests/browser/browser_syncedtabs_errors_firefoxview.js b/browser/components/firefoxview/tests/browser/browser_syncedtabs_errors_firefoxview.js
index 15dba68551..86e4d9cdee 100644
--- a/browser/components/firefoxview/tests/browser/browser_syncedtabs_errors_firefoxview.js
+++ b/browser/components/firefoxview/tests/browser/browser_syncedtabs_errors_firefoxview.js
@@ -56,7 +56,7 @@ add_task(async function test_network_offline() {
sandbox.spy(TabsSetupFlowManager, "tryToClearError");
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "syncedtabs");
+ await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
Services.obs.notifyObservers(
@@ -112,7 +112,7 @@ add_task(async function test_sync_error() {
const sandbox = await setupWithDesktopDevices();
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "syncedtabs");
+ await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
Services.obs.notifyObservers(null, "weave:service:sync:error");
diff --git a/browser/components/firefoxview/tests/browser/browser_syncedtabs_firefoxview.js b/browser/components/firefoxview/tests/browser/browser_syncedtabs_firefoxview.js
index 8a3c63985b..11f135cd52 100644
--- a/browser/components/firefoxview/tests/browser/browser_syncedtabs_firefoxview.js
+++ b/browser/components/firefoxview/tests/browser/browser_syncedtabs_firefoxview.js
@@ -29,7 +29,7 @@ add_task(async function test_unconfigured_initial_state() {
});
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "syncedtabs");
+ await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector(
@@ -85,7 +85,7 @@ add_task(async function test_signed_in() {
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "syncedtabs");
+ await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector(
@@ -148,7 +148,7 @@ add_task(async function test_no_synced_tabs() {
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "syncedtabs");
+ await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector(
@@ -188,7 +188,7 @@ add_task(async function test_no_error_for_two_desktop() {
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "syncedtabs");
+ await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector(
@@ -232,7 +232,7 @@ add_task(async function test_empty_state() {
await withFirefoxView({ openNewWindow: true }, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "syncedtabs");
+ await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector(
@@ -277,7 +277,7 @@ add_task(async function test_tabs() {
await withFirefoxView({ openNewWindow: true }, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "syncedtabs");
+ await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector(
@@ -365,7 +365,7 @@ add_task(async function test_empty_desktop_same_name() {
await withFirefoxView({ openNewWindow: true }, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "syncedtabs");
+ await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector(
@@ -413,7 +413,7 @@ add_task(async function test_empty_desktop_same_name_three() {
await withFirefoxView({ openNewWindow: true }, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "syncedtabs");
+ await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector(
@@ -459,7 +459,7 @@ add_task(async function search_synced_tabs() {
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "syncedtabs");
+ await navigateToViewAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector(
@@ -666,7 +666,7 @@ add_task(async function search_synced_tabs_recent_browsing() {
});
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- await navigateToCategoryAndWait(document, "recentbrowsing");
+ await navigateToViewAndWait(document, "recentbrowsing");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
const recentBrowsing = document.querySelector("view-recentbrowsing");
diff --git a/browser/components/firefoxview/tests/browser/browser_tab_list_keyboard_navigation.js b/browser/components/firefoxview/tests/browser/browser_tab_list_keyboard_navigation.js
new file mode 100644
index 0000000000..d83c1056e0
--- /dev/null
+++ b/browser/components/firefoxview/tests/browser/browser_tab_list_keyboard_navigation.js
@@ -0,0 +1,334 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(async function test_open_tab_row_navigation() {
+ await withFirefoxView({}, async browser => {
+ const { document } = browser.contentWindow;
+ let win = browser.ownerGlobal;
+
+ await navigateToViewAndWait(document, "opentabs");
+ const openTabs = document.querySelector("view-opentabs[name=opentabs]");
+
+ await TestUtils.waitForCondition(
+ () => openTabs.viewCards[0].tabList?.rowEls.length === 1,
+ "The tab list hasn't rendered"
+ );
+
+ // Focus tab row
+ let tabRow = openTabs.viewCards[0].tabList.rowEls[0];
+ let tabRowFocused = BrowserTestUtils.waitForEvent(tabRow, "focus", win);
+ tabRow.focus();
+ await tabRowFocused;
+ info("The tab row main element has focus.");
+
+ // Navigate right to context menu button
+ let secondaryButtonFocused = BrowserTestUtils.waitForEvent(
+ tabRow.secondaryButtonEl,
+ "focus",
+ win
+ );
+ EventUtils.synthesizeKey("KEY_ArrowRight", {}, win);
+ await secondaryButtonFocused;
+ info("The context menu button has focus.");
+
+ // Navigate right to close button
+ let tertiaryButtonFocused = BrowserTestUtils.waitForEvent(
+ tabRow.tertiaryButtonEl,
+ "focus",
+ win
+ );
+ EventUtils.synthesizeKey("KEY_ArrowRight", {}, win);
+ await tertiaryButtonFocused;
+ info("The close button has focus");
+
+ // Navigate left to context menu button
+ secondaryButtonFocused = BrowserTestUtils.waitForEvent(
+ tabRow.secondaryButtonEl,
+ "focus",
+ win
+ );
+ EventUtils.synthesizeKey("KEY_ArrowLeft", {}, win);
+ await secondaryButtonFocused;
+ info("The context menu button has focus.");
+
+ // Navigate left to tab row main element
+ tabRowFocused = BrowserTestUtils.waitForEvent(tabRow.mainEl, "focus", win);
+ EventUtils.synthesizeKey("KEY_ArrowLeft", {}, win);
+ await tabRowFocused;
+ info("The tab row main element has focus.");
+ });
+
+ cleanupTabs();
+});
+
+add_task(async function test_focus_moves_after_unmute() {
+ await withFirefoxView({}, async browser => {
+ const { document } = browser.contentWindow;
+ let win = browser.ownerGlobal;
+ await navigateToViewAndWait(document, "opentabs");
+ let openTabs = document.querySelector("view-opentabs[name=opentabs]");
+ await openTabs.updateComplete;
+ await TestUtils.waitForCondition(
+ () => openTabs.viewCards[0].tabList.rowEls.length,
+ "The tab list has rendered."
+ );
+ await openTabs.openTabsTarget.readyWindowsPromise;
+ let card = openTabs.viewCards[0];
+ let openTabEl = card.tabList.rowEls[0];
+ let tabChangeRaised = BrowserTestUtils.waitForEvent(
+ NonPrivateTabs,
+ "TabChange"
+ );
+
+ // Mute tab
+ openTabEl.muteOrUnmuteTab();
+
+ await tabChangeRaised;
+ await openTabs.updateComplete;
+
+ let mutedTab = card.tabList.rowEls[0];
+ await TestUtils.waitForCondition(
+ () => mutedTab.indicators.includes("muted"),
+ "The tab has been muted."
+ );
+
+ // Unmute using keyboard
+ card.tabList.currentActiveElementId = mutedTab.focusMediaButton();
+ isActiveElement(mutedTab.mediaButtonEl);
+ info("The media button has focus.");
+
+ tabChangeRaised = BrowserTestUtils.waitForEvent(
+ NonPrivateTabs,
+ "TabChange"
+ );
+ EventUtils.synthesizeKey("KEY_Enter", {}, win);
+
+ await tabChangeRaised;
+ await openTabs.updateComplete;
+
+ let unmutedTab = card.tabList.rowEls[0];
+ await TestUtils.waitForCondition(
+ () => !unmutedTab.indicators.includes("muted"),
+ "The tab is no longer muted."
+ );
+ isActiveElement(unmutedTab.secondaryButtonEl);
+ info(
+ "Focus should be on the tab's secondary button element after unmuting."
+ );
+
+ // Mute tab again and check that only Enter keys will toggle it
+ unmutedTab.muteOrUnmuteTab();
+ await TestUtils.waitForCondition(
+ () => mutedTab.indicators.includes("muted"),
+ "The tab has been muted."
+ );
+ mutedTab = card.tabList.rowEls[0];
+
+ card.tabList.currentActiveElementId = mutedTab.focusLink();
+ isActiveElement(mutedTab.mainEl);
+ info("The 'main' element has focus.");
+
+ EventUtils.synthesizeKey("KEY_ArrowRight", {}, win);
+ isActiveElement(mutedTab.mediaButtonEl);
+ info("The media button has focus.");
+
+ EventUtils.synthesizeKey("KEY_ArrowRight", {}, win);
+ isActiveElement(mutedTab.secondaryButtonEl);
+ info("The secondary/more button has focus.");
+
+ ok(
+ mutedTab.indicators.includes("muted"),
+ "The muted tab is still muted after arrowing past it."
+ );
+
+ EventUtils.synthesizeKey("KEY_ArrowLeft", {}, win);
+ isActiveElement(mutedTab.mediaButtonEl);
+ info("The media button has focus.");
+
+ EventUtils.synthesizeKey("KEY_Enter", {}, win);
+ await tabChangeRaised;
+ await openTabs.updateComplete;
+
+ unmutedTab = card.tabList.rowEls[0];
+ await TestUtils.waitForCondition(
+ () => !unmutedTab.indicators.includes("muted"),
+ "The tab is no longer muted."
+ );
+ isActiveElement(unmutedTab.secondaryButtonEl);
+ info(
+ "Focus should be on the tab's secondary button element after unmuting."
+ );
+ });
+
+ cleanupTabs();
+});
+
+add_task(async function test_open_tab_row_with_sound_navigation() {
+ const tabWithSound = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "http://mochi.test:8888/browser/dom/base/test/file_audioLoop.html",
+ true
+ );
+ const tabsUpdated = await BrowserTestUtils.waitForEvent(
+ NonPrivateTabs,
+ "TabChange"
+ );
+ await withFirefoxView({}, async browser => {
+ const { document } = browser.contentWindow;
+ let win = browser.ownerGlobal;
+
+ await navigateToViewAndWait(document, "opentabs");
+ const openTabs = document.querySelector("view-opentabs[name=opentabs]");
+ await TestUtils.waitForCondition(
+ () => tabWithSound.hasAttribute("soundplaying"),
+ "Tab is playing sound"
+ );
+ await tabsUpdated;
+ await openTabs.updateComplete;
+
+ await TestUtils.waitForCondition(
+ () => openTabs.viewCards[0].tabList?.rowEls.length === 2,
+ "The tab list has rendered."
+ );
+
+ // Focus tab row with sound playing
+ let tabRow;
+ for (const rowEl of openTabs.viewCards[0].tabList.rowEls) {
+ if (rowEl.indicators.includes("soundplaying")) {
+ tabRow = rowEl;
+ break;
+ }
+ }
+ ok(tabRow, "Found a tab row with sound playing.");
+ let tabRowFocused = BrowserTestUtils.waitForEvent(tabRow, "focus", win);
+ tabRow.focus();
+ await tabRowFocused;
+ info("The tab row main element has focus.");
+
+ // Navigate right to media button
+ let mediaButtonFocused = BrowserTestUtils.waitForEvent(
+ tabRow.mediaButtonEl,
+ "focus",
+ win
+ );
+ EventUtils.synthesizeKey("KEY_ArrowRight", {}, win);
+ await mediaButtonFocused;
+ info("The media button has focus.");
+
+ // Navigate right to context menu button
+ let secondaryButtonFocused = BrowserTestUtils.waitForEvent(
+ tabRow.secondaryButtonEl,
+ "focus",
+ win
+ );
+ EventUtils.synthesizeKey("KEY_ArrowRight", {}, win);
+ await secondaryButtonFocused;
+ info("The context menu button has focus.");
+
+ // Navigate right to close button
+ let tertiaryButtonFocused = BrowserTestUtils.waitForEvent(
+ tabRow.tertiaryButtonEl,
+ "focus",
+ win
+ );
+ EventUtils.synthesizeKey("KEY_ArrowRight", {}, win);
+ await tertiaryButtonFocused;
+ info("The close button has focus");
+
+ // Navigate left to context menuo button
+ secondaryButtonFocused = BrowserTestUtils.waitForEvent(
+ tabRow.secondaryButtonEl,
+ "focus",
+ win
+ );
+ EventUtils.synthesizeKey("KEY_ArrowLeft", {}, win);
+ await secondaryButtonFocused;
+ info("The context menu button has focus.");
+
+ // Navigate left to media button
+ mediaButtonFocused = BrowserTestUtils.waitForEvent(
+ tabRow.mediaButtonEl,
+ "focus",
+ win
+ );
+ EventUtils.synthesizeKey("KEY_ArrowLeft", {}, win);
+ await mediaButtonFocused;
+ info("The media button has focus.");
+
+ // Navigate left to main element of tab row
+ tabRowFocused = BrowserTestUtils.waitForEvent(tabRow.mainEl, "focus", win);
+ EventUtils.synthesizeKey("KEY_ArrowLeft", {}, win);
+ await tabRowFocused;
+ info("The tab row main element has focus.");
+ });
+
+ cleanupTabs();
+});
+
+add_task(async function test_open_tab_row_with_sound_mute_and_unmute() {
+ const tabWithSound = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "http://mochi.test:8888/browser/dom/base/test/file_audioLoop.html",
+ true
+ );
+ const tabsUpdated = await BrowserTestUtils.waitForEvent(
+ NonPrivateTabs,
+ "TabChange"
+ );
+ await withFirefoxView({}, async browser => {
+ const { document } = browser.contentWindow;
+ let win = browser.ownerGlobal;
+
+ await navigateToViewAndWait(document, "opentabs");
+ const openTabs = document.querySelector("view-opentabs[name=opentabs]");
+ await TestUtils.waitForCondition(
+ () => tabWithSound.hasAttribute("soundplaying"),
+ "Tab is playing sound"
+ );
+ await tabsUpdated;
+ await openTabs.updateComplete;
+
+ await TestUtils.waitForCondition(
+ () => openTabs.viewCards[0].tabList?.rowEls.length === 2,
+ "The tab list has rendered."
+ );
+
+ // Focus tab row with sound playing
+ let tabRow;
+ for (const rowEl of openTabs.viewCards[0].tabList.rowEls) {
+ if (rowEl.indicators.includes("soundplaying")) {
+ tabRow = rowEl;
+ break;
+ }
+ }
+ ok(tabRow, "Found a tab row with sound playing.");
+ let tabRowFocused = BrowserTestUtils.waitForEvent(tabRow, "focus", win);
+ tabRow.focus();
+ await tabRowFocused;
+ info("The tab row main element has focus.");
+
+ // Navigate right to media button
+ let mediaButtonFocused = BrowserTestUtils.waitForEvent(
+ tabRow.mediaButtonEl,
+ "focus",
+ win
+ );
+ EventUtils.synthesizeKey("KEY_ArrowRight", {}, win);
+ await mediaButtonFocused;
+ info("The media button has focus.");
+
+ EventUtils.synthesizeKey("KEY_Enter", {}, win);
+ await TestUtils.waitForCondition(
+ () => tabRow.indicators.includes("muted"),
+ "Tab has been muted"
+ );
+
+ EventUtils.synthesizeKey("KEY_Enter", {}, win);
+ await TestUtils.waitForCondition(
+ () => tabRow.indicators.includes("soundplaying"),
+ "Tab has been unmuted"
+ );
+ });
+
+ cleanupTabs();
+});
diff --git a/browser/components/firefoxview/tests/browser/head.js b/browser/components/firefoxview/tests/browser/head.js
index b0b41b759d..302f19071c 100644
--- a/browser/components/firefoxview/tests/browser/head.js
+++ b/browser/components/firefoxview/tests/browser/head.js
@@ -3,6 +3,7 @@
const {
getFirefoxViewURL,
+ switchToWindow,
withFirefoxView,
assertFirefoxViewTab,
assertFirefoxViewTabSelected,
@@ -31,6 +32,11 @@ const { FeatureCalloutMessages } = ChromeUtils.importESModule(
const { TelemetryTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/TelemetryTestUtils.sys.mjs"
);
+const { NonPrivateTabs } = ChromeUtils.importESModule(
+ "resource:///modules/OpenTabs.sys.mjs"
+);
+// shut down the open tabs module after each test so we don't get debounced events bleeding into the next
+registerCleanupFunction(() => NonPrivateTabs.stop());
const triggeringPrincipal_base64 = E10SUtils.SERIALIZED_SYSTEMPRINCIPAL;
const { SessionStoreTestUtils } = ChromeUtils.importESModule(
@@ -548,31 +554,19 @@ registerCleanupFunction(() => {
gSandbox?.restore();
});
-function navigateToCategory(document, category) {
- const navigation = document.querySelector("fxview-category-navigation");
- let navButton = Array.from(navigation.categoryButtons).filter(
- categoryButton => {
- return categoryButton.name === category;
- }
- )[0];
- navButton.buttonEl.click();
-}
-
-async function navigateToCategoryAndWait(document, category) {
- info(`navigateToCategoryAndWait, for ${category}`);
- const navigation = document.querySelector("fxview-category-navigation");
+async function navigateToViewAndWait(document, view) {
+ info(`navigateToViewAndWait, for ${view}`);
+ const navigation = document.querySelector("moz-page-nav");
const win = document.ownerGlobal;
SimpleTest.promiseFocus(win);
- let navButton = Array.from(navigation.categoryButtons).find(
- categoryButton => {
- return categoryButton.name === category;
- }
- );
+ let navButton = Array.from(navigation.pageNavButtons).find(pageNavButton => {
+ return pageNavButton.view === view;
+ });
const namedDeck = document.querySelector("named-deck");
await BrowserTestUtils.waitForCondition(
() => navButton.getBoundingClientRect().height,
- `Waiting for ${category} button to be clickable`
+ `Waiting for ${view} button to be clickable`
);
EventUtils.synthesizeMouseAtCenter(navButton, {}, win);
@@ -582,10 +576,10 @@ async function navigateToCategoryAndWait(document, category) {
child => child.slot == "selected"
);
return (
- namedDeck.selectedViewName == category &&
+ namedDeck.selectedViewName == view &&
selectedView?.getBoundingClientRect().height
);
- }, `Waiting for ${category} to be visible`);
+ }, `Waiting for ${view} to be visible`);
}
/**
@@ -597,6 +591,7 @@ async function navigateToCategoryAndWait(document, category) {
* The tab switched to.
*/
async function switchToFxViewTab(win = window) {
+ await switchToWindow(win);
return BrowserTestUtils.switchTab(win.gBrowser, win.FirefoxViewHandler.tab);
}
@@ -657,10 +652,32 @@ function setSortOption(component, value) {
EventUtils.synthesizeMouseAtCenter(el, {}, el.ownerGlobal);
}
+/**
+ * Select the Open Tabs view-page in the Firefox View tab.
+ */
+async function navigateToOpenTabs(browser) {
+ const document = browser.contentDocument;
+ if (document.querySelector("named-deck").selectedViewName != "opentabs") {
+ await navigateToViewAndWait(browser.contentDocument, "opentabs");
+ }
+}
+
function getOpenTabsCards(openTabs) {
return openTabs.shadowRoot.querySelectorAll("view-opentabs-card");
}
+function getOpenTabsComponent(browser) {
+ return browser.contentDocument.querySelector("named-deck > view-opentabs");
+}
+
+async function getTabRowsForCard(card) {
+ await TestUtils.waitForCondition(
+ () => card.tabList.rowEls.length,
+ "Wait for the card's tab list to have rows"
+ );
+ return card.tabList.rowEls;
+}
+
async function click_recently_closed_tab_item(itemElem, itemProperty = "") {
// Make sure the firefoxview tab still has focus
is(
@@ -675,7 +692,7 @@ async function click_recently_closed_tab_item(itemElem, itemProperty = "") {
let clickTarget;
switch (itemProperty) {
case "dismiss":
- clickTarget = itemElem.buttonEl;
+ clickTarget = itemElem.secondaryButtonEl;
break;
default:
clickTarget = itemElem.mainEl;
@@ -706,3 +723,25 @@ async function waitForRecentlyClosedTabsList(doc) {
});
return [cardMainSlotNode, cardMainSlotNode.rowEls];
}
+
+async function add_new_tab(URL) {
+ let tabChangeRaised = BrowserTestUtils.waitForEvent(
+ NonPrivateTabs,
+ "TabChange"
+ );
+ let tab = BrowserTestUtils.addTab(gBrowser, URL);
+ // wait so we can reliably compare the tab URL
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await tabChangeRaised;
+ return tab;
+}
+
+function isActiveElement(expectedLinkEl) {
+ return expectedLinkEl.getRootNode().activeElement == expectedLinkEl;
+}
+
+function cleanupTabs() {
+ while (gBrowser.tabs.length > 1) {
+ BrowserTestUtils.removeTab(gBrowser.tabs[0]);
+ }
+}
diff --git a/browser/components/firefoxview/tests/chrome/chrome.toml b/browser/components/firefoxview/tests/chrome/chrome.toml
index b1677430b2..3edeefd4a9 100644
--- a/browser/components/firefoxview/tests/chrome/chrome.toml
+++ b/browser/components/firefoxview/tests/chrome/chrome.toml
@@ -2,6 +2,4 @@
["test_card_container.html"]
-["test_fxview_category_navigation.html"]
-
["test_fxview_tab_list.html"]
diff --git a/browser/components/firefoxview/tests/chrome/test_fxview_category_navigation.html b/browser/components/firefoxview/tests/chrome/test_fxview_category_navigation.html
deleted file mode 100644
index 0ea0a94baf..0000000000
--- a/browser/components/firefoxview/tests/chrome/test_fxview_category_navigation.html
+++ /dev/null
@@ -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>
diff --git a/browser/components/firefoxview/tests/chrome/test_fxview_tab_list.html b/browser/components/firefoxview/tests/chrome/test_fxview_tab_list.html
index 22f04acab2..e48f776592 100644
--- a/browser/components/firefoxview/tests/chrome/test_fxview_tab_list.html
+++ b/browser/components/firefoxview/tests/chrome/test_fxview_tab_list.html
@@ -308,12 +308,12 @@
);
await arrowRight();
ok(
- isActiveElement(tabItems[0].buttonEl),
+ isActiveElement(tabItems[0].secondaryButtonEl),
"Focus should be on the first row's context menu button element of the list"
);
await arrowDown();
ok(
- isActiveElement(tabItems[1].buttonEl),
+ isActiveElement(tabItems[1].secondaryButtonEl),
"Focus should be on the second row's context menu button element of the list"
);
await arrowLeft();
diff --git a/browser/components/firefoxview/viewpage.mjs b/browser/components/firefoxview/viewpage.mjs
index fee02b49d6..83b04faf5d 100644
--- a/browser/components/firefoxview/viewpage.mjs
+++ b/browser/components/firefoxview/viewpage.mjs
@@ -131,7 +131,7 @@ export class ViewPage extends ViewPageContent {
super();
this.selectedTab = false;
this.recentBrowsing = Boolean(this.recentBrowsingElement);
- this.onVisibilityChange = this.onVisibilityChange.bind(this);
+ this.onTabSelect = this.onTabSelect.bind(this);
this.onResize = this.onResize.bind(this);
}
@@ -148,14 +148,17 @@ export class ViewPage extends ViewPageContent {
this.windowResizeTask?.arm();
}
- onVisibilityChange(event) {
- if (this.isVisible) {
+ onTabSelect({ target }) {
+ const win = target.ownerGlobal;
+
+ let selfBrowser = window.docShell?.chromeEventHandler;
+ const { gBrowser } = this.getWindow();
+ let isForegroundTab = gBrowser.selectedBrowser == selfBrowser;
+
+ if (win.FirefoxViewHandler.tab?.selected && isForegroundTab) {
this.paused = false;
this.viewVisibleCallback();
- } else if (
- this.ownerViewPage.selectedTab &&
- this.ownerDocument.visibilityState == "hidden"
- ) {
+ } else {
this.paused = true;
this.viewHiddenCallback();
}
@@ -163,19 +166,12 @@ export class ViewPage extends ViewPageContent {
connectedCallback() {
super.connectedCallback();
- this.ownerDocument.addEventListener(
- "visibilitychange",
- this.onVisibilityChange
- );
}
disconnectedCallback() {
super.disconnectedCallback();
- this.ownerDocument.removeEventListener(
- "visibilitychange",
- this.onVisibilityChange
- );
this.getWindow().removeEventListener("resize", this.onResize);
+ this.getWindow().removeEventListener("TabSelect", this.onTabSelect);
}
updateAllVirtualLists() {
@@ -246,6 +242,7 @@ export class ViewPage extends ViewPageContent {
this.paused = false;
this.viewVisibleCallback();
this.getWindow().addEventListener("resize", this.onResize);
+ this.getWindow().addEventListener("TabSelect", this.onTabSelect);
}
}
@@ -257,5 +254,6 @@ export class ViewPage extends ViewPageContent {
this.windowResizeTask?.finalize();
}
this.getWindow().removeEventListener("resize", this.onResize);
+ this.getWindow().removeEventListener("TabSelect", this.onTabSelect);
}
}