diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /browser/components/newtab/lib/FaviconFeed.jsm | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/newtab/lib/FaviconFeed.jsm')
-rw-r--r-- | browser/components/newtab/lib/FaviconFeed.jsm | 213 |
1 files changed, 213 insertions, 0 deletions
diff --git a/browser/components/newtab/lib/FaviconFeed.jsm b/browser/components/newtab/lib/FaviconFeed.jsm new file mode 100644 index 0000000000..6114cf889a --- /dev/null +++ b/browser/components/newtab/lib/FaviconFeed.jsm @@ -0,0 +1,213 @@ +/* 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/. */ +"use strict"; + +const { actionTypes: at } = ChromeUtils.import( + "resource://activity-stream/common/Actions.jsm" +); +const { getDomain } = ChromeUtils.import( + "resource://activity-stream/lib/TippyTopProvider.jsm" +); +const { RemoteSettings } = ChromeUtils.import( + "resource://services-settings/remote-settings.js" +); + +ChromeUtils.defineModuleGetter( + this, + "PlacesUtils", + "resource://gre/modules/PlacesUtils.jsm" +); +ChromeUtils.defineModuleGetter( + this, + "Services", + "resource://gre/modules/Services.jsm" +); +ChromeUtils.defineModuleGetter( + this, + "NewTabUtils", + "resource://gre/modules/NewTabUtils.jsm" +); + +const MIN_FAVICON_SIZE = 96; + +/** + * Get favicon info (uri and size) for a uri from Places. + * + * @param uri {nsIURI} Page to check for favicon data + * @returns A promise of an object (possibly null) containing the data + */ +function getFaviconInfo(uri) { + // Use 0 to get the biggest width available + const preferredWidth = 0; + return new Promise(resolve => + PlacesUtils.favicons.getFaviconDataForPage( + uri, + // Package up the icon data in an object if we have it; otherwise null + (iconUri, faviconLength, favicon, mimeType, faviconSize) => + resolve(iconUri ? { iconUri, faviconSize } : null), + preferredWidth + ) + ); +} + +/** + * Fetches visit paths for a given URL from its most recent visit in Places. + * + * Note that this includes the URL itself as well as all the following + * permenent&temporary redirected URLs if any. + * + * @param {String} a URL string + * + * @returns {Array} Returns an array containing objects as + * {int} visit_id: ID of the visit in moz_historyvisits. + * {String} url: URL of the redirected URL. + */ +async function fetchVisitPaths(url) { + const query = ` + WITH RECURSIVE path(visit_id) + AS ( + SELECT v.id + FROM moz_places h + JOIN moz_historyvisits v + ON v.place_id = h.id + WHERE h.url_hash = hash(:url) AND h.url = :url + AND v.visit_date = h.last_visit_date + + UNION + + SELECT id + FROM moz_historyvisits + JOIN path + ON visit_id = from_visit + WHERE visit_type IN + (${PlacesUtils.history.TRANSITIONS.REDIRECT_PERMANENT}, + ${PlacesUtils.history.TRANSITIONS.REDIRECT_TEMPORARY}) + ) + SELECT visit_id, ( + SELECT ( + SELECT url + FROM moz_places + WHERE id = place_id) + FROM moz_historyvisits + WHERE id = visit_id) AS url + FROM path + `; + + const visits = await NewTabUtils.activityStreamProvider.executePlacesQuery( + query, + { + columns: ["visit_id", "url"], + params: { url }, + } + ); + return visits; +} + +/** + * Fetch favicon for a url by following its redirects in Places. + * + * This can improve the rich icon coverage for Top Sites since Places only + * associates the favicon to the final url if the original one gets redirected. + * Note this is not an urgent request, hence it is dispatched to the main + * thread idle handler to avoid any possible performance impact. + */ +async function fetchIconFromRedirects(url) { + const visitPaths = await fetchVisitPaths(url); + if (visitPaths.length > 1) { + const lastVisit = visitPaths.pop(); + const redirectedUri = Services.io.newURI(lastVisit.url); + const iconInfo = await getFaviconInfo(redirectedUri); + if (iconInfo && iconInfo.faviconSize >= MIN_FAVICON_SIZE) { + PlacesUtils.favicons.setAndFetchFaviconForPage( + Services.io.newURI(url), + iconInfo.iconUri, + false, + PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, + null, + Services.scriptSecurityManager.getSystemPrincipal() + ); + } + } +} + +this.FaviconFeed = class FaviconFeed { + constructor() { + this._queryForRedirects = new Set(); + } + + /** + * fetchIcon attempts to fetch a rich icon for the given url from two sources. + * First, it looks up the tippy top feed, if it's still missing, then it queries + * the places for rich icon with its most recent visit in order to deal with + * the redirected visit. See Bug 1421428 for more details. + */ + async fetchIcon(url) { + // Avoid initializing and fetching icons if prefs are turned off + if (!this.shouldFetchIcons) { + return; + } + + const site = await this.getSite(getDomain(url)); + if (!site) { + if (!this._queryForRedirects.has(url)) { + this._queryForRedirects.add(url); + Services.tm.idleDispatchToMainThread(() => fetchIconFromRedirects(url)); + } + return; + } + + let iconUri = Services.io.newURI(site.image_url); + // The #tippytop is to be able to identify them for telemetry. + iconUri = iconUri + .mutate() + .setRef("tippytop") + .finalize(); + PlacesUtils.favicons.setAndFetchFaviconForPage( + Services.io.newURI(url), + iconUri, + false, + PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, + null, + Services.scriptSecurityManager.getSystemPrincipal() + ); + } + + /** + * Get the site tippy top data from Remote Settings. + */ + async getSite(domain) { + const sites = await this.tippyTop.get({ + filters: { domain }, + syncIfEmpty: false, + }); + return sites.length ? sites[0] : null; + } + + /** + * Get the tippy top collection from Remote Settings. + */ + get tippyTop() { + if (!this._tippyTop) { + this._tippyTop = RemoteSettings("tippytop"); + } + return this._tippyTop; + } + + /** + * Determine if we should be fetching and saving icons. + */ + get shouldFetchIcons() { + return Services.prefs.getBoolPref("browser.chrome.site_icons"); + } + + onAction(action) { + switch (action.type) { + case at.RICH_ICON_MISSING: + this.fetchIcon(action.data.url); + break; + } + } +}; + +const EXPORTED_SYMBOLS = ["FaviconFeed", "fetchIconFromRedirects"]; |