Skip to content

Commit 1cd1965

Browse files
committed
Bug 2005307 - Send pre-flight headers when communicating with MARS over OHTTP for sponsored topsites. r=home-newtab-reviewers,nbarrett
Differential Revision: https://phabricator.services.mozilla.com/D275865
1 parent 83a675a commit 1cd1965

File tree

2 files changed

+138
-0
lines changed

2 files changed

+138
-0
lines changed

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,12 @@ const DISPLAY_FAIL_REASON_UNRESOLVED = "unresolved";
162162
const RS_FALLBACK_BASE_URL =
163163
"https://firefox-settings-attachments.cdn.mozilla.net/";
164164

165+
ChromeUtils.defineLazyGetter(lazy, "userAgent", () => {
166+
return Cc["@mozilla.org/network/protocol;1?name=http"].getService(
167+
Ci.nsIHttpProtocolHandler
168+
).userAgent;
169+
});
170+
165171
// Smart shortcuts
166172
import { RankShortcutsProvider } from "resource://newtab/lib/SmartShortcutsRanker/RankShortcuts.mjs";
167173

@@ -644,6 +650,32 @@ export class ContileIntegration {
644650

645651
const endpointBaseUrl = state.Prefs.values[PREF_UNIFIED_ADS_ENDPOINT];
646652

653+
const preFlightConfig =
654+
state.Prefs.values?.trainhopConfig?.marsPreFlight || {};
655+
656+
// We need some basic data that we can pass along to the ohttp request.
657+
// We purposefully don't use ohttp on this request. We also expect to
658+
// mostly hit the HTTP cache rather than the network with these requests.
659+
if (preFlightConfig.enabled) {
660+
const preflightResponse = await this._topSitesFeed.fetch(
661+
`${endpointBaseUrl}v1/ads-preflight`,
662+
{
663+
method: "GET",
664+
}
665+
);
666+
const preFlight = await preflightResponse.json();
667+
668+
if (preFlight) {
669+
// If we don't get a normalized_ua, it means it matched the default userAgent.
670+
headers.append(
671+
"X-User-Agent",
672+
preFlight.normalized_ua || lazy.userAgent
673+
);
674+
headers.append("X-Geoname-ID", preFlight.geoname_id);
675+
headers.append("X-Geo-Location", preFlight.geo_location);
676+
}
677+
}
678+
647679
let blockedSponsors =
648680
this._topSitesFeed.store.getState().Prefs.values[
649681
PREF_UNIFIED_ADS_BLOCKED_LIST
@@ -693,6 +725,17 @@ export class ContileIntegration {
693725
);
694726
return null;
695727
}
728+
729+
// ObliviousHTTP.ohttpRequest only accepts a key/value object, and not
730+
// a Headers instance. We normalize any headers to a key/value object.
731+
//
732+
// We use instanceof here since isInstance isn't available for
733+
// Headers, it seems.
734+
// eslint-disable-next-line mozilla/use-isInstance
735+
if (options.headers && options.headers instanceof Headers) {
736+
options.headers = Object.fromEntries(options.headers);
737+
}
738+
696739
fetchPromise = lazy.ObliviousHTTP.ohttpRequest(
697740
ohttpRelayURL,
698741
config,

browser/extensions/newtab/test/xpcshell/test_TopSitesFeed.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ ChromeUtils.defineESModuleGetters(this, {
1111
FilterAdult: "resource:///modules/FilterAdult.sys.mjs",
1212
NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs",
1313
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
14+
ObliviousHTTP: "resource://gre/modules/ObliviousHTTP.sys.mjs",
1415
PageThumbs: "resource://gre/modules/PageThumbs.sys.mjs",
1516
PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
1617
sinon: "resource://testing-common/Sinon.sys.mjs",
@@ -3395,5 +3396,99 @@ add_task(async function test_ContileIntegration() {
33953396
sandbox.restore();
33963397
}
33973398

3399+
{
3400+
info(
3401+
"TopSitesFeed._fetchSites should cast headers from a Headers object to JS object when using OHTTP"
3402+
);
3403+
let { feed } = prepFeed(getTopSitesFeedForTest(sandbox));
3404+
3405+
Services.prefs.setStringPref(
3406+
"browser.newtabpage.activity-stream.discoverystream.ohttp.relayURL",
3407+
"https://relay.url"
3408+
);
3409+
Services.prefs.setStringPref(
3410+
"browser.newtabpage.activity-stream.discoverystream.ohttp.configURL",
3411+
"https://config.url"
3412+
);
3413+
Services.prefs.setBoolPref(
3414+
"browser.newtabpage.activity-stream.unifiedAds.ohttp.enabled",
3415+
true
3416+
);
3417+
feed.store.state.Prefs.values["unifiedAds.tiles.enabled"] = true;
3418+
feed.store.state.Prefs.values["unifiedAds.adsFeed.enabled"] = false;
3419+
feed.store.state.Prefs.values["unifiedAds.endpoint"] =
3420+
"https://test.endpoint/";
3421+
feed.store.state.Prefs.values["discoverystream.placements.tiles"] = "1";
3422+
feed.store.state.Prefs.values["discoverystream.placements.tiles.counts"] =
3423+
"1";
3424+
feed.store.state.Prefs.values["unifiedAds.blockedAds"] = "";
3425+
3426+
const fakeOhttpConfig = { config: "config" };
3427+
sandbox.stub(ObliviousHTTP, "getOHTTPConfig").resolves(fakeOhttpConfig);
3428+
3429+
const ohttpRequestStub = sandbox
3430+
.stub(ObliviousHTTP, "ohttpRequest")
3431+
.resolves({
3432+
ok: true,
3433+
status: 200,
3434+
headers: new Map([
3435+
["cache-control", "private, max-age=859, stale-if-error=10463"],
3436+
]),
3437+
json: () =>
3438+
Promise.resolve({
3439+
1: [
3440+
{
3441+
block_key: 12345,
3442+
name: "test",
3443+
url: "https://www.test.com",
3444+
image_url: "images/test-com.png",
3445+
callbacks: {
3446+
click: "https://www.test-click.com",
3447+
impression: "https://www.test-impression.com",
3448+
},
3449+
},
3450+
],
3451+
}),
3452+
});
3453+
3454+
let fetched = await feed._contile._fetchSites();
3455+
3456+
Assert.ok(fetched);
3457+
Assert.ok(
3458+
ohttpRequestStub.calledOnce,
3459+
"ohttpRequest should be called once"
3460+
);
3461+
const callArgs = ohttpRequestStub.getCall(0).args;
3462+
Assert.equal(callArgs[0], "https://relay.url", "relay URL should match");
3463+
Assert.deepEqual(
3464+
callArgs[1],
3465+
fakeOhttpConfig,
3466+
"config should be passed through"
3467+
);
3468+
Assert.equal(
3469+
typeof callArgs[3].headers,
3470+
"object",
3471+
"headers should be a plain object"
3472+
);
3473+
Assert.ok(
3474+
// We use instanceof here since isInstance isn't available for
3475+
// Headers, it seems.
3476+
// eslint-disable-next-line mozilla/use-isInstance
3477+
!(callArgs[3].headers instanceof Headers),
3478+
"headers should not be a Headers instance"
3479+
);
3480+
3481+
Services.prefs.clearUserPref(
3482+
"browser.newtabpage.activity-stream.discoverystream.ohttp.relayURL"
3483+
);
3484+
Services.prefs.clearUserPref(
3485+
"browser.newtabpage.activity-stream.discoverystream.ohttp.configURL"
3486+
);
3487+
Services.prefs.clearUserPref(
3488+
"browser.newtabpage.activity-stream.unifiedAds.ohttp.enabled"
3489+
);
3490+
sandbox.restore();
3491+
}
3492+
33983493
Services.prefs.clearUserPref(TOP_SITES_BLOCKED_SPONSORS_PREF);
33993494
});

0 commit comments

Comments
 (0)