commit 7095f6d0c2a7bdb551fd314820a9a768835e3c1f
parent 34e69dad516582ad5e0c21ab47beb660381b08a4
Author: Irene Ni <ini@mozilla.com>
Date: Thu, 4 Dec 2025 20:21:23 +0000
Bug 2002081 - Make New Tab frecency based tile sorting import from remote settings. r=home-newtab-reviewers,thecount
Differential Revision: https://phabricator.services.mozilla.com/D275091
Diffstat:
1 file changed, 109 insertions(+), 13 deletions(-)
diff --git a/browser/extensions/newtab/lib/TopSitesFeed.sys.mjs b/browser/extensions/newtab/lib/TopSitesFeed.sys.mjs
@@ -47,6 +47,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
Sampling: "resource://gre/modules/components-utils/Sampling.sys.mjs",
Screenshots: "resource://newtab/lib/Screenshots.sys.mjs",
+ Utils: "resource://services-settings/Utils.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "log", () => {
@@ -157,6 +158,9 @@ const DISPLAY_FAIL_REASON_OVERSOLD = "oversold";
const DISPLAY_FAIL_REASON_DISMISSED = "dismissed";
const DISPLAY_FAIL_REASON_UNRESOLVED = "unresolved";
+const RS_FALLBACK_BASE_URL =
+ "https://firefox-settings-attachments.cdn.mozilla.net/";
+
// Smart shortcuts
import { RankShortcutsProvider } from "resource://newtab/lib/SmartShortcutsRanker/RankShortcuts.mjs";
@@ -839,6 +843,8 @@ export class TopSitesFeed {
this._telemetryUtility = new TopSitesTelemetry();
this._contile = new ContileIntegration(this);
this._tippyTopProvider = new TippyTopProvider();
+ this._frecencyBoostedSponsors = new Map();
+ this._frecencyBoostRS = null;
ChromeUtils.defineLazyGetter(
this,
"_currentSearchHostname",
@@ -1320,6 +1326,91 @@ export class TopSitesFeed {
return fetch(...args);
}
+ get _frecencyBoostRemoteSettings() {
+ if (!this._frecencyBoostRS) {
+ this._frecencyBoostRS = lazy.RemoteSettings(
+ "newtab-frecency-boosted-sponsors"
+ );
+ }
+ return this._frecencyBoostRS;
+ }
+
+ /**
+ * Import all sponsors from Remote Settings and save their favicons.
+ * This is called lazily when frecency boosted spocs are first requested.
+ * We fetch all favicons regardless of whether the user has visited these sites.
+ */
+ async _importFrecencyBoostedSponsors() {
+ const records = await this._frecencyBoostRemoteSettings.get();
+
+ const userRegion = lazy.Region.home || "";
+ const regionRecords = records.filter(
+ record => record.region === userRegion
+ );
+
+ await Promise.all(
+ regionRecords.map(record =>
+ this._importFrecencyBoostedSponsor(record).catch(error => {
+ lazy.log.warn(
+ `Failed to import sponsor ${record.title || "unknown"}`,
+ error
+ );
+ })
+ )
+ );
+ }
+
+ /**
+ * Import a single sponsor record and fetch its favicon as data URI.
+ *
+ * @param {object} record - Remote Settings record with title, domain, redirect_url, and attachment
+ */
+ async _importFrecencyBoostedSponsor(record) {
+ const { title, domain, redirect_url, attachment } = record;
+ const faviconDataURI = await this._fetchSponsorFaviconAsDataURI(attachment);
+ const { hostname } = new URL(domain);
+
+ const sponsorData = {
+ title,
+ hostname,
+ redirectURL: redirect_url,
+ faviconDataURI,
+ };
+
+ this._frecencyBoostedSponsors.set(hostname, sponsorData);
+ }
+
+ /**
+ * Fetch favicon from Remote Settings attachment and return as data URI.
+ *
+ * @param {object} attachment - Remote Settings attachment object
+ * @returns {Promise<string|null>} Favicon data URI, or null on error
+ */
+ async _fetchSponsorFaviconAsDataURI(attachment) {
+ let baseAttachmentURL = RS_FALLBACK_BASE_URL;
+ try {
+ baseAttachmentURL = await lazy.Utils.baseAttachmentsURL();
+ } catch (error) {
+ lazy.log.warn(
+ `Error fetching remote settings base url from CDN. Falling back to ${RS_FALLBACK_BASE_URL}`,
+ error
+ );
+ }
+
+ const faviconURL = baseAttachmentURL + attachment.location;
+ const response = await fetch(faviconURL);
+
+ const blob = await response.blob();
+ const dataURI = await new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.addEventListener("load", () => resolve(reader.result));
+ reader.addEventListener("error", reject);
+ reader.readAsDataURL(blob);
+ });
+
+ return dataURI;
+ }
+
/**
* Dedupe sponsored domains against organic topsites.
*
@@ -1340,15 +1431,15 @@ export class TopSitesFeed {
.filter(Boolean)
);
- return sponsors.filter(({ domain }) => !topsites.has(domain));
+ return sponsors.filter(({ hostname }) => !topsites.has(hostname));
}
/**
* Build frecency-boosted spocs from a list of sponsor domains by checking Places history.
- * Checks if domains exist in history with favicons, dedupes against organic
- * topsites, and returns all matches sorted by frecency.
+ * Checks if domains exist in history, dedupes against organic topsites,
+ * and returns all matches sorted by frecency.
*
- * @param {Array} sponsors - List of sponsor domain objects with domain and title
+ * @param {Array} sponsors - List of sponsor domain objects with hostname and title
* @returns {Array} Array of sponsored tile objects sorted by frecency, or empty array
*/
async buildFrecencyBoostedSpocs(sponsors) {
@@ -1365,7 +1456,7 @@ export class TopSitesFeed {
let pagesMap;
try {
pagesMap = await lazy.PlacesUtils.history.fetchMany(
- sponsorsToCheck.map(({ domain }) => `https://${domain}/`)
+ sponsorsToCheck.map(({ hostname }) => `https://${hostname}`)
);
} catch (error) {
lazy.log.warn(`Failed to fetch history data: ${error.message}`);
@@ -1374,22 +1465,26 @@ export class TopSitesFeed {
const candidates = [];
for (const domainObj of sponsorsToCheck) {
- const url = `https://${domainObj.domain}/`;
+ const url = `https://${domainObj.hostname}/`;
const page = pagesMap.get(url);
if (!page || lazy.NewTabUtils.blockedLinks.isBlocked({ url })) {
continue;
}
+ const sponsorData = this._frecencyBoostedSponsors.get(domainObj.hostname);
+
candidates.push({
- hostname: domainObj.domain,
- url,
+ hostname: domainObj.hostname,
+ url: domainObj.redirectURL,
label: domainObj.title,
sponsored_position: 3,
partner: SPONSORED_TILE_PARTNER_FREC_BOOST,
type: "frecency-boost",
frecency: page.frecency,
show_sponsored_label: true,
+ favicon: sponsorData.faviconDataURI,
+ faviconSize: 96,
});
}
@@ -1417,12 +1512,13 @@ export class TopSitesFeed {
return [];
}
- const domainData = {};
- // Get domains for current region
- const userRegion = lazy.Region.home || "";
- const domainList = domainData[userRegion] || [];
+ if (this._frecencyBoostedSponsors.size === 0) {
+ await this._importFrecencyBoostedSponsors();
+ }
+
+ const domainList = Array.from(this._frecencyBoostedSponsors.values());
- // Find all matches from the test domains, sorted by frecency
+ // Find all matches from the sponsor domains, sorted by frecency
return this.buildFrecencyBoostedSpocs(domainList);
}