/* 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/. */ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs", }); import { formatURIForDisplay, convertTimestamp, 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" ); const SS_NOTIFY_CLOSED_OBJECTS_CHANGED = "sessionstore-closed-objects-changed"; const SS_NOTIFY_BROWSER_SHUTDOWN_FLUSH = "sessionstore-browser-shutdown-flush"; const UI_OPEN_STATE = "browser.tabs.firefox-view.ui-state.recently-closed-tabs.open"; function getWindow() { return window.browsingContext.embedderWindowGlobal.browsingContext.window; } class RecentlyClosedTabsList extends MozLitElement { constructor() { super(); this.maxTabsLength = 25; this.recentlyClosedTabs = []; this.lastFocusedIndex = -1; // The recency timestamp update period is stored in a pref to allow tests to easily change it XPCOMUtils.defineLazyPreferenceGetter( lazy, "timeMsPref", "browser.tabs.firefox-view.updateTimeMs", NOW_THRESHOLD_MS, timeMsPref => { clearInterval(this.intervalID); this.intervalID = setInterval(() => this.requestUpdate(), timeMsPref); this.requestUpdate(); } ); } 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); } return this._fluentStrings; } connectedCallback() { super.connectedCallback(); this.intervalID = setInterval(() => this.requestUpdate(), lazy.timeMsPref); } disconnectedCallback() { clearInterval(this.intervalID); } getTabStateValue(tab, key) { let value = ""; const tabEntries = tab.entries; const activeIndex = tab.index - 1; if (activeIndex >= 0 && tabEntries[activeIndex]) { value = tabEntries[activeIndex][key]; } return value; } openTabAndUpdate(event) { if ( (event.type == "click" && !event.altKey) || (event.type == "keydown" && event.code == "Enter") || (event.type == "keydown" && event.code == "Space") ) { 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, undefined, getWindow()); // record telemetry let tabClosedAt = parseInt( item.querySelector(".closed-tab-li-time").getAttribute("data-timestamp") ); let now = Date.now(); let deltaSeconds = (now - tabClosedAt) / 1000; Services.telemetry.recordEvent( "firefoxview", "recently_closed", "tabs", null, { position: position.toString(), delta: deltaSeconds.toString(), } ); } } dismissTabAndUpdate(event) { event.preventDefault(); const item = event.target.closest(".closed-tab-li"); this.dismissTabAndUpdateForElement(item); } dismissTabAndUpdateForElement(item) { const sourceWindow = lazy.SessionStore.getWindowById( item.dataset.sourceWindowId ); let recentlyClosedList = lazy.SessionStore.getClosedTabDataForWindow(sourceWindow); let closedTabIndex = recentlyClosedList.findIndex(closedTab => { return closedTab.closedId === parseInt(item.dataset.tabid, 10); }); if (closedTabIndex < 0) { // Tab not found in recently closed list return; } // in order forget a closed tab, we need to pass the window the closed tab data is associated with lazy.SessionStore.forgetClosedTab(sourceWindow, closedTabIndex); // record telemetry let tabClosedAt = parseInt( item.querySelector(".closed-tab-li-time").dataset.timestamp ); let now = Date.now(); let deltaSeconds = (now - tabClosedAt) / 1000; Services.telemetry.recordEvent( "firefoxview", "dismiss_closed_tab", "tabs", null, { delta: deltaSeconds.toString(), } ); } getClosedTabsDataForOpenWindows() { // get closed tabs in currently-open windows const closedTabsData = lazy.SessionStore.getClosedTabData(getWindow()).map( tabData => { // flatten the object; move properties of `.state` into the top-level object const stateData = tabData.state; delete tabData.state; return { ...tabData, ...stateData, }; } ); return closedTabsData; } updateRecentlyClosedTabs() { // add recently-closed tabs from currently-open windows const recentlyClosedTabsData = this.getClosedTabsDataForOpenWindows(); recentlyClosedTabsData.sort((a, b) => a.closedAt < b.closedAt); this.recentlyClosedTabs = recentlyClosedTabsData.slice( 0, this.maxTabsLength ); this.requestUpdate(); } 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()} `; } closedTabsContainer.toggleContainerStyleForEmptyMsg(false); return html`