diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:14:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:14:29 +0000 |
commit | fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8 (patch) | |
tree | 4c1ccaf5486d4f2009f9a338a98a83e886e29c97 /browser/components/firefoxview/tests | |
parent | Releasing progress-linux version 124.0.1-1~progress7.99u1. (diff) | |
download | firefox-fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8.tar.xz firefox-fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8.zip |
Merging upstream version 125.0.1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/firefoxview/tests')
27 files changed, 1759 insertions, 948 deletions
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(); |