tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit f10ae21e5ae408e01f7140469f09e4cc87b42d0a
parent 3dc471cd51b135d36a303909302c6cb2b13768f3
Author: Nathan Barrett <nbarrett@mozilla.com>
Date:   Fri,  9 Jan 2026 21:38:06 +0000

Bug 2006377 - Retreive task object from new partner_id endpoint r=home-newtab-reviewers,mconley

Differential Revision: https://phabricator.services.mozilla.com/D276683

Diffstat:
Mbrowser/app/profile/firefox.js | 2+-
Mbrowser/extensions/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx | 2+-
Mbrowser/extensions/newtab/data/content/activity-stream.bundle.js | 2+-
Mbrowser/extensions/newtab/lib/NewTabAttributionService.sys.mjs | 144+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mbrowser/extensions/newtab/test/xpcshell/test_NewTabAttributionService.js | 290+++++++++++++++++++++++++++++++++++--------------------------------------------
5 files changed, 225 insertions(+), 215 deletions(-)

diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js @@ -681,7 +681,7 @@ pref("browser.urlbar.merino.ohttpConfigURL", ""); pref("browser.urlbar.merino.ohttpRelayURL", ""); // OHTTP hpke for DAP -pref("dap.ohttp.hpke", "https://dap-09-3.api.divviup.org/ohttp-configs"); +pref("dap.ohttp.hpke", "gAAgJSO22Y3HKzRSese15JtQVuuFfOIcTrZ56lQ5kDQwS0oABAABAAE"); // OHTTP relay URL for DAP pref("dap.ohttp.relayURL", "https://mozilla-ohttp-dap.mozilla.fastly-edge.com/"); diff --git a/browser/extensions/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx b/browser/extensions/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx @@ -363,7 +363,7 @@ export class DiscoveryStreamAdminUI extends React.PureComponent { sendConversionEvent() { const detail = { - partnerId: "demo-partner", + partnerId: "295BEEF7-1E3B-4128-B8F8-858E12AA660B", lookbackDays: 7, impressionType: "default", }; diff --git a/browser/extensions/newtab/data/content/activity-stream.bundle.js b/browser/extensions/newtab/data/content/activity-stream.bundle.js @@ -893,7 +893,7 @@ class DiscoveryStreamAdminUI extends (external_React_default()).PureComponent { } sendConversionEvent() { const detail = { - partnerId: "demo-partner", + partnerId: "295BEEF7-1E3B-4128-B8F8-858E12AA660B", lookbackDays: 7, impressionType: "default" }; diff --git a/browser/extensions/newtab/lib/NewTabAttributionService.sys.mjs b/browser/extensions/newtab/lib/NewTabAttributionService.sys.mjs @@ -8,15 +8,23 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { IndexedDB: "resource://gre/modules/IndexedDB.sys.mjs", DAPSender: "resource://gre/modules/DAPSender.sys.mjs", + ObliviousHTTP: "resource://gre/modules/ObliviousHTTP.sys.mjs", + HPKEConfigManager: "resource://gre/modules/HPKEConfigManager.sys.mjs", }); -const MAX_CONVERSIONS = 5; +const MAX_CONVERSIONS = 2; const MAX_LOOKBACK_DAYS = 30; const DAY_IN_MILLI = 1000 * 60 * 60 * 24; const CONVERSION_RESET_MILLI = 7 * DAY_IN_MILLI; const DAP_HPKE_PREF = "dap.ohttp.hpke"; const DAP_RELAY_PREF = "dap.ohttp.relayURL"; +const MARS_ENDPOINT_PREF = + "browser.newtabpage.activity-stream.unifiedAds.endpoint"; +const PREF_MARS_OHTTP_CONFIG = + "browser.newtabpage.activity-stream.discoverystream.ohttp.configURL"; +const PREF_MARS_OHTTP_RELAY = + "browser.newtabpage.activity-stream.discoverystream.ohttp.relayURL"; /** * @@ -30,7 +38,6 @@ class NewTabAttributionService { * @typedef {object} task - DAP task settings. * @property {string} id - task id. * @property {string} vdaf - vdaf type. - * @property {number} bits - datatype size. * @property {number} length - number of buckets. * @property {number} time_precision - time precision. * @@ -90,39 +97,26 @@ class NewTabAttributionService { try { const now = this.#now(); - const impressionStore = await this.#getImpressionStore(); - - if (!params || !params.conversion) { + if ( + !params || + !params.partner_id || + params.index === undefined || + params.index === null + ) { return; } - const impression = await this.#getImpression( - impressionStore, - params.partner_id, - { - conversion: { - task: { - id: params.conversion.task_id, - vdaf: params.conversion.vdaf, - bits: params.conversion.bits, - length: params.conversion.length, - time_precision: params.conversion.time_precision, - }, - defaultMeasurement: params.conversion.default_measurement, - index: params.conversion.index, - }, - } - ); + const impression = await this.#getImpression(params.partner_id, { + conversion: { + index: params.index, + }, + }); const prop = this.#getModelProp(type); impression.lastImpression = now; impression[prop] = now; - await this.#updateImpression( - impressionStore, - params.partner_id, - impression - ); + await this.#updateImpression(params.partner_id, impression); } catch (e) { console.error(e); } @@ -178,9 +172,10 @@ class NewTabAttributionService { if (lookbackDays > MAX_LOOKBACK_DAYS) { return; } + // we don't want to request the gateway key at time of conversion to avoid an IP address leak const dapHpke = Services.prefs.getCharPref( DAP_HPKE_PREF, - "https://dap-09-3.api.divviup.org/ohttp-configs" + "gAAgJSO22Y3HKzRSese15JtQVuuFfOIcTrZ56lQ5kDQwS0oABAABAAE" ); const ohttpRelayURL = Services.prefs.getCharPref( DAP_RELAY_PREF, @@ -196,21 +191,28 @@ class NewTabAttributionService { now ); - let conversion = impression?.conversion; - if (!conversion) { - // retreive "conversion" for conversions with no found impression - // conversion = await this.#getUnattributedTask(partnerId); - if (!conversion) { - return; - } + const receivedTaskConfig = await this.#getTaskConfig(partnerId); + + if (!receivedTaskConfig) { + return; } - let measurement = conversion.defaultMeasurement; + // Need to rename task_id to id for DAP report submission. + const taskConfig = { + ...receivedTaskConfig, + id: receivedTaskConfig.task_id, + }; + + let measurement = receivedTaskConfig.default_measurement; let budgetSpend = 0; - if (budget.conversions < MAX_CONVERSIONS && conversion) { + if (budget.conversions < MAX_CONVERSIONS && impression) { budgetSpend = 1; - if (conversion.task && conversion.task.length > conversion.index) { - measurement = conversion.index; + const conversionIndex = impression.conversion.index; + if ( + receivedTaskConfig.length > conversionIndex && + conversionIndex !== undefined + ) { + measurement = conversionIndex; } } @@ -218,7 +220,7 @@ class NewTabAttributionService { const options = {}; if (dapHpke) { - options.ohttp_hpke = dapHpke; + options.ohttp_hpke = lazy.HPKEConfigManager.decodeKey(dapHpke); } if (ohttpRelayURL) { @@ -226,7 +228,7 @@ class NewTabAttributionService { } await this.#dapSender.sendDAPMeasurement( - conversion.task, + taskConfig, measurement, options ); @@ -278,12 +280,12 @@ class NewTabAttributionService { * if it is found, defaulting to the passed in impression if there are none. This * enables timestamp fields of the stored event to be updated or carried forward. * - * @param {ObjectStore} impressionStore - Promise-based wrapped IDBObjectStore. * @param {string} partnerId - partner this event is associated with. * @param {impression} defaultImpression - event to use if it has not been seen previously. * @returns {Promise<impression>} */ - async #getImpression(impressionStore, partnerId, defaultImpression) { + async #getImpression(partnerId, defaultImpression) { + const impressionStore = await this.#getImpressionStore(); const impressions = await this.#getPartnerImpressions( impressionStore, partnerId @@ -295,15 +297,60 @@ class NewTabAttributionService { return impression ?? defaultImpression; } + async #getTaskConfig(partnerId) { + const baseUrl = Services.prefs.getCharPref(MARS_ENDPOINT_PREF, ""); + const endpoint = `${baseUrl}/v1/attribution?partner_id=${encodeURIComponent( + partnerId + )}`; + const ohttpConfigURL = Services.prefs.getCharPref( + PREF_MARS_OHTTP_CONFIG, + "" + ); + const ohttpRelayURL = Services.prefs.getCharPref(PREF_MARS_OHTTP_RELAY, ""); + + if (!partnerId || !endpoint || !ohttpRelayURL || !ohttpConfigURL) { + return null; + } + const controller = new AbortController(); + const { signal } = controller; + let config = await lazy.ObliviousHTTP.getOHTTPConfig(ohttpConfigURL); + if (!config) { + console.error( + new Error( + `OHTTP was configured for ${endpoint} but we couldn't fetch a valid config` + ) + ); + return null; + } + try { + const response = await lazy.ObliviousHTTP.ohttpRequest( + ohttpRelayURL, + config, + endpoint, + { + headers: {}, + signal, + } + ); + return response.json(); + } catch (error) { + console.error( + `Failed to make OHTTP request for unattributed task: ${error.message}`, + error + ); + return null; + } + } + /** * updateImpression stores the passed event, either updating the record * if this event was already seen, or appending to the list of events if it is new. * - * @param {ObjectStore} impressionStore - Promise-based wrapped IDBObjectStore. * @param {string} partnerId - partner this event is associated with. * @param {impression} impression - event to update. */ - async #updateImpression(impressionStore, partnerId, impression) { + async #updateImpression(partnerId, impression) { + const impressionStore = await this.#getImpressionStore(); let impressions = await this.#getPartnerImpressions( impressionStore, partnerId @@ -324,13 +371,10 @@ class NewTabAttributionService { /** * @param {impression} cur * @param {impression} impression - * @returns {boolean} true if cur and impression have the same DAP allocation, else false. + * @returns {boolean} true if cur and impression have the same index */ #compareImpression(cur, impression) { - return ( - cur.conversion.task.id === impression.conversion.task.id && - cur.conversion.index === impression.conversion.index - ); + return cur.conversion.index === impression.conversion.index; } /** diff --git a/browser/extensions/newtab/test/xpcshell/test_NewTabAttributionService.js b/browser/extensions/newtab/test/xpcshell/test_NewTabAttributionService.js @@ -6,6 +6,8 @@ https://creativecommons.org/publicdomain/zero/1.0/ */ ChromeUtils.defineESModuleGetters(this, { NewTabAttributionServiceClass: "resource://newtab/lib/NewTabAttributionService.sys.mjs", + ObliviousHTTP: "resource://gre/modules/ObliviousHTTP.sys.mjs", + sinon: "resource://testing-common/Sinon.sys.mjs", }); const { HttpServer } = ChromeUtils.importESModule( @@ -21,7 +23,7 @@ const BinaryInputStream = Components.Constructor( const PREF_LEADER = "toolkit.telemetry.dap.leader.url"; const PREF_HELPER = "toolkit.telemetry.dap.helper.url"; const TASK_ID = "DSZGMFh26hBYXNaKvhL_N4AHA3P5lDn19on1vFPBxJM"; -const MAX_CONVERSIONS = 5; +const MAX_CONVERSIONS = 2; const DAY_IN_MILLI = 1000 * 60 * 60 * 24; const LOOKBACK_DAYS = 1; const MAX_LOOKBACK_DAYS = 30; @@ -100,8 +102,52 @@ class MockServer { } } +let globalSandbox; + add_setup(async function () { do_get_profile(); + Services.prefs.setStringPref( + "browser.newtabpage.activity-stream.unifiedAds.endpoint", + "https://test.example.com" + ); + Services.prefs.setStringPref( + "browser.newtabpage.activity-stream.discoverystream.ohttp.configURL", + "https://test.example.com/config" + ); + Services.prefs.setStringPref( + "browser.newtabpage.activity-stream.discoverystream.ohttp.relayURL", + "https://test.example.com/relay" + ); + + globalSandbox = sinon.createSandbox(); + globalSandbox.stub(ObliviousHTTP, "getOHTTPConfig").resolves({}); + globalSandbox.stub(ObliviousHTTP, "ohttpRequest").resolves({ + status: 200, + json: () => { + return Promise.resolve({ + task_id: TASK_ID, + vdaf: "histogram", + bits: 1, + length: HISTOGRAM_SIZE, + time_precision: 60, + default_measurement: 0, + }); + }, + }); +}); + +registerCleanupFunction(() => { + Services.prefs.clearUserPref( + "browser.newtabpage.activity-stream.unifiedAds.endpoint" + ); + Services.prefs.clearUserPref( + "browser.newtabpage.activity-stream.discoverystream.ohttp.configURL" + ); + Services.prefs.clearUserPref( + "browser.newtabpage.activity-stream.discoverystream.ohttp.relayURL" + ); + + globalSandbox.restore(); }); add_task(async function testSuccessfulConversion() { @@ -111,24 +157,56 @@ add_task(async function testSuccessfulConversion() { }); const partnerIdentifier = "partner_identifier"; - const conversionSettings = { + const index = 1; + + await privateAttribution.onAttributionEvent("view", { + partner_id: partnerIdentifier, + index, + }); + + await privateAttribution.onAttributionEvent("click", { + partner_id: partnerIdentifier, + index, + }); + + await privateAttribution.onAttributionConversion( + partnerIdentifier, + LOOKBACK_DAYS, + "view" + ); + + const receivedMeasurement = mockSender.receivedMeasurements.pop(); + Assert.deepEqual(receivedMeasurement.task, { task_id: TASK_ID, + id: TASK_ID, vdaf: "histogram", bits: 1, length: HISTOGRAM_SIZE, time_precision: 60, default_measurement: 0, - index: 1, - }; + }); + Assert.equal(receivedMeasurement.measurement, index); + Assert.ok(receivedMeasurement.options.ohttp_hpke); + Assert.equal(receivedMeasurement.options.ohttp_hpke.length, 41); + Assert.equal( + receivedMeasurement.options.ohttp_relay, + Services.prefs.getStringPref("dap.ohttp.relayURL") + ); + Assert.equal(mockSender.receivedMeasurements.length, 0); +}); - await privateAttribution.onAttributionEvent("view", { - partner_id: partnerIdentifier, - conversion: conversionSettings, +add_task(async function testZeroIndex() { + const mockSender = new MockDAPSender(); + const privateAttribution = new NewTabAttributionServiceClass({ + dapSender: mockSender, }); - await privateAttribution.onAttributionEvent("click", { + const partnerIdentifier = "partner_identifier_zero"; + const index = 0; + + await privateAttribution.onAttributionEvent("view", { partner_id: partnerIdentifier, - conversion: conversionSettings, + index, }); await privateAttribution.onAttributionConversion( @@ -137,23 +215,8 @@ add_task(async function testSuccessfulConversion() { "view" ); - const expectedMeasurement = { - task: { - id: conversionSettings.task_id, - vdaf: conversionSettings.vdaf, - bits: conversionSettings.bits, - length: conversionSettings.length, - time_precision: conversionSettings.time_precision, - }, - measurement: conversionSettings.index, - options: { - ohttp_hpke: Services.prefs.getStringPref("dap.ohttp.hpke"), - ohttp_relay: Services.prefs.getStringPref("dap.ohttp.relayURL"), - }, - }; - const receivedMeasurement = mockSender.receivedMeasurements.pop(); - Assert.deepEqual(receivedMeasurement, expectedMeasurement); + Assert.equal(receivedMeasurement.measurement, index); Assert.equal(mockSender.receivedMeasurements.length, 0); }); @@ -171,6 +234,17 @@ add_task(async function testConversionWithoutImpression() { "view" ); + const receivedMeasurement = mockSender.receivedMeasurements.pop(); + Assert.deepEqual(receivedMeasurement.task, { + task_id: TASK_ID, + id: TASK_ID, + vdaf: "histogram", + bits: 1, + length: HISTOGRAM_SIZE, + time_precision: 60, + default_measurement: 0, + }); + Assert.equal(receivedMeasurement.measurement, 0); Assert.equal(mockSender.receivedMeasurements.length, 0); }); @@ -181,19 +255,11 @@ add_task(async function testConversionWithInvalidLookbackDays() { }); const partnerIdentifier = "partner_identifier"; - const conversionSettings = { - task_id: TASK_ID, - vdaf: "histogram", - bits: 1, - length: HISTOGRAM_SIZE, - time_precision: 60, - default_measurement: 0, - index: 1, - }; + const index = 1; await privateAttribution.onAttributionEvent("view", { partner_id: partnerIdentifier, - conversion: conversionSettings, + index, }); await privateAttribution.onAttributionConversion( @@ -214,15 +280,6 @@ add_task(async function testSelectionByLastView() { }); const partnerIdentifier = "partner_identifier_last_view"; - const conversionSettings = { - task_id: TASK_ID, - vdaf: "histogram", - bits: 1, - length: HISTOGRAM_SIZE, - time_precision: 60, - default_measurement: 0, - index: 1, - }; const selectedViewIndex = 1; const ignoredViewIndex = 2; const clickIndex = 3; @@ -230,10 +287,7 @@ add_task(async function testSelectionByLastView() { // View event that will be ignored, as a more recent view will exist await privateAttribution.onAttributionEvent("view", { partner_id: partnerIdentifier, - conversion: { - ...conversionSettings, - index: ignoredViewIndex, - }, + index: ignoredViewIndex, }); // step forward time @@ -242,10 +296,7 @@ add_task(async function testSelectionByLastView() { // View event that will be selected, as no more recent view exists await privateAttribution.onAttributionEvent("view", { partner_id: partnerIdentifier, - conversion: { - ...conversionSettings, - index: selectedViewIndex, - }, + index: selectedViewIndex, }); // step forward time @@ -254,10 +305,7 @@ add_task(async function testSelectionByLastView() { // Click event that will be ignored because the match type is "view" await privateAttribution.onAttributionEvent("click", { partner_id: partnerIdentifier, - conversion: { - ...conversionSettings, - index: clickIndex, - }, + index: clickIndex, }); // Conversion filtering for "view" finds the view event @@ -281,15 +329,6 @@ add_task(async function testSelectionByLastClick() { }); const partnerIdentifier = "partner_identifier_last_click"; - const conversionSettings = { - task_id: TASK_ID, - vdaf: "histogram", - bits: 1, - length: HISTOGRAM_SIZE, - time_precision: 60, - default_measurement: 0, - index: 1, - }; const viewIndex = 1; const ignoredClickIndex = 2; const selectedClickIndex = 3; @@ -297,10 +336,7 @@ add_task(async function testSelectionByLastClick() { // Click event that will be ignored, as a more recent click will exist await privateAttribution.onAttributionEvent("click", { partner_id: partnerIdentifier, - conversion: { - ...conversionSettings, - index: ignoredClickIndex, - }, + index: ignoredClickIndex, }); // step forward time @@ -309,10 +345,7 @@ add_task(async function testSelectionByLastClick() { // Click event that will be selected, as no more recent click exists await privateAttribution.onAttributionEvent("click", { partner_id: partnerIdentifier, - conversion: { - ...conversionSettings, - index: selectedClickIndex, - }, + index: selectedClickIndex, }); // step forward time @@ -321,10 +354,7 @@ add_task(async function testSelectionByLastClick() { // View event that will be ignored because the match type is "click" await privateAttribution.onAttributionEvent("view", { partner_id: partnerIdentifier, - conversion: { - ...conversionSettings, - index: viewIndex, - }, + index: viewIndex, }); // Conversion filtering for "click" finds the click event @@ -348,25 +378,13 @@ add_task(async function testSelectionByLastTouch() { }); const partnerIdentifier = "partner_identifier_last_touch"; - const conversionSettings = { - task_id: TASK_ID, - vdaf: "histogram", - bits: 1, - length: HISTOGRAM_SIZE, - time_precision: 60, - default_measurement: 0, - index: 1, - }; const viewIndex = 1; const clickIndex = 2; // Click at clickIndex await privateAttribution.onAttributionEvent("click", { partner_id: partnerIdentifier, - conversion: { - ...conversionSettings, - index: clickIndex, - }, + index: clickIndex, }); // step forward time so the view event occurs most recently @@ -375,10 +393,7 @@ add_task(async function testSelectionByLastTouch() { // View at viewIndex await privateAttribution.onAttributionEvent("view", { partner_id: partnerIdentifier, - conversion: { - ...conversionSettings, - index: viewIndex, - }, + index: viewIndex, }); // Conversion filtering for "default" finds the view event @@ -403,25 +418,13 @@ add_task(async function testSelectionByPartnerId() { const partnerIdentifier1 = "partner_identifier_1"; const partnerIdentifier2 = "partner_identifier_2"; - const conversionSettings = { - task_id: TASK_ID, - vdaf: "histogram", - bits: 1, - length: HISTOGRAM_SIZE, - time_precision: 60, - default_measurement: 0, - index: 1, - }; const partner1Index = 1; const partner2Index = 2; // view event associated with partner 1 await privateAttribution.onAttributionEvent("view", { partner_id: partnerIdentifier1, - conversion: { - ...conversionSettings, - index: partner1Index, - }, + index: partner1Index, }); // step forward time so the partner 2 event occurs most recently @@ -430,10 +433,7 @@ add_task(async function testSelectionByPartnerId() { // view event associated with partner 2 await privateAttribution.onAttributionEvent("view", { partner_id: partnerIdentifier2, - conversion: { - ...conversionSettings, - index: partner2Index, - }, + index: partner2Index, }); // Conversion filtering for "default" finds the correct view event @@ -457,32 +457,26 @@ add_task(async function testExpiredImpressions() { }); const partnerIdentifier = "partner_identifier"; - const conversionSettings = { - task_id: TASK_ID, - vdaf: "histogram", - bits: 1, - length: HISTOGRAM_SIZE, - time_precision: 60, - default_measurement: 0, - index: 1, - }; + const index = 1; + const defaultMeasurement = 0; // Register impression await privateAttribution.onAttributionEvent("view", { partner_id: partnerIdentifier, - conversion: conversionSettings, + index, }); // Fast-forward time by LOOKBACK_DAYS days + 1 ms mockDateProvider.add(LOOKBACK_DAYS * DAY_IN_MILLI + 1); - // Conversion doesn't match expired impression await privateAttribution.onAttributionConversion( partnerIdentifier, LOOKBACK_DAYS, "view" ); + const receivedMeasurement = mockSender.receivedMeasurements.pop(); + Assert.deepEqual(receivedMeasurement.measurement, defaultMeasurement); Assert.equal(mockSender.receivedMeasurements.length, 0); }); @@ -493,19 +487,12 @@ add_task(async function testConversionBudget() { }); const partnerIdentifier = "partner_identifier_budget"; - const conversionSettings = { - task_id: TASK_ID, - vdaf: "histogram", - bits: 1, - length: HISTOGRAM_SIZE, - time_precision: 60, - default_measurement: 0, - index: 1, - }; + const index = 1; + const defaultMeasurement = 0; await privateAttribution.onAttributionEvent("view", { partner_id: partnerIdentifier, - conversion: conversionSettings, + index, }); // Measurements uploaded for conversions up to MAX_CONVERSIONS @@ -517,7 +504,7 @@ add_task(async function testConversionBudget() { ); const receivedMeasurement = mockSender.receivedMeasurements.pop(); - Assert.deepEqual(receivedMeasurement.measurement, conversionSettings.index); + Assert.deepEqual(receivedMeasurement.measurement, index); Assert.equal(mockSender.receivedMeasurements.length, 0); } @@ -529,10 +516,7 @@ add_task(async function testConversionBudget() { ); const receivedMeasurement = mockSender.receivedMeasurements.pop(); - Assert.deepEqual( - receivedMeasurement.measurement, - conversionSettings.default_measurement - ); + Assert.deepEqual(receivedMeasurement.measurement, defaultMeasurement); Assert.equal(mockSender.receivedMeasurements.length, 0); }); @@ -543,20 +527,13 @@ add_task(async function testHistogramSize() { }); const partnerIdentifier = "partner_identifier_bad_settings"; - const conversionSettings = { - task_id: TASK_ID, - vdaf: "histogram", - bits: 1, - length: HISTOGRAM_SIZE, - time_precision: 60, - default_measurement: 0, - // Zero-based index equal to histogram size is out of bounds - index: HISTOGRAM_SIZE, - }; + const defaultMeasurement = 0; + // Zero-based index equal to histogram size is out of bounds + const index = HISTOGRAM_SIZE; await privateAttribution.onAttributionEvent("view", { partner_id: partnerIdentifier, - conversion: conversionSettings, + index, }); await privateAttribution.onAttributionConversion( @@ -566,10 +543,7 @@ add_task(async function testHistogramSize() { ); const receivedMeasurement = mockSender.receivedMeasurements.pop(); - Assert.deepEqual( - receivedMeasurement.measurement, - conversionSettings.default_measurement - ); + Assert.deepEqual(receivedMeasurement.measurement, defaultMeasurement); Assert.equal(mockSender.receivedMeasurements.length, 0); }); @@ -584,19 +558,11 @@ add_task(async function testWithRealDAPSender() { const privateAttribution = new NewTabAttributionServiceClass(); const partnerIdentifier = "partner_identifier_real_dap"; - const conversionSettings = { - task_id: TASK_ID, - vdaf: "histogram", - bits: 1, - length: HISTOGRAM_SIZE, - time_precision: 60, - default_measurement: 0, - index: 1, - }; + const index = 1; await privateAttribution.onAttributionEvent("view", { partner_id: partnerIdentifier, - conversion: conversionSettings, + index, }); await privateAttribution.onAttributionConversion(