453 lines
14 KiB
JavaScript
453 lines
14 KiB
JavaScript
/* 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 { MAX_TABS_FOR_RECENT_BROWSING } from "./helpers.mjs";
|
|
import { searchTabList } from "./search-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) {
|
|
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;
|
|
Glean.firefoxviewNext.recentlyClosedTabs.record({
|
|
position,
|
|
delta: deltaSeconds,
|
|
page: this.recentBrowsing ? "recentbrowsing" : "recentlyclosed",
|
|
});
|
|
if (this.searchQuery) {
|
|
Glean.firefoxview.cumulativeSearches[
|
|
this.recentBrowsing ? "recentbrowsing" : "recentlyclosed"
|
|
].accumulateSingleSample(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.sourceWindowId;
|
|
if (!isNaN(sourceClosedId)) {
|
|
// the sourceClosedId is an identifier for a now-closed window the tab
|
|
// was closed in.
|
|
lazy.SessionStore.forgetClosedTabById(closedId, {
|
|
sourceClosedId,
|
|
});
|
|
} else if (sourceWindowId) {
|
|
// the sourceWindowId is an identifier for a currently-open window the tab
|
|
// was closed in.
|
|
lazy.SessionStore.forgetClosedTabById(closedId, {
|
|
sourceWindowId,
|
|
});
|
|
} else {
|
|
// without either identifier, SessionStore will need to walk its window collections
|
|
// to find the close tab with matching closedId
|
|
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;
|
|
Glean.firefoxviewNext.dismissClosedTabTabs.record({
|
|
position,
|
|
delta: deltaSeconds,
|
|
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-2";
|
|
descriptionLabels = [
|
|
"firefoxview-dont-remember-history-empty-description-one",
|
|
];
|
|
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/history-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>
|
|
<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
|
|
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}
|
|
secondaryActionClass="dismiss-button"
|
|
></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;
|
|
Glean.firefoxviewNext.searchShowAllShowallbutton.record({
|
|
section: "recentlyclosed",
|
|
});
|
|
}
|
|
}
|
|
}
|
|
customElements.define("view-recentlyclosed", RecentlyClosedTabsInView);
|