From a90a5cba08fdf6c0ceb95101c275108a152a3aed Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 12 Jun 2024 07:35:37 +0200 Subject: Merging upstream version 127.0. Signed-off-by: Daniel Baumann --- .../components/firefoxview/HistoryController.mjs | 188 ---------- .../firefoxview/HistoryController.sys.mjs | 383 ++++++++++++++++++++ .../firefoxview/SyncedTabsController.sys.mjs | 2 +- browser/components/firefoxview/card-container.mjs | 131 ++++--- .../firefox-view-notification-manager.sys.mjs | 112 ------ .../firefoxview/firefox-view-places-query.sys.mjs | 187 ---------- browser/components/firefoxview/firefoxview.css | 11 - browser/components/firefoxview/firefoxview.html | 1 - .../components/firefoxview/fxview-empty-state.mjs | 31 +- .../firefoxview/fxview-search-textbox.mjs | 2 +- browser/components/firefoxview/fxview-tab-list.mjs | 12 +- browser/components/firefoxview/helpers.mjs | 21 -- browser/components/firefoxview/history.mjs | 67 ++-- browser/components/firefoxview/jar.mn | 2 +- browser/components/firefoxview/opentabs.mjs | 2 +- browser/components/firefoxview/recentlyclosed.mjs | 7 +- browser/components/firefoxview/search-helpers.mjs | 24 ++ browser/components/firefoxview/syncedtabs.mjs | 1 - .../firefoxview/tests/browser/browser.toml | 3 - .../tests/browser/browser_firefoxview.js | 39 +- .../browser/browser_firefoxview_virtual_list.js | 4 +- .../tests/browser/browser_history_firefoxview.js | 83 ++++- .../tests/browser/browser_notification_dot.js | 392 --------------------- .../browser/browser_syncedtabs_firefoxview.js | 160 +++++++++ .../tests/chrome/test_fxview_tab_list.html | 21 +- 25 files changed, 806 insertions(+), 1080 deletions(-) delete mode 100644 browser/components/firefoxview/HistoryController.mjs create mode 100644 browser/components/firefoxview/HistoryController.sys.mjs delete mode 100644 browser/components/firefoxview/firefox-view-notification-manager.sys.mjs delete mode 100644 browser/components/firefoxview/firefox-view-places-query.sys.mjs create mode 100644 browser/components/firefoxview/search-helpers.mjs delete mode 100644 browser/components/firefoxview/tests/browser/browser_notification_dot.js (limited to 'browser/components/firefoxview') diff --git a/browser/components/firefoxview/HistoryController.mjs b/browser/components/firefoxview/HistoryController.mjs deleted file mode 100644 index d2bda5cec2..0000000000 --- a/browser/components/firefoxview/HistoryController.mjs +++ /dev/null @@ -1,188 +0,0 @@ -/* 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, { - FirefoxViewPlacesQuery: - "resource:///modules/firefox-view-places-query.sys.mjs", - PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", -}); - -let XPCOMUtils = ChromeUtils.importESModule( - "resource://gre/modules/XPCOMUtils.sys.mjs" -).XPCOMUtils; - -XPCOMUtils.defineLazyPreferenceGetter( - lazy, - "maxRowsPref", - "browser.firefox-view.max-history-rows", - -1 -); - -const HISTORY_MAP_L10N_IDS = { - sidebar: { - "history-date-today": "sidebar-history-date-today", - "history-date-yesterday": "sidebar-history-date-yesterday", - "history-date-this-month": "sidebar-history-date-this-month", - "history-date-prev-month": "sidebar-history-date-prev-month", - }, - firefoxview: { - "history-date-today": "firefoxview-history-date-today", - "history-date-yesterday": "firefoxview-history-date-yesterday", - "history-date-this-month": "firefoxview-history-date-this-month", - "history-date-prev-month": "firefoxview-history-date-prev-month", - }, -}; - -export class HistoryController { - host; - allHistoryItems; - historyMapByDate; - historyMapBySite; - searchQuery; - searchResults; - sortOption; - - constructor(host, options) { - this.allHistoryItems = new Map(); - this.historyMapByDate = []; - this.historyMapBySite = []; - this.placesQuery = new lazy.FirefoxViewPlacesQuery(); - this.searchQuery = ""; - this.searchResults = null; - this.sortOption = "date"; - this.searchResultsLimit = options?.searchResultsLimit || 300; - this.component = HISTORY_MAP_L10N_IDS?.[options?.component] - ? options?.component - : "firefoxview"; - this.host = host; - - host.addController(this); - } - - async hostConnected() { - this.placesQuery.observeHistory(data => this.updateAllHistoryItems(data)); - await this.updateHistoryData(); - this.createHistoryMaps(); - } - - hostDisconnected() { - this.placesQuery.close(); - } - - deleteFromHistory() { - lazy.PlacesUtils.history.remove(this.host.triggerNode.url); - } - - async onSearchQuery(e) { - this.searchQuery = e.detail.query; - await this.updateSearchResults(); - this.host.requestUpdate(); - } - - async onChangeSortOption(e) { - this.sortOption = e.target.value; - await this.updateHistoryData(); - await this.updateSearchResults(); - this.host.requestUpdate(); - } - - async updateHistoryData() { - this.allHistoryItems = await this.placesQuery.getHistory({ - daysOld: 60, - limit: lazy.maxRowsPref, - sortBy: this.sortOption, - }); - } - - async updateAllHistoryItems(allHistoryItems) { - if (allHistoryItems) { - this.allHistoryItems = allHistoryItems; - } else { - await this.updateHistoryData(); - } - this.resetHistoryMaps(); - this.host.requestUpdate(); - await this.updateSearchResults(); - } - - async updateSearchResults() { - if (this.searchQuery) { - try { - this.searchResults = await this.placesQuery.searchHistory( - this.searchQuery, - this.searchResultsLimit - ); - } catch (e) { - // Connection interrupted, ignore. - } - } else { - this.searchResults = null; - } - } - - resetHistoryMaps() { - this.historyMapByDate = []; - this.historyMapBySite = []; - } - - createHistoryMaps() { - if (!this.historyMapByDate.length) { - const { - visitsFromToday, - visitsFromYesterday, - visitsByDay, - visitsByMonth, - } = this.placesQuery; - - // Add visits from today and yesterday. - if (visitsFromToday.length) { - this.historyMapByDate.push({ - l10nId: HISTORY_MAP_L10N_IDS[this.component]["history-date-today"], - items: visitsFromToday, - }); - } - if (visitsFromYesterday.length) { - this.historyMapByDate.push({ - l10nId: - HISTORY_MAP_L10N_IDS[this.component]["history-date-yesterday"], - items: visitsFromYesterday, - }); - } - - // Add visits from this month, grouped by day. - visitsByDay.forEach(visits => { - this.historyMapByDate.push({ - l10nId: - HISTORY_MAP_L10N_IDS[this.component]["history-date-this-month"], - items: visits, - }); - }); - - // Add visits from previous months, grouped by month. - visitsByMonth.forEach(visits => { - this.historyMapByDate.push({ - l10nId: - HISTORY_MAP_L10N_IDS[this.component]["history-date-prev-month"], - items: visits, - }); - }); - } else if ( - this.sortOption === "site" && - !this.historyMapBySite.length && - this.component === "firefoxview" - ) { - this.historyMapBySite = Array.from( - this.allHistoryItems.entries(), - ([domain, items]) => ({ - domain, - items, - l10nId: domain ? null : "firefoxview-history-site-localhost", - }) - ).sort((a, b) => a.domain.localeCompare(b.domain)); - } - this.host.requestUpdate(); - } -} diff --git a/browser/components/firefoxview/HistoryController.sys.mjs b/browser/components/firefoxview/HistoryController.sys.mjs new file mode 100644 index 0000000000..b6f316e8e7 --- /dev/null +++ b/browser/components/firefoxview/HistoryController.sys.mjs @@ -0,0 +1,383 @@ +/* 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 = {}; + +import { getLogger } from "chrome://browser/content/firefoxview/helpers.mjs"; + +ChromeUtils.defineESModuleGetters(lazy, { + PlacesQuery: "resource://gre/modules/PlacesQuery.sys.mjs", + PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", +}); + +let XPCOMUtils = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +).XPCOMUtils; + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "maxRowsPref", + "browser.firefox-view.max-history-rows", + -1 +); + +const HISTORY_MAP_L10N_IDS = { + sidebar: { + "history-date-today": "sidebar-history-date-today", + "history-date-yesterday": "sidebar-history-date-yesterday", + "history-date-this-month": "sidebar-history-date-this-month", + "history-date-prev-month": "sidebar-history-date-prev-month", + }, + firefoxview: { + "history-date-today": "firefoxview-history-date-today", + "history-date-yesterday": "firefoxview-history-date-yesterday", + "history-date-this-month": "firefoxview-history-date-this-month", + "history-date-prev-month": "firefoxview-history-date-prev-month", + }, +}; + +/** + * A list of visits displayed on a card. + * + * @typedef {object} CardEntry + * + * @property {string} domain + * @property {HistoryVisit[]} items + * @property {string} l10nId + */ + +export class HistoryController { + /** + * @type {{ entries: CardEntry[]; searchQuery: string; sortOption: string; }} + */ + historyCache; + host; + searchQuery; + sortOption; + #todaysDate; + #yesterdaysDate; + + constructor(host, options) { + this.placesQuery = new lazy.PlacesQuery(); + this.searchQuery = ""; + this.sortOption = "date"; + this.searchResultsLimit = options?.searchResultsLimit || 300; + this.component = HISTORY_MAP_L10N_IDS?.[options?.component] + ? options?.component + : "firefoxview"; + this.historyCache = { + entries: [], + searchQuery: null, + sortOption: null, + }; + this.host = host; + + host.addController(this); + } + + hostConnected() { + this.placesQuery.observeHistory(historyMap => this.updateCache(historyMap)); + } + + hostDisconnected() { + ChromeUtils.idleDispatch(() => this.placesQuery.close()); + } + + deleteFromHistory() { + lazy.PlacesUtils.history.remove(this.host.triggerNode.url); + } + + onSearchQuery(e) { + this.searchQuery = e.detail.query; + this.updateCache(); + } + + onChangeSortOption(e) { + this.sortOption = e.target.value; + this.updateCache(); + } + + get historyVisits() { + return this.historyCache.entries; + } + + get searchResults() { + return this.historyCache.searchQuery + ? this.historyCache.entries[0].items + : null; + } + + get totalVisitsCount() { + return this.historyVisits.reduce( + (count, entry) => count + entry.items.length, + 0 + ); + } + + get isHistoryEmpty() { + return !this.historyVisits.length; + } + + /** + * Update cached history. + * + * @param {Map} [historyMap] + * If provided, performs an update using the given data (instead of fetching + * it from the db). + */ + async updateCache(historyMap) { + const { searchQuery, sortOption } = this; + const entries = searchQuery + ? await this.#getVisitsForSearchQuery(searchQuery) + : await this.#getVisitsForSortOption(sortOption, historyMap); + if (this.searchQuery !== searchQuery || this.sortOption !== sortOption) { + // This query is stale, discard results and do not update the cache / UI. + return; + } + for (const { items } of entries) { + for (const item of items) { + this.#normalizeVisit(item); + } + } + this.historyCache = { entries, searchQuery, sortOption }; + this.host.requestUpdate(); + } + + /** + * Normalize data for fxview-tabs-list. + * + * @param {HistoryVisit} visit + * The visit to format. + */ + #normalizeVisit(visit) { + visit.time = visit.date.getTime(); + visit.title = visit.title || visit.url; + visit.icon = `page-icon:${visit.url}`; + visit.primaryL10nId = "fxviewtabrow-tabs-list-tab"; + visit.primaryL10nArgs = JSON.stringify({ + targetURI: visit.url, + }); + visit.secondaryL10nId = "fxviewtabrow-options-menu-button"; + visit.secondaryL10nArgs = JSON.stringify({ + tabTitle: visit.title || visit.url, + }); + } + + async #getVisitsForSearchQuery(searchQuery) { + let items = []; + try { + items = await this.placesQuery.searchHistory( + searchQuery, + this.searchResultsLimit + ); + } catch (e) { + getLogger("HistoryController").warn( + "There is a new search query in progress, so cancelling this one.", + e + ); + } + return [{ items }]; + } + + async #getVisitsForSortOption(sortOption, historyMap) { + if (!historyMap) { + historyMap = await this.#fetchHistory(); + } + switch (sortOption) { + case "date": + this.#setTodaysDate(); + return this.#getVisitsForDate(historyMap); + case "site": + return this.#getVisitsForSite(historyMap); + default: + return []; + } + } + + #setTodaysDate() { + const now = new Date(); + this.#todaysDate = new Date( + now.getFullYear(), + now.getMonth(), + now.getDate() + ); + this.#yesterdaysDate = new Date( + now.getFullYear(), + now.getMonth(), + now.getDate() - 1 + ); + } + + /** + * Get a list of visits, sorted by date, in reverse chronological order. + * + * @param {Map} historyMap + * @returns {CardEntry[]} + */ + #getVisitsForDate(historyMap) { + const entries = []; + const visitsFromToday = this.#getVisitsFromToday(historyMap); + const visitsFromYesterday = this.#getVisitsFromYesterday(historyMap); + const visitsByDay = this.#getVisitsByDay(historyMap); + const visitsByMonth = this.#getVisitsByMonth(historyMap); + + // Add visits from today and yesterday. + if (visitsFromToday.length) { + entries.push({ + l10nId: HISTORY_MAP_L10N_IDS[this.component]["history-date-today"], + items: visitsFromToday, + }); + } + if (visitsFromYesterday.length) { + entries.push({ + l10nId: HISTORY_MAP_L10N_IDS[this.component]["history-date-yesterday"], + items: visitsFromYesterday, + }); + } + + // Add visits from this month, grouped by day. + visitsByDay.forEach(visits => { + entries.push({ + l10nId: HISTORY_MAP_L10N_IDS[this.component]["history-date-this-month"], + items: visits, + }); + }); + + // Add visits from previous months, grouped by month. + visitsByMonth.forEach(visits => { + entries.push({ + l10nId: HISTORY_MAP_L10N_IDS[this.component]["history-date-prev-month"], + items: visits, + }); + }); + return entries; + } + + #getVisitsFromToday(cachedHistory) { + const mapKey = this.placesQuery.getStartOfDayTimestamp(this.#todaysDate); + const visits = cachedHistory.get(mapKey) ?? []; + return [...visits]; + } + + #getVisitsFromYesterday(cachedHistory) { + const mapKey = this.placesQuery.getStartOfDayTimestamp( + this.#yesterdaysDate + ); + const visits = cachedHistory.get(mapKey) ?? []; + return [...visits]; + } + + /** + * Get a list of visits per day for each day on this month, excluding today + * and yesterday. + * + * @param {Map} cachedHistory + * The history cache to process. + * @returns {HistoryVisit[][]} + * A list of visits for each day. + */ + #getVisitsByDay(cachedHistory) { + const visitsPerDay = []; + for (const [time, visits] of cachedHistory.entries()) { + const date = new Date(time); + if ( + this.#isSameDate(date, this.#todaysDate) || + this.#isSameDate(date, this.#yesterdaysDate) + ) { + continue; + } else if (!this.#isSameMonth(date, this.#todaysDate)) { + break; + } else { + visitsPerDay.push(visits); + } + } + return visitsPerDay; + } + + /** + * Get a list of visits per month for each month, excluding this one, and + * excluding yesterday's visits if yesterday happens to fall on the previous + * month. + * + * @param {Map} cachedHistory + * The history cache to process. + * @returns {HistoryVisit[][]} + * A list of visits for each month. + */ + #getVisitsByMonth(cachedHistory) { + const visitsPerMonth = []; + let previousMonth = null; + for (const [time, visits] of cachedHistory.entries()) { + const date = new Date(time); + if ( + this.#isSameMonth(date, this.#todaysDate) || + this.#isSameDate(date, this.#yesterdaysDate) + ) { + continue; + } + const month = this.placesQuery.getStartOfMonthTimestamp(date); + if (month !== previousMonth) { + visitsPerMonth.push(visits); + } else { + visitsPerMonth[visitsPerMonth.length - 1] = visitsPerMonth + .at(-1) + .concat(visits); + } + previousMonth = month; + } + return visitsPerMonth; + } + + /** + * Given two date instances, check if their dates are equivalent. + * + * @param {Date} dateToCheck + * @param {Date} date + * @returns {boolean} + * Whether both date instances have equivalent dates. + */ + #isSameDate(dateToCheck, date) { + return ( + dateToCheck.getDate() === date.getDate() && + this.#isSameMonth(dateToCheck, date) + ); + } + + /** + * Given two date instances, check if their months are equivalent. + * + * @param {Date} dateToCheck + * @param {Date} month + * @returns {boolean} + * Whether both date instances have equivalent months. + */ + #isSameMonth(dateToCheck, month) { + return ( + dateToCheck.getMonth() === month.getMonth() && + dateToCheck.getFullYear() === month.getFullYear() + ); + } + + /** + * Get a list of visits, sorted by site, in alphabetical order. + * + * @param {Map} historyMap + * @returns {CardEntry[]} + */ + #getVisitsForSite(historyMap) { + return Array.from(historyMap.entries(), ([domain, items]) => ({ + domain, + items, + l10nId: domain ? null : "firefoxview-history-site-localhost", + })).sort((a, b) => a.domain.localeCompare(b.domain)); + } + + async #fetchHistory() { + return this.placesQuery.getHistory({ + daysOld: 60, + limit: lazy.maxRowsPref, + sortBy: this.sortOption, + }); + } +} diff --git a/browser/components/firefoxview/SyncedTabsController.sys.mjs b/browser/components/firefoxview/SyncedTabsController.sys.mjs index 6ab8249bfe..9462766545 100644 --- a/browser/components/firefoxview/SyncedTabsController.sys.mjs +++ b/browser/components/firefoxview/SyncedTabsController.sys.mjs @@ -10,7 +10,7 @@ ChromeUtils.defineESModuleGetters(lazy, { import { SyncedTabsErrorHandler } from "resource:///modules/firefox-view-synced-tabs-error-handler.sys.mjs"; import { TabsSetupFlowManager } from "resource:///modules/firefox-view-tabs-setup-manager.sys.mjs"; -import { searchTabList } from "chrome://browser/content/firefoxview/helpers.mjs"; +import { searchTabList } from "chrome://browser/content/firefoxview/search-helpers.mjs"; const SYNCED_TABS_CHANGED = "services.sync.tabs.changed"; const TOPIC_SETUPSTATE_CHANGED = "firefox-view.setupstate.changed"; diff --git a/browser/components/firefoxview/card-container.mjs b/browser/components/firefoxview/card-container.mjs index 1755d97555..84c6acc5c4 100644 --- a/browser/components/firefoxview/card-container.mjs +++ b/browser/components/firefoxview/card-container.mjs @@ -132,76 +132,71 @@ class CardContainer extends MozLitElement { rel="stylesheet" href="chrome://browser/content/firefoxview/card-container.css" /> -
- ${when( - this.toggleDisabled, - () => html`
html`
+ - - - - - - - -
`, - () => html`
+ + + + + +
`, + () => html`
+ - - - - - - - -
` - )} -
+ + + + + + + ` + )} `; } } diff --git a/browser/components/firefoxview/firefox-view-notification-manager.sys.mjs b/browser/components/firefoxview/firefox-view-notification-manager.sys.mjs deleted file mode 100644 index 3f9056a7cd..0000000000 --- a/browser/components/firefoxview/firefox-view-notification-manager.sys.mjs +++ /dev/null @@ -1,112 +0,0 @@ -/* 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/. */ - -/** - * This module exports the FirefoxViewNotificationManager singleton, which manages the notification state - * for the Firefox View button - */ - -const RECENT_TABS_SYNC = "services.sync.lastTabFetch"; -const SHOULD_NOTIFY_FOR_TABS = "browser.tabs.firefox-view.notify-for-tabs"; -const lazy = {}; - -import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; - -ChromeUtils.defineESModuleGetters(lazy, { - BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs", - SyncedTabs: "resource://services-sync/SyncedTabs.sys.mjs", -}); - -export const FirefoxViewNotificationManager = new (class { - #currentlyShowing; - constructor() { - XPCOMUtils.defineLazyPreferenceGetter( - this, - "lastTabFetch", - RECENT_TABS_SYNC, - 0, - () => { - this.handleTabSync(); - } - ); - XPCOMUtils.defineLazyPreferenceGetter( - this, - "shouldNotifyForTabs", - SHOULD_NOTIFY_FOR_TABS, - false - ); - // Need to access the pref variable for the observer to start observing - // See the defineLazyPreferenceGetter function header - this.lastTabFetch; - - Services.obs.addObserver(this, "firefoxview-notification-dot-update"); - - this.#currentlyShowing = false; - } - - async handleTabSync() { - if (!this.shouldNotifyForTabs) { - return; - } - let newSyncedTabs = await lazy.SyncedTabs.getRecentTabs(3); - this.#currentlyShowing = this.tabsListChanged(newSyncedTabs); - this.showNotificationDot(); - this.syncedTabs = newSyncedTabs; - } - - showNotificationDot() { - if (this.#currentlyShowing) { - Services.obs.notifyObservers( - null, - "firefoxview-notification-dot-update", - "true" - ); - } - } - - observe(sub, topic, data) { - if (topic === "firefoxview-notification-dot-update" && data === "false") { - this.#currentlyShowing = false; - } - } - - tabsListChanged(newTabs) { - // The first time the tabs list is changed this.tabs is undefined because we haven't synced yet. - // We don't want to show the badge here because it's not an actual change, - // we are just syncing for the first time. - if (!this.syncedTabs) { - return false; - } - - // We loop through all windows to see if any window has currentURI "about:firefoxview" and - // the window is visible because we don't want to show the notification badge in that case - for (let window of lazy.BrowserWindowTracker.orderedWindows) { - // if the url is "about:firefoxview" and the window visible we don't want to show the notification badge - if ( - window.FirefoxViewHandler.tab?.selected && - !window.isFullyOccluded && - window.windowState !== window.STATE_MINIMIZED - ) { - return false; - } - } - - if (newTabs.length > this.syncedTabs.length) { - return true; - } - for (let i = 0; i < newTabs.length; i++) { - let newTab = newTabs[i]; - let oldTab = this.syncedTabs[i]; - - if (newTab?.url !== oldTab?.url) { - return true; - } - } - return false; - } - - shouldNotificationDotBeShowing() { - return this.#currentlyShowing; - } -})(); diff --git a/browser/components/firefoxview/firefox-view-places-query.sys.mjs b/browser/components/firefoxview/firefox-view-places-query.sys.mjs deleted file mode 100644 index 8923905769..0000000000 --- a/browser/components/firefoxview/firefox-view-places-query.sys.mjs +++ /dev/null @@ -1,187 +0,0 @@ -/* 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 { PlacesQuery } from "resource://gre/modules/PlacesQuery.sys.mjs"; - -/** - * Extension of PlacesQuery which provides additional caches for Firefox View. - */ -export class FirefoxViewPlacesQuery extends PlacesQuery { - /** @type {Date} */ - #todaysDate = null; - /** @type {Date} */ - #yesterdaysDate = null; - - get visitsFromToday() { - if (this.cachedHistory == null || this.#todaysDate == null) { - return []; - } - const mapKey = this.getStartOfDayTimestamp(this.#todaysDate); - return this.cachedHistory.get(mapKey) ?? []; - } - - get visitsFromYesterday() { - if (this.cachedHistory == null || this.#yesterdaysDate == null) { - return []; - } - const mapKey = this.getStartOfDayTimestamp(this.#yesterdaysDate); - return this.cachedHistory.get(mapKey) ?? []; - } - - /** - * Get a list of visits per day for each day on this month, excluding today - * and yesterday. - * - * @returns {HistoryVisit[][]} - * A list of visits for each day. - */ - get visitsByDay() { - const visitsPerDay = []; - for (const [time, visits] of this.cachedHistory.entries()) { - const date = new Date(time); - if ( - this.#isSameDate(date, this.#todaysDate) || - this.#isSameDate(date, this.#yesterdaysDate) - ) { - continue; - } else if (!this.#isSameMonth(date, this.#todaysDate)) { - break; - } else { - visitsPerDay.push(visits); - } - } - return visitsPerDay; - } - - /** - * Get a list of visits per month for each month, excluding this one, and - * excluding yesterday's visits if yesterday happens to fall on the previous - * month. - * - * @returns {HistoryVisit[][]} - * A list of visits for each month. - */ - get visitsByMonth() { - const visitsPerMonth = []; - let previousMonth = null; - for (const [time, visits] of this.cachedHistory.entries()) { - const date = new Date(time); - if ( - this.#isSameMonth(date, this.#todaysDate) || - this.#isSameDate(date, this.#yesterdaysDate) - ) { - continue; - } - const month = this.getStartOfMonthTimestamp(date); - if (month !== previousMonth) { - visitsPerMonth.push(visits); - } else { - visitsPerMonth[visitsPerMonth.length - 1] = visitsPerMonth - .at(-1) - .concat(visits); - } - previousMonth = month; - } - return visitsPerMonth; - } - - formatRowAsVisit(row) { - const visit = super.formatRowAsVisit(row); - this.#normalizeVisit(visit); - return visit; - } - - formatEventAsVisit(event) { - const visit = super.formatEventAsVisit(event); - this.#normalizeVisit(visit); - return visit; - } - - /** - * Normalize data for fxview-tabs-list. - * - * @param {HistoryVisit} visit - * The visit to format. - */ - #normalizeVisit(visit) { - visit.time = visit.date.getTime(); - visit.title = visit.title || visit.url; - visit.icon = `page-icon:${visit.url}`; - visit.primaryL10nId = "fxviewtabrow-tabs-list-tab"; - visit.primaryL10nArgs = JSON.stringify({ - targetURI: visit.url, - }); - visit.secondaryL10nId = "fxviewtabrow-options-menu-button"; - visit.secondaryL10nArgs = JSON.stringify({ - tabTitle: visit.title || visit.url, - }); - } - - async fetchHistory() { - await super.fetchHistory(); - if (this.cachedHistoryOptions.sortBy === "date") { - this.#setTodaysDate(); - } - } - - handlePageVisited(event) { - const visit = super.handlePageVisited(event); - if (!visit) { - return; - } - if ( - this.cachedHistoryOptions.sortBy === "date" && - (this.#todaysDate == null || - (visit.date.getTime() > this.#todaysDate.getTime() && - !this.#isSameDate(visit.date, this.#todaysDate))) - ) { - // If today's date has passed (or is null), it should be updated now. - this.#setTodaysDate(); - } - } - - #setTodaysDate() { - const now = new Date(); - this.#todaysDate = new Date( - now.getFullYear(), - now.getMonth(), - now.getDate() - ); - this.#yesterdaysDate = new Date( - now.getFullYear(), - now.getMonth(), - now.getDate() - 1 - ); - } - - /** - * Given two date instances, check if their dates are equivalent. - * - * @param {Date} dateToCheck - * @param {Date} date - * @returns {boolean} - * Whether both date instances have equivalent dates. - */ - #isSameDate(dateToCheck, date) { - return ( - dateToCheck.getDate() === date.getDate() && - this.#isSameMonth(dateToCheck, date) - ); - } - - /** - * Given two date instances, check if their months are equivalent. - * - * @param {Date} dateToCheck - * @param {Date} month - * @returns {boolean} - * Whether both date instances have equivalent months. - */ - #isSameMonth(dateToCheck, month) { - return ( - dateToCheck.getMonth() === month.getMonth() && - dateToCheck.getFullYear() === month.getFullYear() - ); - } -} diff --git a/browser/components/firefoxview/firefoxview.css b/browser/components/firefoxview/firefoxview.css index a91c90c39e..0789c887bf 100644 --- a/browser/components/firefoxview/firefoxview.css +++ b/browser/components/firefoxview/firefoxview.css @@ -31,17 +31,6 @@ --newtab-background-color: #F9F9FB; --fxview-card-header-font-weight: 500; - - /* Make the attention dot color match the browser UI on Linux, and on HCM - * with a lightweight theme. */ - &[lwtheme] { - --attention-dot-color: light-dark(#2ac3a2, #54ffbd); - } - @media (-moz-platform: linux) { - &:not([lwtheme]) { - --attention-dot-color: AccentColor; - } - } } @media (prefers-color-scheme: dark) { diff --git a/browser/components/firefoxview/firefoxview.html b/browser/components/firefoxview/firefoxview.html index 5bffb5a1d8..bdaa41bd7c 100644 --- a/browser/components/firefoxview/firefoxview.html +++ b/browser/components/firefoxview/firefoxview.html @@ -13,7 +13,6 @@ - diff --git a/browser/components/firefoxview/fxview-empty-state.mjs b/browser/components/firefoxview/fxview-empty-state.mjs index 9e6bc488fa..3e94767043 100644 --- a/browser/components/firefoxview/fxview-empty-state.mjs +++ b/browser/components/firefoxview/fxview-empty-state.mjs @@ -53,7 +53,6 @@ class FxviewEmptyState extends MozLitElement { return html``; } return html` + }" id="card-container" isEmptyState="true" role="group" aria-labelledby="header" aria-describedby="description">
- ${repeat( - this.descriptionLabels, - descLabel => descLabel, - (descLabel, index) => html`

- ${this.linkTemplate(this.descriptionLink)} -

` - )} + + ${repeat( + this.descriptionLabels, + descLabel => descLabel, + (descLabel, index) => html`

+ ${this.linkTemplate(this.descriptionLink)} +

` + )} +
diff --git a/browser/components/firefoxview/fxview-search-textbox.mjs b/browser/components/firefoxview/fxview-search-textbox.mjs index 1332f5f3f6..107aa8f7a4 100644 --- a/browser/components/firefoxview/fxview-search-textbox.mjs +++ b/browser/components/firefoxview/fxview-search-textbox.mjs @@ -20,7 +20,7 @@ const SEARCH_DEBOUNCE_TIMEOUT_MS = 1000; * * There is no actual searching done here. That needs to be implemented by the * `fxview-search-textbox-query` event handler. `searchTabList()` from - * `helpers.mjs` can be used as a starting point. + * `search-helpers.mjs` can be used as a starting point. * * @property {string} placeholder * The placeholder text for the search box. diff --git a/browser/components/firefoxview/fxview-tab-list.mjs b/browser/components/firefoxview/fxview-tab-list.mjs index 57181e3bea..63be9379db 100644 --- a/browser/components/firefoxview/fxview-tab-list.mjs +++ b/browser/components/firefoxview/fxview-tab-list.mjs @@ -11,7 +11,7 @@ import { when, } from "chrome://global/content/vendor/lit.all.mjs"; import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; -import { escapeRegExp } from "./helpers.mjs"; +import { escapeRegExp } from "./search-helpers.mjs"; // eslint-disable-next-line import/no-unassigned-import import "chrome://global/content/elements/moz-button.mjs"; @@ -49,7 +49,6 @@ if (!window.IS_STORYBOOK) { * @property {number} maxTabsLength - The max number of tabs for the list * @property {Array} tabItems - Items to show in the tab list * @property {string} searchQuery - The query string to highlight, if provided. - * @property {string} searchInProgress - Whether a search has been initiated. * @property {string} secondaryActionClass - The class used to style the secondary action element * @property {string} tertiaryActionClass - The class used to style the tertiary action element */ @@ -65,7 +64,6 @@ export class FxviewTabListBase extends MozLitElement { this.maxTabsLength = 25; this.tabItems = []; this.compactRows = false; - this.searchInProgress = false; this.updatesPaused = true; this.#register(); } @@ -80,12 +78,12 @@ export class FxviewTabListBase extends MozLitElement { tabItems: { type: Array }, updatesPaused: { type: Boolean }, searchQuery: { type: String }, - searchInProgress: { type: Boolean }, secondaryActionClass: { type: String }, tertiaryActionClass: { type: String }, }; static queries = { + emptyState: "fxview-empty-state", rowEls: { all: "fxview-tab-row", }, @@ -308,11 +306,7 @@ export class FxviewTabListBase extends MozLitElement { } render() { - if ( - this.searchQuery && - this.tabItems.length === 0 && - !this.searchInProgress - ) { + if (this.searchQuery && !this.tabItems.length) { return this.emptySearchResultsTemplate(); } return html` diff --git a/browser/components/firefoxview/helpers.mjs b/browser/components/firefoxview/helpers.mjs index b206deef18..fb41fac0e1 100644 --- a/browser/components/firefoxview/helpers.mjs +++ b/browser/components/firefoxview/helpers.mjs @@ -125,27 +125,6 @@ export function isSearchEnabled() { return lazy.searchEnabledPref; } -/** - * Escape special characters for regular expressions from a string. - * - * @param {string} string - * The string to sanitize. - * @returns {string} The sanitized string. - */ -export function escapeRegExp(string) { - return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); -} - -/** - * Search a tab list for items that match the given query. - */ -export function searchTabList(query, tabList) { - const regex = RegExp(escapeRegExp(query), "i"); - return tabList.filter( - ({ title, url }) => regex.test(title) || regex.test(url) - ); -} - /** * Get or create a logger, whose log-level is controlled by a pref * diff --git a/browser/components/firefoxview/history.mjs b/browser/components/firefoxview/history.mjs index 478422d49b..4919f94e9c 100644 --- a/browser/components/firefoxview/history.mjs +++ b/browser/components/firefoxview/history.mjs @@ -15,13 +15,13 @@ import { import { ViewPage } from "./viewpage.mjs"; // eslint-disable-next-line import/no-unassigned-import import "chrome://browser/content/migration/migration-wizard.mjs"; -import { HistoryController } from "./HistoryController.mjs"; // eslint-disable-next-line import/no-unassigned-import import "chrome://global/content/elements/moz-button.mjs"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { + HistoryController: "resource:///modules/HistoryController.sys.mjs", ProfileAge: "resource://gre/modules/ProfileAge.sys.mjs", }); @@ -47,7 +47,7 @@ class HistoryInView extends ViewPage { this.cumulativeSearches = 0; } - controller = new HistoryController(this, { + controller = new lazy.HistoryController(this, { searchResultsLimit: SEARCH_RESULTS_LIMIT, }); @@ -57,7 +57,7 @@ class HistoryInView extends ViewPage { } this._started = true; - this.controller.updateAllHistoryItems(); + this.controller.updateCache(); this.toggleVisibilityInCardContainer(); } @@ -170,8 +170,8 @@ class HistoryInView extends ViewPage { this.recordContextMenuTelemetry("delete-from-history", e); } - async onChangeSortOption(e) { - await this.controller.onChangeSortOption(e); + onChangeSortOption(e) { + this.controller.onChangeSortOption(e); Services.telemetry.recordEvent( "firefoxview_next", "sort_history", @@ -184,8 +184,8 @@ class HistoryInView extends ViewPage { ); } - async onSearchQuery(e) { - await this.controller.onSearchQuery(e); + onSearchQuery(e) { + this.controller.onSearchQuery(e); this.cumulativeSearches = this.controller.searchQuery ? this.cumulativeSearches + 1 : 0; @@ -287,7 +287,7 @@ class HistoryInView extends ViewPage { get cardsTemplate() { if (this.controller.searchResults) { return this.#searchResultsTemplate(); - } else if (this.controller.allHistoryItems.size) { + } else if (!this.controller.isHistoryEmpty) { return this.#historyCardsTemplate(); } return this.#emptyMessageTemplate(); @@ -295,14 +295,11 @@ class HistoryInView extends ViewPage { #historyCardsTemplate() { let cardsTemplate = []; - if ( - this.controller.sortOption === "date" && - this.controller.historyMapByDate.length - ) { - this.controller.historyMapByDate.forEach(historyItem => { - if (historyItem.items.length) { + switch (this.controller.sortOption) { + case "date": + cardsTemplate = this.controller.historyVisits.map(historyItem => { let dateArg = JSON.stringify({ date: historyItem.items[0].time }); - cardsTemplate.push(html` + return html`

${this.panelListTemplate()} - `); - } - }); - } else if (this.controller.historyMapBySite.length) { - this.controller.historyMapBySite.forEach(historyItem => { - if (historyItem.items.length) { - cardsTemplate.push(html` + `; + }); + break; + case "site": + cardsTemplate = this.controller.historyVisits.map(historyItem => { + return html`

${historyItem.domain}

@@ -338,15 +334,15 @@ class HistoryInView extends ViewPage { dateTimeFormat="dateTime" hasPopup="menu" maxTabsLength=${this.maxTabsLength} - .tabItems=${[...historyItem.items]} + .tabItems=${historyItem.items} @fxview-tab-list-primary-action=${this.onPrimaryAction} @fxview-tab-list-secondary-action=${this.onSecondaryAction} > ${this.panelListTemplate()} -
`); - } - }); + `; + }); + break; } return cardsTemplate; } @@ -420,7 +416,6 @@ class HistoryInView extends ViewPage { .tabItems=${this.controller.searchResults} @fxview-tab-list-primary-action=${this.onPrimaryAction} @fxview-tab-list-secondary-action=${this.onSecondaryAction} - .searchInProgress=${this.controller.placesQuery.searchInProgress} > ${this.panelListTemplate()} @@ -491,12 +486,19 @@ class HistoryInView extends ViewPage { class="import-history-banner" hideHeader="true" ?hidden=${!this.shouldShowImportBanner()} + role="group" + aria-labelledby="header" + aria-describedby="description" >
@@ -518,7 +520,7 @@ class HistoryInView extends ViewPage {