Skip to content

Commit 42c441f

Browse files
committed
Bug 2002078 - Newtab frecency based tile sorting SOV r=nbarrett,home-newtab-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D273902
1 parent fba5061 commit 42c441f

File tree

2 files changed

+109
-0
lines changed

2 files changed

+109
-0
lines changed

browser/extensions/newtab/lib/ActivityStream.sys.mjs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -955,6 +955,35 @@ export const PREFS_CONFIG = new Map([
955955
value: "",
956956
},
957957
],
958+
[
959+
"sov.enabled",
960+
{
961+
title: "Enables share of voice (SOV)",
962+
value: false,
963+
},
964+
],
965+
[
966+
"sov.name",
967+
{
968+
title:
969+
"A unique id, usually this is a timestamp for the day it was generated",
970+
value: "SOV-20251122215625",
971+
},
972+
],
973+
[
974+
"sov.amp.allocation",
975+
{
976+
title: "How many positions can be filled from amp",
977+
value: "100, 100, 100",
978+
},
979+
],
980+
[
981+
"sov.frecency.allocation",
982+
{
983+
title: "How many positions can be filled by frecency",
984+
value: "0, 0, 0",
985+
},
986+
],
958987
[
959988
"widgets.system.enabled",
960989
{

browser/extensions/newtab/lib/TopSitesFeed.sys.mjs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ const PREF_UNIFIED_ADS_COUNTS = "discoverystream.placements.tiles.counts";
106106
const PREF_UNIFIED_ADS_BLOCKED_LIST = "unifiedAds.blockedAds";
107107
const PREF_UNIFIED_ADS_ADSFEED_ENABLED = "unifiedAds.adsFeed.enabled";
108108

109+
const PREF_SOV_ENABLED = "sov.enabled";
110+
const PREF_SOV_NAME = "sov.name";
111+
const PREF_SOV_AMP_ALLOCATION = "sov.amp.allocation";
112+
const PREF_SOV_FRECENCY_ALLOCATION = "sov.frecency.allocation";
113+
const DEFAULT_SOV_SLOT_COUNT = 3;
114+
109115
// Search experiment stuff
110116
const FILTER_DEFAULT_SEARCH_PREF = "improvesearch.noDefaultSearchTile";
111117
const SEARCH_FILTERS = [
@@ -139,9 +145,11 @@ const CONTILE_CACHE_VALID_FOR_FALLBACK = 3 * 60 * 60; // 3 hours in seconds
139145
// Partners of sponsored tiles.
140146
const SPONSORED_TILE_PARTNER_AMP = "amp";
141147
const SPONSORED_TILE_PARTNER_MOZ_SALES = "moz-sales";
148+
const SPONSORED_TILE_PARTNER_FREC_BOOST = "frec-boost";
142149
const SPONSORED_TILE_PARTNERS = new Set([
143150
SPONSORED_TILE_PARTNER_AMP,
144151
SPONSORED_TILE_PARTNER_MOZ_SALES,
152+
SPONSORED_TILE_PARTNER_FREC_BOOST,
145153
]);
146154

147155
const DISPLAY_FAIL_REASON_OVERSOLD = "oversold";
@@ -515,6 +523,64 @@ export class ContileIntegration {
515523
return { tiles: formattedTileData };
516524
}
517525

526+
sovEnabled() {
527+
return this._topSitesFeed.store.getState().Prefs.values[PREF_SOV_ENABLED];
528+
}
529+
530+
csvToInts(val) {
531+
if (!val) {
532+
return [];
533+
}
534+
535+
return val
536+
.split(",")
537+
.map(s => s.trim())
538+
.filter(item => item)
539+
.map(item => parseInt(item, 10));
540+
}
541+
542+
/**
543+
* Builds a Share of Voice (SOV) config.
544+
*
545+
* @example input data from prefs/trainhopConfig
546+
* // name: "SOV-20251122215625"
547+
* // amp: "100, 100, 100"
548+
* // frec: "0, 0, 0"
549+
*
550+
* @returns {{
551+
* name: string,
552+
* allocations: Array<{
553+
* position: number,
554+
* allocation: Array<{
555+
* partner: string,
556+
* percentage: number,
557+
* }>,
558+
* }>,
559+
* }}
560+
*/
561+
generateSov() {
562+
const { values } = this._topSitesFeed.store.getState().Prefs;
563+
const name = values[PREF_SOV_NAME];
564+
const amp = this.csvToInts(values[PREF_SOV_AMP_ALLOCATION]);
565+
const frec = this.csvToInts(values[PREF_SOV_FRECENCY_ALLOCATION]);
566+
567+
const allocations = Array.from(
568+
{ length: DEFAULT_SOV_SLOT_COUNT },
569+
(val, i) => ({
570+
position: i + 1, // 1-based
571+
allocation: [
572+
{ partner: SPONSORED_TILE_PARTNER_AMP, percentage: amp[i] || 0 },
573+
{
574+
partner: SPONSORED_TILE_PARTNER_FREC_BOOST,
575+
percentage: frec[i] || 0,
576+
},
577+
],
578+
})
579+
);
580+
581+
return { name, allocations };
582+
}
583+
518584
// eslint-disable-next-line max-statements
519585
async _fetchSites() {
520586
if (
@@ -687,6 +753,8 @@ export class ContileIntegration {
687753
// Logic below runs the same regardless of ad source
688754
if (body?.sov) {
689755
this._sov = JSON.parse(atob(body.sov));
756+
} else if (this.sovEnabled()) {
757+
this._sov = this.generateSov();
690758
}
691759

692760
if (body?.tiles && Array.isArray(body.tiles)) {
@@ -1244,6 +1312,16 @@ export class TopSitesFeed {
12441312
return fetch(...args);
12451313
}
12461314

1315+
/**
1316+
* Fetch topsites spocs that are frecency boosted.
1317+
*
1318+
* @returns {Array} An array of sponsored tile objects.
1319+
*/
1320+
fetchFrecencyBoostedSpocs() {
1321+
let sponsored = [];
1322+
return sponsored;
1323+
}
1324+
12471325
/**
12481326
* Fetch topsites spocs from the DiscoveryStream feed.
12491327
*
@@ -1417,11 +1495,13 @@ export class TopSitesFeed {
14171495
);
14181496

14191497
const discoverySponsored = this.fetchDiscoveryStreamSpocs();
1498+
const frecencyBoostedSponsored = this.fetchFrecencyBoostedSpocs();
14201499
this._telemetryUtility.setTiles(discoverySponsored);
14211500

14221501
const sponsored = this._mergeSponsoredLinks({
14231502
[SPONSORED_TILE_PARTNER_AMP]: contileSponsored,
14241503
[SPONSORED_TILE_PARTNER_MOZ_SALES]: discoverySponsored,
1504+
[SPONSORED_TILE_PARTNER_FREC_BOOST]: frecencyBoostedSponsored,
14251505
});
14261506

14271507
this._maybeCapSponsoredLinks(sponsored);

0 commit comments

Comments
 (0)