diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /browser/components/firefoxview/recentlyclosed.mjs | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/firefoxview/recentlyclosed.mjs')
-rw-r--r-- | browser/components/firefoxview/recentlyclosed.mjs | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/browser/components/firefoxview/recentlyclosed.mjs b/browser/components/firefoxview/recentlyclosed.mjs new file mode 100644 index 0000000000..6e7e06c1f4 --- /dev/null +++ b/browser/components/firefoxview/recentlyclosed.mjs @@ -0,0 +1,473 @@ +/* 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, + when, +} from "chrome://global/content/vendor/lit.all.mjs"; +import { + isSearchEnabled, + searchTabList, + MAX_TABS_FOR_RECENT_BROWSING, +} from "./helpers.mjs"; +import { ViewPage } from "./viewpage.mjs"; +// eslint-disable-next-line import/no-unassigned-import +import "chrome://browser/content/firefoxview/card-container.mjs"; +// eslint-disable-next-line import/no-unassigned-import +import "chrome://browser/content/firefoxview/fxview-tab-list.mjs"; + +const lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs", +}); + +const SS_NOTIFY_CLOSED_OBJECTS_CHANGED = "sessionstore-closed-objects-changed"; +const SS_NOTIFY_BROWSER_SHUTDOWN_FLUSH = "sessionstore-browser-shutdown-flush"; +const NEVER_REMEMBER_HISTORY_PREF = "browser.privatebrowsing.autostart"; +const INCLUDE_CLOSED_TABS_FROM_CLOSED_WINDOWS = + "browser.sessionstore.closedTabsFromClosedWindows"; + +function getWindow() { + return window.browsingContext.embedderWindowGlobal.browsingContext.window; +} + +class RecentlyClosedTabsInView extends ViewPage { + constructor() { + super(); + this._started = false; + this.boundObserve = (...args) => this.observe(...args); + this.firstUpdateComplete = false; + this.fullyUpdated = false; + this.maxTabsLength = this.recentBrowsing + ? MAX_TABS_FOR_RECENT_BROWSING + : -1; + this.recentlyClosedTabs = []; + this.searchQuery = ""; + this.searchResults = null; + this.showAll = false; + this.cumulativeSearches = 0; + } + + static properties = { + ...ViewPage.properties, + searchResults: { type: Array }, + showAll: { type: Boolean }, + cumulativeSearches: { type: Number }, + }; + + static queries = { + cardEl: "card-container", + emptyState: "fxview-empty-state", + searchTextbox: "fxview-search-textbox", + tabList: "fxview-tab-list", + }; + + observe(subject, topic, data) { + if ( + topic == SS_NOTIFY_CLOSED_OBJECTS_CHANGED || + (topic == SS_NOTIFY_BROWSER_SHUTDOWN_FLUSH && + subject.ownerGlobal == getWindow()) + ) { + this.updateRecentlyClosedTabs(); + } + } + + start() { + if (this._started) { + return; + } + this._started = true; + this.paused = false; + this.updateRecentlyClosedTabs(); + + Services.obs.addObserver( + this.boundObserve, + SS_NOTIFY_CLOSED_OBJECTS_CHANGED + ); + Services.obs.addObserver( + this.boundObserve, + SS_NOTIFY_BROWSER_SHUTDOWN_FLUSH + ); + + if (this.recentBrowsing) { + this.recentBrowsingElement.addEventListener( + "fxview-search-textbox-query", + this + ); + } + + this.toggleVisibilityInCardContainer(); + } + + stop() { + if (!this._started) { + return; + } + this._started = false; + + Services.obs.removeObserver( + this.boundObserve, + SS_NOTIFY_CLOSED_OBJECTS_CHANGED + ); + Services.obs.removeObserver( + this.boundObserve, + SS_NOTIFY_BROWSER_SHUTDOWN_FLUSH + ); + + if (this.recentBrowsing) { + this.recentBrowsingElement.removeEventListener( + "fxview-search-textbox-query", + this + ); + } + + this.toggleVisibilityInCardContainer(); + } + + disconnectedCallback() { + super.disconnectedCallback(); + this.stop(); + } + + handleEvent(event) { + if (this.recentBrowsing && event.type === "fxview-search-textbox-query") { + this.onSearchQuery(event); + } + } + + // We remove all the observers when the instance is not visible to the user + viewHiddenCallback() { + this.stop(); + } + + // We add observers and check for changes to the session store once the user return to this tab. + // or the instance becomes visible to the user + viewVisibleCallback() { + this.start(); + } + + firstUpdated() { + this.firstUpdateComplete = true; + } + + getTabStateValue(tab, key) { + let value = ""; + const tabEntries = tab.state.entries; + const activeIndex = tab.state.index - 1; + + if (activeIndex >= 0 && tabEntries[activeIndex]) { + value = tabEntries[activeIndex][key]; + } + + return value; + } + + updateRecentlyClosedTabs() { + let recentlyClosedTabsData = lazy.SessionStore.getClosedTabData( + getWindow() + ); + if (Services.prefs.getBoolPref(INCLUDE_CLOSED_TABS_FROM_CLOSED_WINDOWS)) { + recentlyClosedTabsData.push( + ...lazy.SessionStore.getClosedTabDataFromClosedWindows() + ); + } + // sort the aggregated list to most-recently-closed first + recentlyClosedTabsData.sort((a, b) => a.closedAt < b.closedAt); + this.recentlyClosedTabs = recentlyClosedTabsData; + this.normalizeRecentlyClosedData(); + if (this.searchQuery) { + this.#updateSearchResults(); + } + this.requestUpdate(); + } + + normalizeRecentlyClosedData() { + // Normalize data for fxview-tabs-list + this.recentlyClosedTabs.forEach(recentlyClosedItem => { + const targetURI = this.getTabStateValue(recentlyClosedItem, "url"); + recentlyClosedItem.time = recentlyClosedItem.closedAt; + recentlyClosedItem.icon = recentlyClosedItem.image; + recentlyClosedItem.primaryL10nId = "fxviewtabrow-tabs-list-tab"; + recentlyClosedItem.primaryL10nArgs = JSON.stringify({ + targetURI: typeof targetURI === "string" ? targetURI : "", + }); + recentlyClosedItem.secondaryL10nId = + "firefoxview-closed-tabs-dismiss-tab"; + recentlyClosedItem.secondaryL10nArgs = JSON.stringify({ + tabTitle: recentlyClosedItem.title, + }); + recentlyClosedItem.url = targetURI; + }); + } + + onReopenTab(e) { + const closedId = parseInt(e.originalTarget.closedId, 10); + const sourceClosedId = parseInt(e.originalTarget.sourceClosedId, 10); + if (isNaN(sourceClosedId)) { + lazy.SessionStore.undoCloseById(closedId, getWindow()); + } else { + lazy.SessionStore.undoClosedTabFromClosedWindow( + { sourceClosedId }, + closedId, + getWindow() + ); + } + + // Record telemetry + let tabClosedAt = parseInt(e.originalTarget.time); + const position = + Array.from(this.tabList.rowEls).indexOf(e.originalTarget) + 1; + + let now = Date.now(); + let deltaSeconds = (now - tabClosedAt) / 1000; + Services.telemetry.recordEvent( + "firefoxview_next", + "recently_closed", + "tabs", + null, + { + position: position.toString(), + delta: deltaSeconds.toString(), + page: this.recentBrowsing ? "recentbrowsing" : "recentlyclosed", + } + ); + if (this.searchQuery) { + const searchesHistogram = Services.telemetry.getKeyedHistogramById( + "FIREFOX_VIEW_CUMULATIVE_SEARCHES" + ); + searchesHistogram.add( + this.recentBrowsing ? "recentbrowsing" : "recentlyclosed", + this.cumulativeSearches + ); + this.cumulativeSearches = 0; + } + } + + onDismissTab(e) { + const closedId = parseInt(e.originalTarget.closedId, 10); + const sourceClosedId = parseInt(e.originalTarget.sourceClosedId, 10); + const sourceWindowId = e.originalTarget.souceWindowId; + if (sourceWindowId || !isNaN(sourceClosedId)) { + lazy.SessionStore.forgetClosedTabById(closedId, { + sourceClosedId, + sourceWindowId, + }); + } else { + lazy.SessionStore.forgetClosedTabById(closedId); + } + + // Record telemetry + let tabClosedAt = parseInt(e.originalTarget.time); + const position = + Array.from(this.tabList.rowEls).indexOf(e.originalTarget) + 1; + + let now = Date.now(); + let deltaSeconds = (now - tabClosedAt) / 1000; + Services.telemetry.recordEvent( + "firefoxview_next", + "dismiss_closed_tab", + "tabs", + null, + { + position: position.toString(), + delta: deltaSeconds.toString(), + page: this.recentBrowsing ? "recentbrowsing" : "recentlyclosed", + } + ); + } + + willUpdate() { + this.fullyUpdated = false; + } + + updated() { + this.fullyUpdated = true; + this.toggleVisibilityInCardContainer(); + } + + async scheduleUpdate() { + // Only defer initial update + if (!this.firstUpdateComplete) { + await new Promise(resolve => setTimeout(resolve)); + } + super.scheduleUpdate(); + } + + emptyMessageTemplate() { + let descriptionHeader; + let descriptionLabels; + let descriptionLink; + if (Services.prefs.getBoolPref(NEVER_REMEMBER_HISTORY_PREF, false)) { + // History pref set to never remember history + descriptionHeader = "firefoxview-dont-remember-history-empty-header"; + descriptionLabels = [ + "firefoxview-dont-remember-history-empty-description", + "firefoxview-dont-remember-history-empty-description-two", + ]; + descriptionLink = { + url: "about:preferences#privacy", + name: "history-settings-url-two", + }; + } else { + descriptionHeader = "firefoxview-recentlyclosed-empty-header"; + descriptionLabels = [ + "firefoxview-recentlyclosed-empty-description", + "firefoxview-recentlyclosed-empty-description-two", + ]; + descriptionLink = { + url: "about:firefoxview#history", + name: "history-url", + sameTarget: "true", + }; + } + return html` + <fxview-empty-state + headerLabel=${descriptionHeader} + .descriptionLabels=${descriptionLabels} + .descriptionLink=${descriptionLink} + class="empty-state recentlyclosed" + ?isInnerCard=${this.recentBrowsing} + ?isSelectedTab=${this.selectedTab} + mainImageUrl="chrome://browser/content/firefoxview/recentlyclosed-empty.svg" + > + </fxview-empty-state> + `; + } + + render() { + return html` + <link + rel="stylesheet" + href="chrome://browser/content/firefoxview/firefoxview.css" + /> + ${when( + !this.recentBrowsing, + () => html`<div + class="sticky-container bottom-fade" + ?hidden=${!this.selectedTab} + > + <h2 + class="page-header" + data-l10n-id="firefoxview-recently-closed-header" + ></h2> + ${when( + isSearchEnabled(), + () => html`<div> + <fxview-search-textbox + data-l10n-id="firefoxview-search-text-box-recentlyclosed" + data-l10n-attrs="placeholder" + @fxview-search-textbox-query=${this.onSearchQuery} + .size=${this.searchTextboxSize} + pageName=${this.recentBrowsing + ? "recentbrowsing" + : "recentlyclosed"} + ></fxview-search-textbox> + </div>` + )} + </div>` + )} + <div class=${classMap({ "cards-container": this.selectedTab })}> + <card-container + shortPageName=${this.recentBrowsing ? "recentlyclosed" : null} + ?showViewAll=${this.recentBrowsing && this.recentlyClosedTabs.length} + ?preserveCollapseState=${this.recentBrowsing ? true : null} + ?hideHeader=${this.selectedTab} + ?hidden=${!this.recentlyClosedTabs.length && !this.recentBrowsing} + ?isEmptyState=${!this.recentlyClosedTabs.length} + > + <h3 + slot="header" + data-l10n-id="firefoxview-recently-closed-header" + ></h3> + ${when( + this.recentlyClosedTabs.length, + () => + html` + <fxview-tab-list + class="with-dismiss-button" + slot="main" + .maxTabsLength=${!this.recentBrowsing || this.showAll + ? -1 + : MAX_TABS_FOR_RECENT_BROWSING} + .searchQuery=${ifDefined( + this.searchResults && this.searchQuery + )} + .tabItems=${this.searchResults || this.recentlyClosedTabs} + @fxview-tab-list-secondary-action=${this.onDismissTab} + @fxview-tab-list-primary-action=${this.onReopenTab} + ></fxview-tab-list> + ` + )} + ${when( + this.recentBrowsing && !this.recentlyClosedTabs.length, + () => html` <div slot="main">${this.emptyMessageTemplate()}</div> ` + )} + ${when( + this.isShowAllLinkVisible(), + () => html` <div + @click=${this.enableShowAll} + @keydown=${this.enableShowAll} + data-l10n-id="firefoxview-show-all" + ?hidden=${!this.isShowAllLinkVisible()} + slot="footer" + tabindex="0" + role="link" + ></div>` + )} + </card-container> + ${when( + this.selectedTab && !this.recentlyClosedTabs.length, + () => html` <div>${this.emptyMessageTemplate()}</div> ` + )} + </div> + `; + } + + onSearchQuery(e) { + this.searchQuery = e.detail.query; + this.showAll = false; + this.cumulativeSearches = this.searchQuery + ? this.cumulativeSearches + 1 + : 0; + this.#updateSearchResults(); + } + + #updateSearchResults() { + this.searchResults = this.searchQuery + ? searchTabList(this.searchQuery, this.recentlyClosedTabs) + : null; + } + + isShowAllLinkVisible() { + return ( + this.recentBrowsing && + this.searchQuery && + this.searchResults.length > MAX_TABS_FOR_RECENT_BROWSING && + !this.showAll + ); + } + + enableShowAll(event) { + if ( + event.type == "click" || + (event.type == "keydown" && event.code == "Enter") || + (event.type == "keydown" && event.code == "Space") + ) { + event.preventDefault(); + this.showAll = true; + Services.telemetry.recordEvent( + "firefoxview_next", + "search_show_all", + "showallbutton", + null, + { + section: "recentlyclosed", + } + ); + } + } +} +customElements.define("view-recentlyclosed", RecentlyClosedTabsInView); |