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