commit 2a1f1919071ce5dc161fc07db132a8b6de9c7912
parent 49988b4b9887b39adabb5a698cef009d909bce12
Author: Nathan Barrett <nbarrett@mozilla.com>
Date: Thu, 4 Dec 2025 02:32:01 +0000
Bug 2003048 - Add frecency telemetry for sponsored TopSites r=home-newtab-reviewers,reemhamz
Differential Revision: https://phabricator.services.mozilla.com/D274859
Diffstat:
6 files changed, 242 insertions(+), 42 deletions(-)
diff --git a/browser/components/newtab/metrics.yaml b/browser/components/newtab/metrics.yaml
@@ -2750,6 +2750,107 @@ newtab_content:
no_lint:
- HIGHER_DATA_SENSITIVITY_REQUIRED
+ top_sites_click:
+ type: event
+ description: >
+ Recorded when a top sites tile is clicked.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=2003048
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=2003048
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - nbarrett@mozilla.com
+ - sdowne@mozilla.com
+ expires: never
+ extra_keys:
+ advertiser_name: *advertiser_name
+ tile_id: *tile_id
+ is_sponsored: *is_sponsored
+ position: *topsite_position
+ visible_topsites: *visible_topsites
+ frecency_boosted: &frecency_boosted
+ description: >
+ Whether the top site was frecency boosted
+ type: boolean
+ frecency_boosted_has_exposure: &frecency_boosted_has_exposure
+ description: >
+ Whether the user had the option to see frecency boosted shortcuts
+ type: boolean
+ send_in_pings:
+ - newtab-content
+
+ top_sites_impression:
+ type: event
+ description: >
+ Recorded when a top sites tile is visible to the user.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=2003048
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=2003048
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - nbarrett@mozilla.com
+ - sdowne@mozilla.com
+ expires: never
+ extra_keys:
+ advertiser_name: *advertiser_name
+ tile_id: *tile_id
+ is_sponsored: *is_sponsored
+ position: *topsite_position
+ visible_topsites: *visible_topsites
+ frecency_boosted: *frecency_boosted
+ frecency_boosted_has_exposure: *frecency_boosted_has_exposure
+ send_in_pings:
+ - newtab-content
+
+ top_sites_dismiss:
+ type: event
+ description: >
+ Recorded when the "Dismiss" menu item in the three-dots menu of a topsite
+ is clicked.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=2003048
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=2003048
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - nbarrett@mozilla.com
+ - sdowne@mozilla.com
+ expires: never
+ extra_keys:
+ advertiser_name: *advertiser_name
+ tile_id: *tile_id
+ is_sponsored: *is_sponsored
+ position: *topsite_position
+ send_in_pings:
+ - newtab-content
+
+ top_sites_show_privacy_click:
+ type: event
+ description: >
+ Recorded when the "Our sponsors & your privacy" menu item in the three-dots menu of a topsite
+ is clicked.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=2003048
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=2003048
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - nbarrett@mozilla.com
+ - sdowne@mozilla.com
+ expires: never
+ extra_keys:
+ advertiser_name: *advertiser_name
+ tile_id: *tile_id
+ position: *topsite_position
+ send_in_pings:
+ - newtab-content
+
top_sites: # Replacement for PingCentre "topsites-impression|click" pings.
ping_type:
type: string
diff --git a/browser/extensions/newtab/content-src/components/TopSites/TopSite.jsx b/browser/extensions/newtab/content-src/components/TopSites/TopSite.jsx
@@ -337,6 +337,7 @@ export class TopSiteLink extends React.PureComponent {
advertiser: title.toLocaleLowerCase(),
source: NEWTAB_SOURCE,
visible_topsites: visibleTopSites,
+ frecency_boosted: link.type === "frecency-boost",
}}
// For testing.
IntersectionObserver={this.props.IntersectionObserver}
@@ -585,6 +586,7 @@ export class TopSite extends React.PureComponent {
advertiser: title.toLocaleLowerCase(),
source: NEWTAB_SOURCE,
visible_topsites: this.props.visibleTopSites,
+ frecency_boosted: this.props.link.type === "frecency-boost",
},
})
);
diff --git a/browser/extensions/newtab/data/content/activity-stream.bundle.js b/browser/extensions/newtab/data/content/activity-stream.bundle.js
@@ -8544,7 +8544,8 @@ class TopSiteLink extends (external_React_default()).PureComponent {
reporting_url: link.sponsored_impression_url,
advertiser: title.toLocaleLowerCase(),
source: NEWTAB_SOURCE,
- visible_topsites: visibleTopSites
+ visible_topsites: visibleTopSites,
+ frecency_boosted: link.type === "frecency-boost"
}
// For testing.
,
@@ -8764,7 +8765,8 @@ class TopSite extends (external_React_default()).PureComponent {
reporting_url: this.props.link.sponsored_click_url,
advertiser: title.toLocaleLowerCase(),
source: NEWTAB_SOURCE,
- visible_topsites: this.props.visibleTopSites
+ visible_topsites: this.props.visibleTopSites,
+ frecency_boosted: this.props.link.type === "frecency-boost"
}
}));
} else {
diff --git a/browser/extensions/newtab/lib/ActivityStream.sys.mjs b/browser/extensions/newtab/lib/ActivityStream.sys.mjs
@@ -982,6 +982,14 @@ export const PREFS_CONFIG = new Map([
},
],
[
+ "sov.frecency.exposure",
+ {
+ title:
+ "Is or was the user eligible for frecency ranked sponsored shortcuts",
+ value: false,
+ },
+ ],
+ [
"sov.amp.allocation",
{
title: "How many positions can be filled from amp",
diff --git a/browser/extensions/newtab/lib/TelemetryFeed.sys.mjs b/browser/extensions/newtab/lib/TelemetryFeed.sys.mjs
@@ -78,6 +78,7 @@ const PREF_SYSTEM_INFERRED_PERSONALIZATION =
"discoverystream.sections.personalization.inferred.enabled";
const PREF_SECTIONS_PERSONALIZATION_ENABLED =
"discoverystream.sections.personalization.enabled";
+const PREF_SOV_FRECENCY_EXPOSURE = "sov.frecency.exposure";
const TOP_STORIES_SECTION_NAME = "top_stories_section";
@@ -606,6 +607,17 @@ export class TelemetryFeed {
}
}
+ sovEnabled() {
+ const { values } = this.store?.getState()?.Prefs || {};
+ const trainhopSovEnabled = values?.trainhopConfig?.sov?.enabled;
+ return trainhopSovEnabled;
+ }
+
+ frecencyBoostedHasExposure() {
+ const { values } = this.store?.getState()?.Prefs || {};
+ return values?.[PREF_SOV_FRECENCY_EXPOSURE];
+ }
+
async handleTopSitesSponsoredImpressionStats(action) {
const { data } = action;
const {
@@ -615,6 +627,7 @@ export class TelemetryFeed {
advertiser: advertiser_name,
tile_id,
visible_topsites,
+ frecency_boosted = false,
} = data;
// Legacy telemetry expects 1-based tile positions.
const legacyTelemetryPosition = position + 1;
@@ -632,14 +645,28 @@ export class TelemetryFeed {
`${source}_${legacyTelemetryPosition}`
].add(1);
if (session) {
- Glean.topsites.impression.record({
- advertiser_name,
- tile_id,
- newtab_visit_id: session.session_id,
- is_sponsored: true,
- position,
- visible_topsites,
- });
+ if (this.sovEnabled()) {
+ if (this.privatePingEnabled) {
+ this.newtabContentPing.recordEvent("topSitesImpression", {
+ advertiser_name,
+ tile_id,
+ is_sponsored: true,
+ position,
+ visible_topsites,
+ frecency_boosted,
+ frecency_boosted_has_exposure: this.frecencyBoostedHasExposure(),
+ });
+ }
+ } else {
+ Glean.topsites.impression.record({
+ advertiser_name,
+ tile_id,
+ newtab_visit_id: session.session_id,
+ is_sponsored: true,
+ position,
+ visible_topsites,
+ });
+ }
}
} else if (type === "click") {
pingType = "topsites-click";
@@ -647,30 +674,46 @@ export class TelemetryFeed {
`${source}_${legacyTelemetryPosition}`
].add(1);
if (session) {
- Glean.topsites.click.record({
- advertiser_name,
- tile_id,
- newtab_visit_id: session.session_id,
- is_sponsored: true,
- position,
- visible_topsites,
- });
+ if (this.sovEnabled()) {
+ if (this.privatePingEnabled) {
+ this.newtabContentPing.recordEvent("topSitesClick", {
+ advertiser_name,
+ tile_id,
+ is_sponsored: true,
+ position,
+ visible_topsites,
+ frecency_boosted,
+ frecency_boosted_has_exposure: this.frecencyBoostedHasExposure(),
+ });
+ }
+ } else {
+ Glean.topsites.click.record({
+ advertiser_name,
+ tile_id,
+ newtab_visit_id: session.session_id,
+ is_sponsored: true,
+ position,
+ visible_topsites,
+ });
+ }
}
} else {
console.error("Unknown ping type for sponsored TopSites impression");
return;
}
- Glean.topSites.pingType.set(pingType);
- Glean.topSites.position.set(legacyTelemetryPosition);
- Glean.topSites.source.set(source);
- Glean.topSites.tileId.set(tile_id);
- if (data.reporting_url && !unifiedAdsTilesEnabled) {
- Glean.topSites.reportingUrl.set(data.reporting_url);
+ if (this.sovEnabled()) {
+ Glean.topSites.pingType.set(pingType);
+ Glean.topSites.position.set(legacyTelemetryPosition);
+ Glean.topSites.source.set(source);
+ Glean.topSites.tileId.set(tile_id);
+ if (data.reporting_url && !unifiedAdsTilesEnabled) {
+ Glean.topSites.reportingUrl.set(data.reporting_url);
+ }
+ Glean.topSites.advertiser.set(advertiser_name);
+ Glean.topSites.contextId.set(await lazy.ContextId.request());
+ GleanPings.topSites.submit();
}
- Glean.topSites.advertiser.set(advertiser_name);
- Glean.topSites.contextId.set(await lazy.ContextId.request());
- GleanPings.topSites.submit();
if (data.reporting_url && this.canSendUnifiedAdsTilesCallbacks) {
// Send callback events to MARS unified ads api
@@ -1878,13 +1921,24 @@ export class TelemetryFeed {
if (action.source === "TOP_SITES") {
const { position, advertiser_name, tile_id, isSponsoredTopSite } =
datum;
- Glean.topsites.dismiss.record({
- advertiser_name,
- tile_id,
- newtab_visit_id: session.session_id,
- is_sponsored: !!isSponsoredTopSite,
- position,
- });
+ if (this.sovEnabled()) {
+ if (this.privatePingEnabled) {
+ this.newtabContentPing.recordEvent("topSitesDismiss", {
+ advertiser_name,
+ tile_id,
+ is_sponsored: !!isSponsoredTopSite,
+ position,
+ });
+ }
+ } else {
+ Glean.topsites.dismiss.record({
+ advertiser_name,
+ tile_id,
+ newtab_visit_id: session.session_id,
+ is_sponsored: !!isSponsoredTopSite,
+ position,
+ });
+ }
}
}
}
@@ -1895,12 +1949,22 @@ export class TelemetryFeed {
const { position, advertiser_name, tile_id } = data;
if (session) {
- Glean.topsites.showPrivacyClick.record({
- advertiser_name,
- tile_id,
- newtab_visit_id: session.session_id,
- position,
- });
+ if (this.sovEnabled()) {
+ if (this.privatePingEnabled) {
+ this.newtabContentPing.recordEvent("topSitesShowPrivacyClick", {
+ advertiser_name,
+ tile_id,
+ position,
+ });
+ }
+ } else {
+ Glean.topsites.showPrivacyClick.record({
+ advertiser_name,
+ tile_id,
+ newtab_visit_id: session.session_id,
+ position,
+ });
+ }
}
}
diff --git a/browser/extensions/newtab/lib/TopSitesFeed.sys.mjs b/browser/extensions/newtab/lib/TopSitesFeed.sys.mjs
@@ -107,6 +107,7 @@ const PREF_UNIFIED_ADS_BLOCKED_LIST = "unifiedAds.blockedAds";
const PREF_UNIFIED_ADS_ADSFEED_ENABLED = "unifiedAds.adsFeed.enabled";
const PREF_SOV_ENABLED = "sov.enabled";
+const PREF_SOV_FRECENCY_EXPOSURE = "sov.frecency.exposure";
const PREF_SOV_NAME = "sov.name";
const PREF_SOV_AMP_ALLOCATION = "sov.amp.allocation";
const PREF_SOV_FRECENCY_ALLOCATION = "sov.frecency.allocation";
@@ -526,7 +527,7 @@ export class ContileIntegration {
sovEnabled() {
const { values } = this._topSitesFeed.store.getState().Prefs;
const trainhopSovEnabled = values?.trainhopConfig?.sov?.enabled;
- return trainhopSovEnabled || values[PREF_SOV_ENABLED];
+ return trainhopSovEnabled || values?.[PREF_SOV_ENABLED];
}
csvToInts(val) {
@@ -1364,7 +1365,7 @@ export class TopSitesFeed {
let pagesMap;
try {
pagesMap = await lazy.PlacesUtils.history.fetchMany(
- sponsorsToCheck.map(({ domain }) => `https://${domain}`)
+ sponsorsToCheck.map(({ domain }) => `https://${domain}/`)
);
} catch (error) {
lazy.log.warn(`Failed to fetch history data: ${error.message}`);
@@ -1392,6 +1393,12 @@ export class TopSitesFeed {
});
}
+ // If we have a matched set of candidates,
+ // we can check if it's an exposure event.
+ if (candidates.length) {
+ this.frecencyBoostedSpocsExposureEvent();
+ }
+
candidates.sort((a, b) => b.frecency - a.frecency);
return candidates;
}
@@ -1405,6 +1412,7 @@ export class TopSitesFeed {
if (!this._contile.sovEnabled() || !this._linksWithDefaults?.length) {
return [];
}
+
const domainData = {};
// Get domains for current region
const userRegion = lazy.Region.home || "";
@@ -1415,6 +1423,21 @@ export class TopSitesFeed {
}
/**
+ * Flip exposure event pref,
+ * if the user is in a SOV experiment,
+ * for both control and treatment,
+ * and had frecency boosted spocs because of it.
+ */
+ frecencyBoostedSpocsExposureEvent() {
+ const { values } = this.store.getState().Prefs;
+ const trainhopSovEnabled = values?.trainhopConfig?.sov?.enabled;
+
+ if (trainhopSovEnabled) {
+ this.store.dispatch(ac.SetPref(PREF_SOV_FRECENCY_EXPOSURE, true));
+ }
+ }
+
+ /**
* Fetch topsites spocs from the DiscoveryStream feed.
*
* @returns {Array} An array of sponsored tile objects.