/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import {
classMap,
html,
ifDefined,
styleMap,
when,
} from "chrome://global/content/vendor/lit.all.mjs";
import {
FxviewTabListBase,
FxviewTabRowBase,
} from "chrome://browser/content/firefoxview/fxview-tab-list.mjs";
// eslint-disable-next-line import/no-unassigned-import
import "chrome://global/content/elements/moz-button.mjs";
const lazy = {};
let XPCOMUtils;
XPCOMUtils = ChromeUtils.importESModule(
"resource://gre/modules/XPCOMUtils.sys.mjs"
).XPCOMUtils;
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"virtualListEnabledPref",
"browser.firefox-view.virtual-list.enabled"
);
/**
* A list of clickable tab items
*
* @property {boolean} pinnedTabsGridView - Whether to show pinned tabs in a grid view
*/
export class OpenTabsTabList extends FxviewTabListBase {
constructor() {
super();
this.pinnedTabsGridView = false;
this.pinnedTabs = [];
this.unpinnedTabs = [];
}
static properties = {
pinnedTabsGridView: { type: Boolean },
};
static queries = {
...FxviewTabListBase.queries,
rowEls: {
all: "opentabs-tab-row",
},
};
willUpdate(changes) {
this.activeIndex = Math.min(
Math.max(this.activeIndex, 0),
this.tabItems.length - 1
);
if (changes.has("dateTimeFormat") || changes.has("updatesPaused")) {
this.clearIntervalTimer();
if (!this.updatesPaused && this.dateTimeFormat == "relative") {
this.startIntervalTimer();
this.onIntervalUpdate();
}
}
// Move pinned tabs to the beginning of the list
if (this.pinnedTabsGridView) {
// Can set maxTabsLength to -1 to have no max
this.unpinnedTabs = this.tabItems.filter(
tab => !tab.indicators.includes("pinned")
);
this.pinnedTabs = this.tabItems.filter(tab =>
tab.indicators.includes("pinned")
);
if (this.maxTabsLength > 0) {
this.unpinnedTabs = this.unpinnedTabs.slice(0, this.maxTabsLength);
}
this.tabItems = [...this.pinnedTabs, ...this.unpinnedTabs];
} else if (this.maxTabsLength > 0) {
this.tabItems = this.tabItems.slice(0, this.maxTabsLength);
}
}
/**
* Focuses the expected element (either the link or button) within fxview-tab-row
* The currently focused/active element ID within a row is stored in this.currentActiveElementId
*/
handleFocusElementInRow(e) {
let fxviewTabRow = e.target;
if (e.code == "ArrowUp") {
// Focus either the link or button of the previous row based on this.currentActiveElementId
e.preventDefault();
if (
(this.pinnedTabsGridView &&
this.activeIndex >= this.pinnedTabs.length) ||
!this.pinnedTabsGridView
) {
this.focusPrevRow();
}
} else if (e.code == "ArrowDown") {
// Focus either the link or button of the next row based on this.currentActiveElementId
e.preventDefault();
if (
this.pinnedTabsGridView &&
this.activeIndex < this.pinnedTabs.length
) {
this.focusIndex(this.pinnedTabs.length);
} else {
this.focusNextRow();
}
} else if (e.code == "ArrowRight") {
// Focus either the link or the button in the current row and
// set this.currentActiveElementId to that element's ID
e.preventDefault();
if (document.dir == "rtl") {
fxviewTabRow.moveFocusLeft();
} else {
fxviewTabRow.moveFocusRight();
}
} else if (e.code == "ArrowLeft") {
// Focus either the link or the button in the current row and
// set this.currentActiveElementId to that element's ID
e.preventDefault();
if (document.dir == "rtl") {
fxviewTabRow.moveFocusRight();
} else {
fxviewTabRow.moveFocusLeft();
}
}
}
async focusIndex(index) {
// Focus link or button of item
if (
((this.pinnedTabsGridView && index > this.pinnedTabs.length) ||
!this.pinnedTabsGridView) &&
lazy.virtualListEnabledPref
) {
let row = this.rootVirtualListEl.getItem(index - this.pinnedTabs.length);
if (!row) {
return;
}
let subList = this.rootVirtualListEl.getSubListForItem(
index - this.pinnedTabs.length
);
if (!subList) {
return;
}
this.activeIndex = index;
// In Bug 1866845, these manual updates to the sublists should be removed
// and scrollIntoView() should also be iterated on so that we aren't constantly
// moving the focused item to the center of the viewport
for (const sublist of Array.from(this.rootVirtualListEl.children)) {
await sublist.requestUpdate();
await sublist.updateComplete;
}
row.scrollIntoView({ block: "center" });
row.focus();
} else if (index >= 0 && index < this.rowEls?.length) {
this.rowEls[index].focus();
this.activeIndex = index;
}
}
#getTabListWrapperClasses() {
let wrapperClasses = ["fxview-tab-list"];
let tabsToCheck = this.pinnedTabsGridView
? this.unpinnedTabs
: this.tabItems;
if (tabsToCheck.some(tab => tab.containerObj)) {
wrapperClasses.push(`hasContainerTab`);
}
return wrapperClasses;
}
itemTemplate = (tabItem, i) => {
let time;
if (tabItem.time || tabItem.closedAt) {
let stringTime = (tabItem.time || tabItem.closedAt).toString();
// Different APIs return time in different units, so we use
// the length to decide if it's milliseconds or nanoseconds.
if (stringTime.length === 16) {
time = (tabItem.time || tabItem.closedAt) / 1000;
} else {
time = tabItem.time || tabItem.closedAt;
}
}
return html`