diff --git a/browser/components/firefoxview/firefoxview.css b/browser/components/firefoxview/firefoxview.css index 0d3e6a4ed828..641f24d297b5 100644 --- a/browser/components/firefoxview/firefoxview.css +++ b/browser/components/firefoxview/firefoxview.css @@ -628,18 +628,20 @@ details[open] > .page-section-header > .twisty { } @media (prefers-contrast) { - span.closed-tab-li-main, - button.closed-tab-li-dismiss { + .closed-tab-li-main, + .closed-tab-li-dismiss { color: ButtonText; border-radius: 4px; border: 1px solid ButtonText; } } -.closed-tab-li-main:hover { - background-color: var(--fxview-element-background-hover); - color: var(--fxview-text-color-hover); -} +@media not (prefers-contrast) { + .closed-tab-li-main:hover { + background-color: var(--fxview-element-background-hover); + color: var(--fxview-text-color-hover); + } + } .closed-tab-li-main:hover .closed-tab-li-title { text-decoration-line: underline; @@ -694,32 +696,26 @@ details[open] > .page-section-header > .twisty { fill: var(--in-content-button-text-color-hover); } -.synced-tab-a, -.synced-tab-a:hover, -.synced-tab-a:active, -.synced-tab-a:hover:active, -.synced-tab-a:visited { +.tab-link, +.tab-link:hover, +.tab-link:active, +.tab-link:hover:active, +.tab-link:visited { color: inherit; text-decoration: none; - height: 100%; } @media (prefers-contrast) { - .synced-tab-a { + .tab-link { border-color: FieldText; } - .synced-tab-a, - .synced-tab-a:hover, - .synced-tab-a:active, - .synced-tab-a:hover:active, - .synced-tab-a:visited { + .tab-link, + .tab-link:hover, + .tab-link:active, + .tab-link:hover:active, + .tab-link:visited { color: LinkText; } - .synced-tab-a:focus-visible { - box-shadow: none; - outline: var(--in-content-focus-outline); - outline-offset: var(--in-content-focus-outline-offset); - } } .closed-tab-li-url, @@ -796,7 +792,8 @@ details[open] > .page-section-header > .twisty { grid-template-areas: "favicon title title title" "favicon domain domain domain" - "favicon device device time" + "favicon device device time"; + height: 100%; } .synced-tab-a:hover { diff --git a/browser/components/firefoxview/firefoxview.html b/browser/components/firefoxview/firefoxview.html index d8b473ea1342..aa334025eeec 100644 --- a/browser/components/firefoxview/firefoxview.html +++ b/browser/components/firefoxview/firefoxview.html @@ -159,16 +159,7 @@

- - - - +
diff --git a/browser/components/firefoxview/helpers.mjs b/browser/components/firefoxview/helpers.mjs index 4cd95c794801..e733bd799607 100644 --- a/browser/components/firefoxview/helpers.mjs +++ b/browser/components/firefoxview/helpers.mjs @@ -46,12 +46,8 @@ export function convertTimestamp( } export function createFaviconElement(image, targetURI = "") { - const imageUrl = image - ? lazy.PlacesUIUtils.getImageURL(image) - : `page-icon:${targetURI}`; let favicon = document.createElement("div"); - - favicon.style.backgroundImage = `url('${imageUrl}')`; + favicon.style.backgroundImage = `url('${getImageUrl(image, targetURI)}')`; favicon.classList.add("favicon"); return favicon; } diff --git a/browser/components/firefoxview/recently-closed-tabs.mjs b/browser/components/firefoxview/recently-closed-tabs.mjs index e3a207fc5dcc..30fdebd1a852 100644 --- a/browser/components/firefoxview/recently-closed-tabs.mjs +++ b/browser/components/firefoxview/recently-closed-tabs.mjs @@ -10,11 +10,18 @@ ChromeUtils.defineESModuleGetters(lazy, { import { formatURIForDisplay, convertTimestamp, - createFaviconElement, + getImageUrl, onToggleContainer, NOW_THRESHOLD_MS, } from "./helpers.mjs"; +import { + html, + ifDefined, + styleMap, +} from "chrome://global/content/vendor/lit.all.mjs"; +import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; + const { XPCOMUtils } = ChromeUtils.importESModule( "resource://gre/modules/XPCOMUtils.sys.mjs" ); @@ -28,11 +35,12 @@ function getWindow() { return window.browsingContext.embedderWindowGlobal.browsingContext.window; } -class RecentlyClosedTabsList extends HTMLElement { +class RecentlyClosedTabsList extends MozLitElement { constructor() { super(); this.maxTabsLength = 25; - this.closedTabsData = new Map(); + this.recentlyClosedTabs = []; + this.lastFocusedIndex = -1; // The recency timestamp update period is stored in a pref to allow tests to easily change it XPCOMUtils.defineLazyPreferenceGetter( @@ -44,10 +52,15 @@ class RecentlyClosedTabsList extends HTMLElement { ); } - get tabsList() { - return this.querySelector("ol"); + createRenderRoot() { + return this; } + static queries = { + tabsList: "ol", + timeElements: { all: "span.closed-tab-li-time" }, + }; + get fluentStrings() { if (!this._fluentStrings) { this._fluentStrings = new Localization(["browser/firefoxView.ftl"], true); @@ -55,13 +68,8 @@ class RecentlyClosedTabsList extends HTMLElement { return this._fluentStrings; } - get timeElements() { - return this.querySelectorAll("span.closed-tab-li-time"); - } - connectedCallback() { - this.addEventListener("click", this); - this.addEventListener("keydown", this); + super.connectedCallback(); this.intervalID = setInterval(() => this.updateTime(), lazy.timeMsPref); } @@ -69,20 +77,6 @@ class RecentlyClosedTabsList extends HTMLElement { clearInterval(this.intervalID); } - handleEvent(event) { - if ( - (event.type == "click" && !event.altKey) || - (event.type == "keydown" && event.keyCode == KeyEvent.DOM_VK_RETURN) || - (event.type == "keydown" && event.keyCode == KeyEvent.DOM_VK_SPACE) - ) { - if (!event.target.classList.contains("closed-tab-li-dismiss")) { - this.openTabAndUpdate(event); - } else { - this.dismissTabAndUpdate(event); - } - } - } - updateTime() { for (let timeEl of this.timeElements) { timeEl.textContent = convertTimestamp( @@ -105,44 +99,17 @@ class RecentlyClosedTabsList extends HTMLElement { return value; } - focusFirstItemOrHeader(dismissedIndex) { - // When a tab is removed from the list, the focus should - // remain on the list or the list header. This prevents context - // switching when navigating back to Firefox View. - let recentlyClosedList = [...this.tabsList.children]; - if (recentlyClosedList.length) { - recentlyClosedList.forEach(element => - element.setAttribute("tabindex", "-1") - ); - let mainContent; - if (dismissedIndex) { - // Select the item above the one that was just dismissed - mainContent = recentlyClosedList[dismissedIndex - 1].querySelector( - ".closed-tab-li-main" - ); - } else { - mainContent = recentlyClosedList[0].querySelector( - ".closed-tab-li-main" - ); - } - mainContent.setAttribute("tabindex", "0"); - mainContent.focus(); - } else { - document.getElementById("recently-closed-tabs-header-section").focus(); - } - } - openTabAndUpdate(event) { event.preventDefault(); + if (event.type == "click" && event.altKey) { + return; + } const item = event.target.closest(".closed-tab-li"); // only used for telemetry const position = [...this.tabsList.children].indexOf(item) + 1; const closedId = item.dataset.tabid; lazy.SessionStore.undoCloseById(closedId); - this.tabsList.removeChild(item); - - this.focusFirstItemOrHeader(); // record telemetry let tabClosedAt = parseInt( @@ -174,11 +141,8 @@ class RecentlyClosedTabsList extends HTMLElement { // Tab not found in recently closed list return; } - this.tabsList.removeChild(item); lazy.SessionStore.forgetClosedTab(getWindow(), closedTabIndex); - this.focusFirstItemOrHeader(closedTabIndex); - // record telemetry let tabClosedAt = parseInt( item.querySelector(".closed-tab-li-time").dataset.timestamp @@ -197,138 +161,145 @@ class RecentlyClosedTabsList extends HTMLElement { ); } - updateTabsList() { - let newClosedTabs = lazy.SessionStore.getClosedTabData(getWindow()); - newClosedTabs = newClosedTabs.slice(0, this.maxTabsLength); + updateRecentlyClosedTabs() { + let recentlyClosedTabsData = lazy.SessionStore.getClosedTabData( + getWindow() + ); + this.recentlyClosedTabs = recentlyClosedTabsData.slice( + 0, + this.maxTabsLength + ); + this.requestUpdate(); + } - if (this.closedTabsData.size && !newClosedTabs.length) { - // if a user purges history, clear the list - while (this.tabsList.lastElementChild) { - this.tabsList.lastElementChild.remove(); - } - document - .getElementById("recently-closed-tabs-container") - .togglePlaceholderVisibility(true); - this.tabsList.hidden = true; - this.closedTabsData = new Map(); - return; + render() { + let { recentlyClosedTabs } = this; + let closedTabsContainer = document.getElementById( + "recently-closed-tabs-container" + ); + + if (!recentlyClosedTabs.length) { + // Show empty message if no recently closed tabs + closedTabsContainer.toggleContainerStyleForEmptyMsg(true); + return html` + ${this.emptyMessageTemplate()} + `; } - // First purge obsolete items out of the map so we don't leak them forever: - for (let id of this.closedTabsData.keys()) { - if (!newClosedTabs.some(t => t.closedId == id)) { - this.closedTabsData.delete(id); - } - } + closedTabsContainer.toggleContainerStyleForEmptyMsg(false); - // Then work out which of the new closed tabs are additions and which update - // existing items: - let tabsToAdd = []; - let tabsToUpdate = []; - for (let newTab of newClosedTabs) { - let oldTab = this.closedTabsData.get(newTab.closedId); - this.closedTabsData.set(newTab.closedId, newTab); - if (!oldTab) { - tabsToAdd.push(newTab); - } else if ( - this.getTabStateValue(oldTab, "url") != - this.getTabStateValue(newTab, "url") - ) { - tabsToUpdate.push(newTab); - } - } + return html` +
    + ${recentlyClosedTabs.map((tab, i) => + this.recentlyClosedTabTemplate(tab, !i) + )} +
+ `; + } - // Remove existing tabs from tabsList if not in latest closedTabsData - // which is necessary when using "Reopen Closed Tab" from the toolbar - // or when selecting "Forget this site" in History - [...this.tabsList.children].forEach(existingTab => { - if (!this.closedTabsData.get(parseInt(existingTab.dataset.tabid, 10))) { - this.tabsList.removeChild(existingTab); - } - }); - - // If there's nothing to add/update, return. - if (!tabsToAdd.length && !tabsToUpdate.length) { - return; - } - - // Add new tabs. - for (let tab of tabsToAdd.reverse()) { - if (this.tabsList.children.length == this.maxTabsLength) { - this.tabsList.lastChild.remove(); - } - let li = this.generateListItem(tab); - let mainContent = li.querySelector(".closed-tab-li-main"); - // Only the first item in the list should be focusable - if (!this.tabsList.children.length) { - mainContent.setAttribute("tabindex", "0"); - } else if (this.tabsList.children.length) { - mainContent.setAttribute("tabindex", "0"); - this.tabsList.children[0].setAttribute("tabindex", "-1"); - } - this.tabsList.prepend(li); - } - - // Update any recently closed tabs that now have different URLs: - for (let tab of tabsToUpdate) { - let tabElement = this.querySelector( - `.closed-tab-li[data-tabid="${tab.closedId}"]` - ); - let url = this.getTabStateValue(tab, "url"); - this.updateURLForListItem(tabElement, url); - } - - // Now unhide the list if necessary: - if (this.tabsList.hidden) { - this.tabsList.hidden = false; - document - .getElementById("recently-closed-tabs-container") - .togglePlaceholderVisibility(false); + willUpdate() { + if (this.tabsList && this.tabsList.contains(document.activeElement)) { + let activeLi = document.activeElement.closest(".closed-tab-li"); + this.lastFocusedIndex = [...this.tabsList.children].indexOf(activeLi); + } else { + this.lastFocusedIndex = -1; } } - generateListItem(tab) { - const li = document.createElement("li"); - li.classList.add("closed-tab-li"); - li.dataset.tabid = tab.closedId; - - const title = document.createElement("span"); - title.textContent = `${tab.title}`; - title.classList.add("closed-tab-li-title"); - - const targetURI = this.getTabStateValue(tab, "url"); - const image = tab.image; - const favicon = createFaviconElement(image, targetURI); - - const urlElement = document.createElement("span"); - urlElement.classList.add("closed-tab-li-url"); - - const time = document.createElement("span"); - const convertedTime = convertTimestamp(tab.closedAt, this.fluentStrings); - time.textContent = convertedTime; - time.setAttribute("data-timestamp", tab.closedAt); - time.classList.add("closed-tab-li-time"); - - const mainContent = document.createElement("span"); - mainContent.classList.add("closed-tab-li-main"); - mainContent.setAttribute("role", "link"); - mainContent.setAttribute("tabindex", 0); - mainContent.append(favicon, title, urlElement, time); - - const dismissButton = document.createElement("button"); - let tabTitle = tab.title ?? ""; - document.l10n.setAttributes( - dismissButton, - "firefoxview-closed-tabs-dismiss-tab", - { - tabTitle, + updated() { + let focusRestored = false; + if (this.lastFocusedIndex >= 0) { + if (this.tabsList && this.tabsList.children.length) { + let items = [...this.tabsList.children]; + let newFocusIndex = Math.max( + Math.min(items.length - 1, this.lastFocusedIndex - 1), + 0 + ); + let newFocus = items[newFocusIndex]; + if (newFocus) { + focusRestored = true; + newFocus.querySelector(".closed-tab-li-main").focus(); + } } - ); - dismissButton.classList.add("closed-tab-li-dismiss"); + if (!focusRestored) { + document.getElementById("recently-closed-tabs-header-section").focus(); + } + } + this.lastFocusedIndex = -1; + } - li.append(mainContent, dismissButton); - this.updateURLForListItem(li, targetURI); - return li; + emptyMessageTemplate() { + return html` + + `; + } + + recentlyClosedTabTemplate(tab, primary) { + const targetURI = this.getTabStateValue(tab, "url"); + const convertedTime = convertTimestamp(tab.closedAt, this.fluentStrings); + return html` +
  • + this.openTabAndUpdate(e)} + > +
    + + ${tab.title} + + + ${formatURIForDisplay(targetURI)} + + + ${convertedTime} + +
    + +
  • + `; } // Update the URL for a new or previously-populated list item. @@ -413,8 +384,7 @@ class RecentlyClosedTabsContainer extends HTMLDetailsElement { handleObservers(contentDocument) { if (contentDocument?.URL == "about:firefoxview") { this.addObserversIfNeeded(); - this.list.updateTabsList(); - this.maybeUpdateFocus(); + this.list.updateRecentlyClosedTabs(); } else { this.removeObserversIfNeeded(); } @@ -426,16 +396,12 @@ class RecentlyClosedTabsContainer extends HTMLDetailsElement { (topic == SS_NOTIFY_BROWSER_SHUTDOWN_FLUSH && subject.ownerGlobal == getWindow()) ) { - this.list.updateTabsList(); + this.list.updateRecentlyClosedTabs(); } } onLoad() { - if (this.getClosedTabCount() == 0) { - this.togglePlaceholderVisibility(true); - } else { - this.list.updateTabsList(); - } + this.list.updateRecentlyClosedTabs(); this.addObserversIfNeeded(); } @@ -447,27 +413,7 @@ class RecentlyClosedTabsContainer extends HTMLDetailsElement { } } - /** - * Manages focus when returning to the Firefox View tab - * - * @memberof RecentlyClosedTabsContainer - */ - maybeUpdateFocus() { - // Check if focus is in the container element - if (this.contains(document.activeElement)) { - let listItems = this.list.querySelectorAll("li"); - // More tabs may have been added to the list, so we'll refocus - // the first item in the list. - if (listItems.length) { - listItems[0].querySelector(".closed-tab-li-main").focus(); - } else { - this.querySelector("summary").focus(); - } - } - } - - togglePlaceholderVisibility(visible) { - this.noTabsElement.toggleAttribute("hidden", !visible); + toggleContainerStyleForEmptyMsg(visible) { this.collapsibleContainer.classList.toggle("empty-container", visible); } diff --git a/browser/components/firefoxview/tab-pickup-list.mjs b/browser/components/firefoxview/tab-pickup-list.mjs index 0acf328a6854..d12126f454bc 100644 --- a/browser/components/firefoxview/tab-pickup-list.mjs +++ b/browser/components/firefoxview/tab-pickup-list.mjs @@ -352,7 +352,7 @@ class TabPickupList extends HTMLElement { li.classList.add("synced-tab-li"); const a = document.createElement("a"); - a.classList.add("synced-tab-a"); + a.classList.add("synced-tab-a", "tab-link"); a.target = "_blank"; if (index != 0) { a.setAttribute("tabindex", "-1"); diff --git a/browser/components/firefoxview/tests/browser/FirefoxViewTestUtils.sys.mjs b/browser/components/firefoxview/tests/browser/FirefoxViewTestUtils.sys.mjs index a913ae7f0c62..0aa17724e7ad 100644 --- a/browser/components/firefoxview/tests/browser/FirefoxViewTestUtils.sys.mjs +++ b/browser/components/firefoxview/tests/browser/FirefoxViewTestUtils.sys.mjs @@ -71,6 +71,10 @@ async function withFirefoxView( // reset internal state so we aren't reacting to whatever state the last invocation left behind TabsSetupFlowManager.resetInternalState(); } + // Setting this pref allows the test to run as expected with a keyboard on MacOS + await win.SpecialPowers.pushPrefEnv({ + set: [["accessibility.tabfocus", 7]], + }); let tab = await openFirefoxViewTab(win); let originalWindow = tab.ownerGlobal; let result = await taskFn(tab.linkedBrowser); @@ -87,6 +91,7 @@ async function withFirefoxView( "removeTab would have been called" ); } + await win.SpecialPowers.popPrefEnv(); if (shouldCloseWin) { await BrowserTestUtils.closeWindow(win); } diff --git a/browser/components/firefoxview/tests/browser/browser_firefoxview_tab.js b/browser/components/firefoxview/tests/browser/browser_firefoxview_tab.js index 124da75dcddb..2b1e392f1261 100644 --- a/browser/components/firefoxview/tests/browser/browser_firefoxview_tab.js +++ b/browser/components/firefoxview/tests/browser/browser_firefoxview_tab.js @@ -245,7 +245,6 @@ add_task(async function test_add_ons_cant_unhide_fx_view() { // Test navigation to first visible tab when the // Firefox View button is present and active. add_task(async function testFirstTabFocusableWhenFxViewOpen() { - await SpecialPowers.pushPrefEnv({ set: [["accessibility.tabfocus", 7]] }); await withFirefoxView({}, async browser => { let win = browser.ownerGlobal; ok(win.FirefoxViewHandler.tab.selected, "Firefox View tab is selected"); @@ -266,7 +265,6 @@ add_task(async function testFirstTabFocusableWhenFxViewOpen() { // Test that Firefox View tab is not multiselectable add_task(async function testFxViewNotMultiselect() { - await SpecialPowers.pushPrefEnv({ set: [["accessibility.tabfocus", 7]] }); await withFirefoxView({ win: window }, async browser => { let win = browser.ownerGlobal; Assert.ok( diff --git a/browser/components/firefoxview/tests/browser/browser_recently_closed_tabs.js b/browser/components/firefoxview/tests/browser/browser_recently_closed_tabs.js index ecb5680c50b1..b66262465ba2 100644 --- a/browser/components/firefoxview/tests/browser/browser_recently_closed_tabs.js +++ b/browser/components/firefoxview/tests/browser/browser_recently_closed_tabs.js @@ -44,7 +44,7 @@ async function close_tab(tab) { } async function dismiss_tab(tab, content) { - info(`Dismissing tab ${tab.dataset.targetURI}`); + info(`Dismissing tab ${tab.dataset.targeturi}`); const closedObjectsChanged = () => TestUtils.topicObserved("sessionstore-closed-objects-changed"); let dismissButton = tab.querySelector(".closed-tab-li-dismiss"); @@ -63,12 +63,15 @@ add_task(async function test_empty_list() { "collapsible container should have correct styling when the list is empty" ); - testVisibility(browser, { - expectedVisible: { - "#recently-closed-tabs-placeholder": true, - "ol.closed-tabs-list": false, - }, - }); + Assert.ok( + document.getElementById("recently-closed-tabs-placeholder"), + "The empty message is displayed." + ); + + Assert.ok( + !document.querySelector("ol.closed-tabs-list"), + "The recently closed tabs list is not displayed." + ); const tab1 = await add_new_tab(URLs[0]); @@ -85,12 +88,15 @@ add_task(async function test_empty_list() { "collapsible container should have correct styling when the list is not empty" ); - testVisibility(browser, { - expectedVisible: { - "#recently-closed-tabs-placeholder": false, - "ol.closed-tabs-list": true, - }, - }); + Assert.ok( + !document.getElementById("recently-closed-tabs-placeholder"), + "The empty message is not displayed." + ); + + Assert.ok( + document.querySelector("ol.closed-tabs-list"), + "The recently closed tabs list is displayed." + ); is( document.querySelector("ol.closed-tabs-list").children.length, @@ -146,7 +152,7 @@ add_task(async function test_list_ordering() { ok( document .querySelector("ol.closed-tabs-list") - .firstChild.textContent.includes("mochi.test"), + .children[0].textContent.includes("mochi.test"), "first list item in recently-closed-tabs-list is in the correct order" ); @@ -158,9 +164,9 @@ add_task(async function test_list_ordering() { ); let ele = document.querySelector("ol.closed-tabs-list").firstElementChild; - let uri = ele.getAttribute("data-target-u-r-i"); + let uri = ele.getAttribute("data-targeturi"); let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, uri); - ele.click(); + ele.querySelector(".closed-tab-li-main").click(); await newTabPromise; await TestUtils.waitForCondition( @@ -245,15 +251,18 @@ add_task(async function test_max_list_items() { "collapsible container should have correct styling when the list is not empty" ); - testVisibility(browser, { - expectedVisible: { - "#recently-closed-tabs-placeholder": false, - "ol.closed-tabs-list": true, - }, - }); + Assert.ok( + !document.getElementById("recently-closed-tabs-placeholder"), + "The empty message is not displayed." + ); + + Assert.ok( + document.querySelector("ol.closed-tabs-list"), + "The recently closed tabs list is displayed." + ); is( - document.querySelector("ol.closed-tabs-list").childNodes.length, + document.querySelector("ol.closed-tabs-list").children.length, mockMaxTabsLength, `recently-closed-tabs-list should have ${mockMaxTabsLength} list items` ); @@ -265,9 +274,8 @@ add_task(async function test_max_list_items() { const tab = await add_new_tab(URLs[3]); await close_tab(tab); await closedObjectsChanged; - let firstListItem = document.querySelector("ol.closed-tabs-list") - .firstChild; + .children[0]; await BrowserTestUtils.waitForMutationCondition( firstListItem, { characterData: true, childList: true, subtree: true }, @@ -279,7 +287,7 @@ add_task(async function test_max_list_items() { ); is( - document.querySelector("ol.closed-tabs-list").childNodes.length, + document.querySelector("ol.closed-tabs-list").children.length, mockMaxTabsLength, `recently-closed-tabs-list should still have ${mockMaxTabsLength} list items` ); @@ -310,6 +318,7 @@ add_task(async function test_time_updates_correctly() { closedId: 0, closedAt: Date.now() - TAB_CLOSED_AGO_MS, image: null, + title: "Example", }, ], }, @@ -329,9 +338,10 @@ add_task(async function test_time_updates_correctly() { }, async browser => { const { document } = browser.contentWindow; - + const numOfListItems = document.querySelector("ol.closed-tabs-list") + .children.length; const lastListItem = document.querySelector("ol.closed-tabs-list") - .lastChild; + .children[numOfListItems - 1]; const timeLabel = lastListItem.querySelector("span.closed-tab-li-time"); let initialTimeText = timeLabel.textContent; Assert.stringContains( @@ -387,14 +397,12 @@ add_task(async function test_list_maintains_focus_when_restoring_tab() { let gBrowser = browser.getTabBrowser(); const { document } = browser.contentWindow; const list = document.querySelectorAll(".closed-tab-li"); - let expectedFocusedElement = list[1].querySelector(".closed-tab-li-main"); list[0].querySelector(".closed-tab-li-main").focus(); EventUtils.synthesizeKey("KEY_Enter"); let firefoxViewTab = gBrowser.tabs.find(tab => tab.label == "Firefox View"); await BrowserTestUtils.switchTab(gBrowser, firefoxViewTab); - is( - document.activeElement, - expectedFocusedElement, + Assert.ok( + document.activeElement.textContent.includes("mochitest index"), "Focus should be on the first item in the recently closed list" ); }); @@ -414,7 +422,6 @@ add_task(async function test_list_maintains_focus_when_restoring_tab() { ); const list = document.querySelectorAll(".closed-tab-li"); list[0].querySelector(".closed-tab-li-main").focus(); - EventUtils.synthesizeKey("KEY_Enter"); let firefoxViewTab = gBrowser.tabs.find(tab => tab.label == "Firefox View"); await BrowserTestUtils.switchTab(gBrowser, firefoxViewTab); @@ -455,27 +462,25 @@ add_task(async function test_switch_before_closing() { ); BrowserTestUtils.loadURIString(newTab.linkedBrowser, FINAL_URL); await loadPromise; - // Close the added tab BrowserTestUtils.removeTab(newTab); const { document } = browser.contentWindow; - const tabsList = document.querySelector("ol.closed-tabs-list"); await BrowserTestUtils.waitForMutationCondition( - tabsList, - { childList: true }, - () => !!tabsList.children.length + document.querySelector("recently-closed-tabs-list"), + { childList: true, subtree: true }, + () => document.querySelector("ol.closed-tabs-list") ); + const tabsList = document.querySelector("ol.closed-tabs-list"); info("A tab appeared in the list, ensure it has the right URL."); - let urlBit = tabsList.firstElementChild.querySelector(".closed-tab-li-url"); + let urlBit = tabsList.children[0].querySelector(".closed-tab-li-url"); await BrowserTestUtils.waitForMutationCondition( urlBit, { characterData: true, attributeFilter: ["title"] }, () => urlBit.textContent.includes(".com") ); - is( - urlBit.textContent, - "example.com", + Assert.ok( + urlBit.textContent.includes("example.com"), "Item should end up with the correct URL." ); }); @@ -495,7 +500,7 @@ add_task(async function test_alt_click_no_launch() { let gBrowser = browser.getTabBrowser(); let originalTabsLength = gBrowser.tabs.length; await BrowserTestUtils.synthesizeMouseAtCenter( - ".closed-tab-li", + ".closed-tab-li .closed-tab-li-main", { altKey: true }, browser ); @@ -554,19 +559,6 @@ add_task(async function test_restore_recently_closed_tabs() { await tabRestored; ok(true, "Tab was restored by using the Enter key"); - await EventUtils.synthesizeMouseAtCenter( - gBrowser.ownerDocument.getElementById("firefox-view-button"), - { type: "mousedown" }, - window - ); - - tabRestored = BrowserTestUtils.waitForNewTab(gBrowser, URLs[0]); - document.querySelector(".closed-tab-li .closed-tab-li-main").focus(); - EventUtils.synthesizeKey(" ", {}, gBrowser.contentWindow); - - await tabRestored; - ok(true, "Tab was restored by using the Space bar"); - // clean up extra tabs while (gBrowser.tabs.length > 1) { BrowserTestUtils.removeTab(gBrowser.tabs.at(-1)); @@ -618,7 +610,7 @@ add_task(async function test_reopen_recently_closed_tabs() { ); Assert.equal( - tabsList.children[0].dataset.targetURI, + tabsList.children[0].dataset.targeturi, URLs[1], `First recently closed item should be ${URLs[1]}` ); @@ -632,7 +624,7 @@ add_task(async function test_reopen_recently_closed_tabs() { ); Assert.equal( - tabsList.children[0].dataset.targetURI, + tabsList.children[0].dataset.targeturi, URLs[2], `First recently closed item should be ${URLs[2]}` ); @@ -646,7 +638,7 @@ add_task(async function test_reopen_recently_closed_tabs() { ); Assert.equal( - tabsList.children[0].dataset.targetURI, + tabsList.children[0].dataset.targeturi, URLs[1], `First recently closed item should be ${URLs[1]}` ); @@ -715,7 +707,7 @@ add_task(async function test_dismiss_tab() { ); Assert.equal( - tabsList.children[0].dataset.targetURI, + tabsList.children[0].dataset.targeturi, URLs[1], `First recently closed item should be ${URLs[1]}` ); @@ -750,7 +742,7 @@ add_task(async function test_dismiss_tab() { ); Assert.equal( - tabsList.children[0].dataset.targetURI, + tabsList.children[0].dataset.targeturi, URLs[2], `First recently closed item should be ${URLs[2]}` ); @@ -784,11 +776,14 @@ add_task(async function test_dismiss_tab() { { clear: true, process: "parent" } ); - testVisibility(browser, { - expectedVisible: { - "#recently-closed-tabs-placeholder": true, - "ol.closed-tabs-list": false, - }, - }); + Assert.ok( + document.getElementById("recently-closed-tabs-placeholder"), + "The empty message is displayed." + ); + + Assert.ok( + !document.querySelector("ol.closed-tabs-list"), + "The recently closed tabs list is not displayed." + ); }); }); diff --git a/browser/components/firefoxview/tests/browser/browser_recently_closed_tabs_keyboard.js b/browser/components/firefoxview/tests/browser/browser_recently_closed_tabs_keyboard.js index 2d495934dfb4..83c3a1cfb621 100644 --- a/browser/components/firefoxview/tests/browser/browser_recently_closed_tabs_keyboard.js +++ b/browser/components/firefoxview/tests/browser/browser_recently_closed_tabs_keyboard.js @@ -73,7 +73,6 @@ add_task(async function test_keyboard_navigation() { "isTabSyncSetupComplete" ); setupCompleteStub.returns(true); - await open_then_close(URLs[0]); await withFirefoxView({ win: window }, async browser => { @@ -84,16 +83,19 @@ add_task(async function test_keyboard_navigation() { ); assertPreconditions(document, summary); + tab(); - ok( - list[0].querySelector(".closed-tab-li-main").matches(":focus"), + Assert.equal( + list[0].querySelector(".closed-tab-li-main"), + document.activeElement, "The first link is focused" ); tab(true); - ok( - summary.matches(":focus"), + Assert.equal( + summary, + document.activeElement, "The container is focused when using shift+tab in the list" ); }); @@ -117,26 +119,30 @@ add_task(async function test_keyboard_navigation() { tab(); - ok( - list[0].querySelector(".closed-tab-li-main").matches(":focus"), + Assert.equal( + list[0].querySelector(".closed-tab-li-main"), + document.activeElement, "The first link is focused" ); tab(); tab(); - ok( - list[1].querySelector(".closed-tab-li-main").matches(":focus"), + Assert.equal( + list[1].querySelector(".closed-tab-li-main"), + document.activeElement, "The second link is focused" ); tab(true); tab(true); - ok( - list[0].querySelector(".closed-tab-li-main").matches(":focus"), + Assert.equal( + list[0].querySelector(".closed-tab-li-main"), + document.activeElement, "The first link is focused again" ); tab(true); - ok( - summary.matches(":focus"), + Assert.equal( + summary, + document.activeElement, "The container is focused when using shift+tab in the list" ); }); @@ -162,32 +168,37 @@ add_task(async function test_keyboard_navigation() { tab(); - ok( - list[0].querySelector(".closed-tab-li-main").matches(":focus"), + Assert.equal( + list[0].querySelector(".closed-tab-li-main"), + document.activeElement, "The first link is focused" ); tab(); tab(); - ok( - list[1].querySelector(".closed-tab-li-main").matches(":focus"), + Assert.equal( + list[1].querySelector(".closed-tab-li-main"), + document.activeElement, "The second link is focused" ); tab(); tab(); - ok( - list[2].querySelector(".closed-tab-li-main").matches(":focus"), + Assert.equal( + list[2].querySelector(".closed-tab-li-main"), + document.activeElement, "The third link is focused" ); tab(true); tab(true); - ok( - list[1].querySelector(".closed-tab-li-main").matches(":focus"), + Assert.equal( + list[1].querySelector(".closed-tab-li-main"), + document.activeElement, "The second link is focused" ); tab(true); tab(true); - ok( - list[0].querySelector(".closed-tab-li-main").matches(":focus"), + Assert.equal( + list[0].querySelector(".closed-tab-li-main"), + document.activeElement, "The first link is focused" ); }); @@ -218,7 +229,7 @@ add_task(async function test_dismiss_tab_keyboard() { await dismiss_tab_keyboard(tabsList.children[0], document); Assert.equal( - tabsList.children[0].dataset.targetURI, + tabsList.children[0].dataset.targeturi, URLs[1], `First recently closed item should be ${URLs[1]}` ); @@ -232,7 +243,7 @@ add_task(async function test_dismiss_tab_keyboard() { await dismiss_tab_keyboard(tabsList.children[0], document); Assert.equal( - tabsList.children[0].dataset.targetURI, + tabsList.children[0].dataset.targeturi, URLs[0], `First recently closed item should be ${URLs[0]}` ); @@ -245,11 +256,14 @@ add_task(async function test_dismiss_tab_keyboard() { await dismiss_tab_keyboard(tabsList.children[0], document); - testVisibility(browser, { - expectedVisible: { - "#recently-closed-tabs-placeholder": true, - "ol.closed-tabs-list": false, - }, - }); + Assert.ok( + document.getElementById("recently-closed-tabs-placeholder"), + "The empty message is displayed." + ); + + Assert.ok( + !document.querySelector("ol.closed-tabs-list"), + "The recently clsoed tabs list is not displayed." + ); }); }); diff --git a/browser/components/firefoxview/tests/browser/browser_setup_state.js b/browser/components/firefoxview/tests/browser/browser_setup_state.js index cbb217b95a32..847a3e0e94e5 100644 --- a/browser/components/firefoxview/tests/browser/browser_setup_state.js +++ b/browser/components/firefoxview/tests/browser/browser_setup_state.js @@ -498,7 +498,9 @@ add_task(async function test_mobile_promo_pref() { }); // reset the dismissed pref, which should case the promo to get shown - await SpecialPowers.popPrefEnv(); + await SpecialPowers.pushPrefEnv({ + set: [[MOBILE_PROMO_DISMISSED_PREF, false]], + }); await waitForElementVisible( browser, "#tab-pickup-container > .promo-box", @@ -760,6 +762,5 @@ add_task(async function test_close_device_connected_tab() { // cleanup time await tearDown(sandbox); - await SpecialPowers.popPrefEnv(); await BrowserTestUtils.closeWindow(win); }); diff --git a/browser/components/firefoxview/tests/browser/browser_tab_pickup_list.js b/browser/components/firefoxview/tests/browser/browser_tab_pickup_list.js index ea1eba3e1d17..671a35557d20 100644 --- a/browser/components/firefoxview/tests/browser/browser_tab_pickup_list.js +++ b/browser/components/firefoxview/tests/browser/browser_tab_pickup_list.js @@ -537,8 +537,6 @@ add_task(async function test_tabs_sync_on_user_page_reload() { }); add_task(async function test_keyboard_navigation() { - // Setting this pref allows the test to run as expected on MacOS - await SpecialPowers.pushPrefEnv({ set: [["accessibility.tabfocus", 7]] }); TabsSetupFlowManager.resetInternalState(); const sandbox = setupRecentDeviceListMocks(); diff --git a/browser/components/sessionstore/test/browser_closedId.js b/browser/components/sessionstore/test/browser_closedId.js index 1de88f73a572..31439595f0d0 100644 --- a/browser/components/sessionstore/test/browser_closedId.js +++ b/browser/components/sessionstore/test/browser_closedId.js @@ -42,6 +42,7 @@ add_task(async function test_closedId_order() { }, closedId: 0, closedAt: Date.now() - 100, + title: "Example", }, { state: { @@ -54,6 +55,7 @@ add_task(async function test_closedId_order() { }, closedId: 1, closedAt: Date.now() - 50, + title: "about:mozilla", }, { state: { @@ -66,6 +68,7 @@ add_task(async function test_closedId_order() { }, closedId: 2, closedAt: Date.now(), + title: "Example", }, ], }, diff --git a/toolkit/content/widgets/lit-utils.mjs b/toolkit/content/widgets/lit-utils.mjs index d0f8633efcd9..b8d5c18b4948 100644 --- a/toolkit/content/widgets/lit-utils.mjs +++ b/toolkit/content/widgets/lit-utils.mjs @@ -102,7 +102,11 @@ export class MozLitElement extends LitElement { connectedCallback() { super.connectedCallback(); - if (!this._l10nRootConnected && document.l10n) { + if ( + this.renderRoot == this.shadowRoot && + !this._l10nRootConnected && + document.l10n + ) { document.l10n.connectRoot(this.renderRoot); this._l10nRootConnected = true; }