tor-browser

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

generate-bid-browser-signals.https.window.js (37444B)


      1 // META: script=/resources/testdriver.js
      2 // META: script=/resources/testdriver-vendor.js
      3 // META: script=/common/utils.js
      4 // META: script=resources/fledge-util.sub.js
      5 // META: script=/common/subset-tests.js
      6 // META: timeout=long
      7 // META: variant=?1-4
      8 // META: variant=?5-8
      9 // META: variant=?9-12
     10 // META: variant=?13-16
     11 // META: variant=?17-20
     12 // META: variant=?21-24
     13 // META: variant=?25-28
     14 // META: variant=?29-last
     15 
     16 "use strict";
     17 
     18 // These tests focus on the browserSignals argument passed to generateBid().
     19 // Note that "topLevelSeller" is covered by component auction tests,
     20 // "dataVersion" by trusted signals tests, and cross-origin
     21 // "topWindowHostname" and "seller" are covered by cross origin tests.
     22 //
     23 // Some of these tests use the "uuid" for interest group name, to avoid
     24 // joins/bids from previous tests that failed to clean up after themselves
     25 // from affecting results.
     26 
     27 subsetTest(promise_test, async test => {
     28  const uuid = generateUuid(test);
     29 
     30  let expectedBrowserSignals = {
     31    'topWindowHostname': window.location.hostname,
     32    'seller': window.location.origin,
     33    'adComponentsLimit': 40,
     34    'joinCount': 1,
     35    'bidCount': 0,
     36    'multiBidLimit': 1,
     37    'prevWinsMs': [],
     38  };
     39  let biddingLogicURL = createBiddingScriptURL({
     40    generateBid:
     41        `let expectedBrowserSignals = ${JSON.stringify(expectedBrowserSignals)};
     42 
     43          // Can't check this value exactly.
     44          expectedBrowserSignals.recency = browserSignals.recency;
     45 
     46          // This value may be affected by other recently run tests.
     47          expectedBrowserSignals.forDebuggingOnlyInCooldownOrLockout =
     48              browserSignals.forDebuggingOnlyInCooldownOrLockout;
     49 
     50 
     51          // Remove deprecated field, if present.
     52          delete browserSignals.prevWins;
     53 
     54          if (!deepEquals(browserSignals, expectedBrowserSignals))
     55             throw "Unexpected browserSignals: " + JSON.stringify(browserSignals) + " - " + JSON.stringify(expectedBrowserSignals)`
     56  });
     57 
     58  await joinGroupAndRunBasicFledgeTestExpectingWinner(
     59      test,
     60      { uuid: uuid,
     61        interestGroupOverrides: {name: uuid,
     62                                 biddingLogicURL: biddingLogicURL}});
     63 }, 'Only expected fields present.');
     64 
     65 // Creates a bidding script URL that expects the "joinCount" to be
     66 // "expectedJoinCount".
     67 function createJoinCountBiddingScriptURL(expectedJoinCount) {
     68  return createBiddingScriptURL(
     69    { generateBid:
     70        `if (browserSignals.joinCount !== ${expectedJoinCount})
     71           throw "Unexpected joinCount: " + browserSignals.joinCount;`
     72    });
     73 }
     74 
     75 subsetTest(promise_test, async test => {
     76  const uuid = generateUuid(test);
     77 
     78  await joinGroupAndRunBasicFledgeTestExpectingWinner(
     79      test,
     80      { uuid: uuid,
     81        interestGroupOverrides: {name: uuid,
     82                                 biddingLogicURL: createJoinCountBiddingScriptURL(1)}});
     83 
     84  // Joining again, even with a different script URL, should increase the join count.
     85  await joinGroupAndRunBasicFledgeTestExpectingWinner(
     86      test,
     87      { uuid: uuid,
     88        interestGroupOverrides: {name: uuid,
     89                                 biddingLogicURL: createJoinCountBiddingScriptURL(2)}});
     90 }, 'browserSignals.joinCount same joining page.');
     91 
     92 subsetTest(promise_test, async test => {
     93  const uuid = generateUuid(test);
     94 
     95  await joinGroupAndRunBasicFledgeTestExpectingWinner(
     96      test,
     97      { uuid: uuid,
     98        interestGroupOverrides: {name: uuid,
     99                                 biddingLogicURL: createJoinCountBiddingScriptURL(1)}});
    100 
    101  // Attempt to re-join the same interest group from a different top-level origin.
    102  // The join count should still be persisted.
    103  await joinCrossOriginInterestGroupInTopLevelWindow(
    104      test, uuid, OTHER_ORIGIN1, window.location.origin,
    105      { name: uuid,
    106        biddingLogicURL: createJoinCountBiddingScriptURL(2)});
    107 
    108  await runBasicFledgeTestExpectingWinner(test, uuid);
    109 }, 'browserSignals.joinCount different top-level joining origin.');
    110 
    111 subsetTest(promise_test, async test => {
    112  const uuid = generateUuid(test);
    113 
    114  await joinGroupAndRunBasicFledgeTestExpectingWinner(
    115      test,
    116      { uuid: uuid,
    117        interestGroupOverrides: {name: uuid,
    118                                 biddingLogicURL: createJoinCountBiddingScriptURL(1)}});
    119 
    120  // Leaving interest group should clear join count.
    121  await leaveInterestGroup({name: uuid});
    122 
    123  // Check that join count was cleared.
    124  await joinGroupAndRunBasicFledgeTestExpectingWinner(
    125      test,
    126      { uuid: uuid,
    127        interestGroupOverrides: {name: uuid,
    128                                 biddingLogicURL: createJoinCountBiddingScriptURL(1)}});
    129 }, 'browserSignals.joinCount leave and rejoin.');
    130 
    131 subsetTest(promise_test, async test => {
    132  const uuid = generateUuid(test);
    133  await runReportTest(
    134    test, uuid,
    135    { generateBid:
    136        `if (browserSignals.recency === undefined)
    137           throw new Error("Missing recency in browserSignals.")
    138 
    139         if (browserSignals.recency < 0)
    140           throw new Error("Recency is a negative value.")
    141 
    142         if (browserSignals.recency > 30000)
    143           throw new Error("Recency is over 30 seconds threshold.")
    144 
    145         if (browserSignals.recency % 100 !== 0)
    146           throw new Error("Recency is not rounded to multiple of 100 milliseconds.")
    147 
    148         return {'bid': 9,
    149                 'render': interestGroup.ads[0].renderURL};`,
    150      reportWin:
    151        `sendReportTo('${createBidderReportURL(uuid)}');`
    152    },
    153    // expectedReportURLs
    154    [createBidderReportURL(uuid)]
    155  );
    156 }, 'Check recency in generateBid() is below a certain threshold and rounded ' +
    157   'to multiple of 100 milliseconds.');
    158 
    159 // Creates a bidding script URL that expects the "bidCount" to be
    160 // "expectedBidCount".
    161 function createBidCountBiddingScriptURL(expectedBidCount) {
    162  return createBiddingScriptURL(
    163    { generateBid:
    164        `if (browserSignals.bidCount !== ${expectedBidCount})
    165           throw "Unexpected bidCount: " + browserSignals.bidCount;`
    166    });
    167 }
    168 
    169 subsetTest(promise_test, async test => {
    170  const uuid = generateUuid(test);
    171 
    172  // Running an auction should not increment "bidCount".
    173  await joinGroupAndRunBasicFledgeTestExpectingWinner(
    174      test, uuid,
    175      { name: uuid,
    176        biddingLogicURL: createBidCountBiddingScriptURL(0)});
    177 
    178  // These auctions would have no winner if the "bidCount" were incremented.
    179  await runBasicFledgeAuction(test, uuid);
    180  await runBasicFledgeAuction(test, uuid);
    181 }, 'browserSignals.bidCount not incremented when ad not used.');
    182 
    183 subsetTest(promise_test, async test => {
    184  const uuid = generateUuid(test);
    185 
    186  await joinInterestGroup(
    187      test, uuid,
    188      { name: uuid,
    189        biddingLogicURL: createBidCountBiddingScriptURL(0) });
    190  await runBasicFledgeAuctionAndNavigate(test, uuid);
    191  // Wait for the navigation to trigger reports. "bidCount" should be updated before
    192  // any reports are sent.
    193  await waitForObservedRequests(uuid, [createSellerReportURL(uuid)]);
    194 
    195  await joinInterestGroup(
    196      test, uuid,
    197      { name: uuid,
    198        biddingLogicURL: createBidCountBiddingScriptURL(1) });
    199  await runBasicFledgeAuctionAndNavigate(test, uuid);
    200  // Wait for the navigation to trigger reports. "bidCount" should be updated before
    201  // any reports are sent.
    202  await waitForObservedRequests(uuid, [createSellerReportURL(uuid),
    203                                       createSellerReportURL(uuid)]);
    204 
    205  await joinInterestGroup(
    206      test, uuid,
    207      { name: uuid,
    208        biddingLogicURL: createBidCountBiddingScriptURL(2) });
    209  await runBasicFledgeTestExpectingWinner(test, uuid);
    210 }, 'browserSignals.bidCount incremented when ad used.');
    211 
    212 subsetTest(promise_test, async test => {
    213  const uuid = generateUuid(test);
    214 
    215  // Join an interest group and run an auction and navigate to the winning ad,
    216  // increasing the bid count to 1.
    217  await joinInterestGroup(
    218      test, uuid,
    219      { name: uuid,
    220        biddingLogicURL: createBidCountBiddingScriptURL(0)});
    221  await runBasicFledgeAuctionAndNavigate(test, uuid);
    222  // Wait for the navigation to trigger reports. "bidCount" should be updated before
    223  // any reports are sent.
    224  await waitForObservedRequests(uuid, [createSellerReportURL(uuid)]);
    225 
    226  await joinCrossOriginInterestGroupInTopLevelWindow(
    227      test, uuid, OTHER_ORIGIN1, window.location.origin,
    228      { name: uuid,
    229        biddingLogicURL: createBidCountBiddingScriptURL(1) });
    230  await runBasicFledgeTestExpectingWinner(test, uuid);
    231 }, 'browserSignals.bidCount persists across re-join from other top-level origin.');
    232 
    233 subsetTest(promise_test, async test => {
    234  const uuid = generateUuid(test);
    235 
    236  // Join an interest group and run an auction and navigate to the winning ad,
    237  // increasing the bid count to 1.
    238  await joinInterestGroup(
    239      test, uuid,
    240      { name: uuid,
    241        biddingLogicURL: createBidCountBiddingScriptURL(0) });
    242  await runBasicFledgeAuctionAndNavigate(test, uuid);
    243  await waitForObservedRequests(uuid, [createSellerReportURL(uuid)]);
    244 
    245  // Leaving interest group should clear "bidCount".
    246  await leaveInterestGroup({name: uuid});
    247 
    248  // Check that bid count was cleared.
    249  await joinInterestGroup(
    250      test, uuid,
    251      { name: uuid,
    252        biddingLogicURL: createBidCountBiddingScriptURL(0)});
    253  await runBasicFledgeTestExpectingWinner(test, uuid);
    254 }, 'browserSignals.bidCount leave and rejoin.');
    255 
    256 subsetTest(promise_test, async test => {
    257  const uuid = generateUuid(test);
    258 
    259  await joinInterestGroup(
    260      test, uuid,
    261      { name: uuid,
    262        biddingLogicURL: createBidCountBiddingScriptURL(0)} );
    263 
    264  // Run two auctions at once, without any navigations.
    265  // "bidCount" should be 0 for both auctions.
    266  let fencedFrameConfigs =
    267      await Promise.all([runBasicFledgeTestExpectingWinner(test, uuid),
    268                         runBasicFledgeTestExpectingWinner(test, uuid)]);
    269 
    270  // Start navigating to both auction winners.
    271  createAndNavigateFencedFrame(test, fencedFrameConfigs[0]);
    272  createAndNavigateFencedFrame(test, fencedFrameConfigs[1]);
    273 
    274  // Wait for navigations to have sent reports (and thus to have updated
    275  // bid counts).
    276  await waitForObservedRequests(uuid, [createSellerReportURL(uuid),
    277                                       createSellerReportURL(uuid)]);
    278 
    279  // Check that "bidCount" has increased by 2.
    280  await joinInterestGroup(
    281      test, uuid,
    282      { name: uuid,
    283        biddingLogicURL: createBidCountBiddingScriptURL(2) });
    284  await runBasicFledgeTestExpectingWinner(test, uuid);
    285 }, 'browserSignals.bidCount two auctions at once.');
    286 
    287 subsetTest(promise_test, async test => {
    288  const uuid = generateUuid(test);
    289 
    290  // Use a tracker URL for the ad. It won't be successfully loaded, due to missing
    291  // the fenced frame header, but it should be fetched twice.
    292  let trackedRenderURL =
    293      createTrackerURL(window.location.origin, uuid, 'track_get', /*id=*/'ad');
    294  await joinInterestGroup(
    295      test, uuid,
    296      { name: uuid,
    297        biddingLogicURL: createBidCountBiddingScriptURL(0),
    298        ads: [{ renderURL: trackedRenderURL }]
    299      });
    300 
    301  let fencedFrameConfig = await runBasicFledgeTestExpectingWinner(test, uuid);
    302 
    303  // Start navigating two frames to the winning ad.
    304  createAndNavigateFencedFrame(test, fencedFrameConfig);
    305  createAndNavigateFencedFrame(test, fencedFrameConfig);
    306 
    307  // Wait for both navigations to have requested ads (and thus to have updated
    308  // bid counts).
    309  await waitForObservedRequests(uuid, [createSellerReportURL(uuid),
    310                                       trackedRenderURL,
    311                                       trackedRenderURL]);
    312 
    313  // Check that "bidCount" has increased by only 1.
    314  await joinInterestGroup(
    315      test, uuid,
    316      { name: uuid,
    317        biddingLogicURL: createBidCountBiddingScriptURL(1) });
    318  await runBasicFledgeTestExpectingWinner(test, uuid);
    319 }, 'browserSignals.bidCount incremented once when winning ad used twice.');
    320 
    321 subsetTest(promise_test, async test => {
    322  const uuid = generateUuid(test);
    323 
    324  let bidderReportURL = createBidderReportURL(uuid, /*id=*/'winner');
    325 
    326  // Join an interest group named "uuid", which will bid 0.1, losing the first auction.
    327  await joinInterestGroup(
    328      test, uuid,
    329      { name: uuid,
    330        biddingLogicURL: createBiddingScriptURL(
    331          { bid: 0.1, reportWin: `sendReportTo('${createBidderReportURL(uuid, /*id=*/'loser')}')` })
    332      });
    333 
    334  // Join an interest group with the default name, which will bid 1 and win the first
    335  // auction, sending a bidder report.
    336  await joinInterestGroup(
    337      test, uuid,
    338      { biddingLogicURL: createBiddingScriptURL(
    339          { bid: 1, reportWin: `sendReportTo('${bidderReportURL}')` })
    340      });
    341 
    342  // Run an auction that both bidders participate in. Despite the first interest group
    343  // losing, its "bidCount" should be incremented.
    344  await runBasicFledgeAuctionAndNavigate(test, uuid);
    345  // Make sure the right bidder won.
    346  await waitForObservedRequests(uuid, [bidderReportURL, createSellerReportURL(uuid)]);
    347 
    348  // Leave the second interest group (which has the default name).
    349  await leaveInterestGroup();
    350 
    351  // Re-join the first interest group, with a bidding script that checks its "bidCount".
    352  await joinInterestGroup(
    353      test, uuid,
    354      { name: uuid,
    355        biddingLogicURL: createBidCountBiddingScriptURL(1)
    356      });
    357 
    358  await runBasicFledgeTestExpectingWinner(test, uuid);
    359 }, 'browserSignals.bidCount incremented when another interest group wins.');
    360 
    361 subsetTest(promise_test, async test => {
    362  const uuid = generateUuid(test);
    363 
    364  // Use default interest group, other than using a unique name. It will make a bid.
    365  await joinInterestGroup(test, uuid, { name: uuid });
    366  // Run auction with seller that rejects all bids.
    367  await runBasicFledgeTestExpectingNoWinner(
    368      test, uuid,
    369      { decisionLogicURL: createDecisionScriptURL(uuid, {scoreAd: `return 0;`})});
    370 
    371  await joinInterestGroup(
    372      test, uuid,
    373      { name: uuid,
    374        biddingLogicURL: createBidCountBiddingScriptURL(1)
    375      });
    376  await runBasicFledgeTestExpectingWinner(test, uuid);
    377 }, 'browserSignals.bidCount incremented when seller rejects bid.');
    378 
    379 subsetTest(promise_test, async test => {
    380  const uuid = generateUuid(test);
    381 
    382  // Use default interest group, other than using a unique name. It will make a bid.
    383  await joinInterestGroup(test, uuid, { name: uuid });
    384  // Run auction with seller that always throws.
    385  await runBasicFledgeTestExpectingNoWinner(
    386      test, uuid,
    387      { decisionLogicURL: createDecisionScriptURL(uuid, {scoreAd: `throw "a fit";`})});
    388 
    389  await joinInterestGroup(
    390      test, uuid,
    391      { name: uuid,
    392        biddingLogicURL: createBidCountBiddingScriptURL(1)
    393      });
    394  await runBasicFledgeTestExpectingWinner(test, uuid);
    395 }, 'browserSignals.bidCount incremented when seller throws.');
    396 
    397 subsetTest(promise_test, async test => {
    398  const uuid = generateUuid(test);
    399 
    400  // Interest group that does not bid.
    401  await joinInterestGroup(
    402      test, uuid,
    403      { name: uuid,
    404        biddingLogicURL: createBiddingScriptURL(
    405          { generateBid: 'return;' })
    406      });
    407  await runBasicFledgeTestExpectingNoWinner(test, uuid);
    408 
    409  // Check that "bidCount" was not incremented.
    410  await joinInterestGroup(
    411      test, uuid,
    412      { name: uuid,
    413        biddingLogicURL: createBidCountBiddingScriptURL(0)
    414      });
    415  await runBasicFledgeTestExpectingWinner(test, uuid);
    416 }, 'browserSignals.bidCount not incremented when no bid.');
    417 
    418 subsetTest(promise_test, async test => {
    419  const uuid = generateUuid(test);
    420 
    421  let bidderReportURL = createBidderReportURL(uuid, /*id=*/'winner');
    422 
    423  // Interest group that does not bid.
    424  await joinInterestGroup(
    425      test, uuid,
    426      { name: uuid,
    427        biddingLogicURL: createBiddingScriptURL(
    428          { generateBid: 'return;' })
    429      });
    430 
    431  // Join an interest group with the default name, which will bid 1 and win the first
    432  // auction, sending a bidder report.
    433  await joinInterestGroup(
    434      test, uuid,
    435      { biddingLogicURL: createBiddingScriptURL(
    436          { bid: 1, reportWin: `sendReportTo('${bidderReportURL}')` })
    437      });
    438 
    439  // Run an auction that both bidders participate in, and make sure the right bidder won.
    440  await runBasicFledgeAuctionAndNavigate(test, uuid);
    441  await waitForObservedRequests(uuid, [bidderReportURL, createSellerReportURL(uuid)]);
    442 
    443  // Leave the second interest group (which has the default name).
    444  await leaveInterestGroup();
    445 
    446  // Re-join the first interest group, with a bidding script that checks its "bidCount".
    447  await joinInterestGroup(
    448      test, uuid,
    449      { name: uuid,
    450        biddingLogicURL: createBidCountBiddingScriptURL(0)
    451      });
    452 
    453  await runBasicFledgeTestExpectingWinner(test, uuid);
    454 }, 'browserSignals.bidCount not incremented when no bid and another interest group wins.');
    455 
    456 subsetTest(promise_test, async test => {
    457  const uuid = generateUuid(test);
    458 
    459  let bidderReportURL = createBidderReportURL(uuid, /*id=*/'winner');
    460 
    461  await joinInterestGroup(
    462      test, uuid,
    463      { name: uuid,
    464        biddingLogicURL: createBiddingScriptURL(
    465          { bid: 42, reportWin: `sendReportTo('${createBidderReportURL(uuid, /*id=*/'loser')}')` })
    466      });
    467 
    468  // Join an interest group with the default name, which will bid 1 and win the first
    469  // auction, sending a bidder report.
    470  await joinInterestGroup(
    471      test, uuid,
    472      { biddingLogicURL: createBiddingScriptURL(
    473          { bid: 1, reportWin: `sendReportTo('${bidderReportURL}')` })
    474      });
    475 
    476  // Run an auction that both bidders participate in. The scoreAd script rejects the
    477  // first interest group's bid.
    478  await runBasicFledgeAuctionAndNavigate(
    479      test, uuid,
    480      { decisionLogicURL: createDecisionScriptURL(
    481          uuid,
    482          { scoreAd: `if (bid === 42) return -1;`})});
    483  // Make sure the second interest group won.
    484  await waitForObservedRequests(uuid, [bidderReportURL]);
    485 
    486  // Leave the second interest group (which has the default name).
    487  await leaveInterestGroup();
    488 
    489  // Re-join the first interest group, with a bidding script that checks its "bidCount".
    490  await joinInterestGroup(
    491      test, uuid,
    492      { name: uuid,
    493        biddingLogicURL: createBidCountBiddingScriptURL(1)
    494      });
    495 
    496  await runBasicFledgeTestExpectingWinner(test, uuid);
    497 }, 'browserSignals.bidCount incremented when makes largest bid, but seller rejects the bid.');
    498 
    499 // Creates a bidding script URL that expects "prevWinsMs" to be
    500 // "expectedPrevWinsMs". All times in "expectedPrevWinsMs" must be 0.
    501 //
    502 // "adIndex" is the index of the ad to use in the bid.
    503 function createPrevWinsMsBiddingScriptURL(expectedPrevWinsMs, adIndex = 0) {
    504  return createBiddingScriptURL(
    505    { generateBid:
    506        `for (let i = 0; i < browserSignals.prevWinsMs.length; i++) {
    507           // Check age is in a reasonable range.
    508           if (browserSignals.prevWinsMs[i][0] < 0 ||
    509               browserSignals.prevWinsMs[i][0] > 30000) {
    510             throw "Unexpected prevWinsMs time: " + JSON.stringify(browserSignals.prevWinsMs);
    511           }
    512 
    513           // Set age to 0.
    514           browserSignals.prevWinsMs[i][0] = 0;
    515 
    516           // Remove obsolete field, if present.
    517           delete browserSignals.prevWinsMs[i][1].render_url;
    518         }
    519         if (!deepEquals(browserSignals.prevWinsMs, ${JSON.stringify(expectedPrevWinsMs)}))
    520           throw "Unexpected prevWinsMs: " + JSON.stringify(browserSignals.prevWinsMs);
    521 
    522           return {
    523             bid: 1,
    524             render: interestGroup.ads[${adIndex}].renderURL
    525         };`
    526    });
    527 }
    528 
    529 subsetTest(promise_test, async test => {
    530  const uuid = generateUuid(test);
    531 
    532  // Running an auction should not increment "prevWinsMs".
    533  await joinGroupAndRunBasicFledgeTestExpectingWinner(
    534      test, uuid,
    535      { name: uuid,
    536        biddingLogicURL: createPrevWinsMsBiddingScriptURL([])});
    537 
    538  // These auctions would have no winner if the "prevWinsMs" were incremented.
    539  await runBasicFledgeAuction(test, uuid);
    540  await runBasicFledgeAuction(test, uuid);
    541 }, 'browserSignals.prevWinsMs not affected when ad not used.');
    542 
    543 subsetTest(promise_test, async test => {
    544  const uuid = generateUuid(test);
    545 
    546  await joinInterestGroup(
    547      test, uuid,
    548      { name: uuid,
    549        biddingLogicURL: createPrevWinsMsBiddingScriptURL([]) });
    550  await runBasicFledgeAuctionAndNavigate(test, uuid);
    551  // Wait for the navigation to trigger reports. "prevWinsMs" should be updated before
    552  // any reports are sent.
    553  await waitForObservedRequests(uuid, [createSellerReportURL(uuid)]);
    554 
    555  await joinInterestGroup(
    556      test, uuid,
    557      { name: uuid,
    558        biddingLogicURL: createPrevWinsMsBiddingScriptURL(
    559            [[0, {renderURL: createRenderURL(uuid)}]]) });
    560  await runBasicFledgeAuctionAndNavigate(test, uuid);
    561  // Wait for the navigation to trigger reports. "prevWinsMs" should be updated before
    562  // any reports are sent.
    563  await waitForObservedRequests(uuid, [createSellerReportURL(uuid),
    564                                       createSellerReportURL(uuid)]);
    565 
    566  await joinInterestGroup(
    567      test, uuid,
    568      { name: uuid,
    569        biddingLogicURL: createPrevWinsMsBiddingScriptURL(
    570            [ [0, {renderURL: createRenderURL(uuid)}],
    571              [0, {renderURL: createRenderURL(uuid)}]]) });
    572  await runBasicFledgeTestExpectingWinner(test, uuid);
    573 }, 'browserSignals.prevWinsMs, no metadata.');
    574 
    575 subsetTest(promise_test, async test => {
    576  const uuid = generateUuid(test);
    577  const ads = [ {renderURL: createRenderURL(uuid, 0), metadata: null},
    578                {renderURL: createRenderURL(uuid, 1), metadata: ['1', 2, {3: 4}]},
    579                {renderURL: createRenderURL(uuid, 2)} ];
    580 
    581  await joinInterestGroup(
    582      test, uuid,
    583      { name: uuid,
    584        biddingLogicURL: createPrevWinsMsBiddingScriptURL([], /*adIndex=*/0),
    585        ads: ads });
    586  await runBasicFledgeAuctionAndNavigate(test, uuid);
    587  await waitForObservedRequests(uuid, [createSellerReportURL(uuid)]);
    588 
    589  await joinInterestGroup(
    590      test, uuid,
    591      { name: uuid,
    592        biddingLogicURL: createPrevWinsMsBiddingScriptURL(
    593            [[0, {renderURL: createRenderURL(uuid, 0), metadata: null}]],
    594            /*adIndex=*/1),
    595        ads: ads });
    596  await runBasicFledgeAuctionAndNavigate(test, uuid);
    597  await waitForObservedRequests(uuid, [createSellerReportURL(uuid),
    598                                       createSellerReportURL(uuid)]);
    599 
    600  await joinInterestGroup(
    601      test, uuid,
    602      { name: uuid,
    603        biddingLogicURL: createPrevWinsMsBiddingScriptURL(
    604            [ [0, {renderURL: createRenderURL(uuid, 0), metadata: null}],
    605              [0, {renderURL: createRenderURL(uuid, 1), metadata: ['1', 2, {3: 4}]}] ],
    606            /*adIndex=*/2),
    607        ads: ads });
    608  await runBasicFledgeAuctionAndNavigate(test, uuid);
    609  await waitForObservedRequests(uuid, [createSellerReportURL(uuid),
    610                                       createSellerReportURL(uuid),
    611                                       createSellerReportURL(uuid)]);
    612 
    613  await joinInterestGroup(
    614      test, uuid,
    615      { name: uuid,
    616        biddingLogicURL: createPrevWinsMsBiddingScriptURL(
    617            [ [0, {renderURL: createRenderURL(uuid, 0), metadata: null}],
    618              [0, {renderURL: createRenderURL(uuid, 1), metadata: ['1', 2, {3: 4}]}],
    619              [0, {renderURL: createRenderURL(uuid, 2)}] ]),
    620        ads: ads });
    621  await runBasicFledgeTestExpectingWinner(test, uuid);
    622 }, 'browserSignals.prevWinsMs, with metadata.');
    623 
    624 subsetTest(promise_test, async test => {
    625  const uuid = generateUuid(test);
    626  const ads = [ {renderURL: createRenderURL(uuid, 0), metadata: null},
    627                {renderURL: createRenderURL(uuid, 1), metadata: ['1', 2, {3: 4}]},
    628                {renderURL: createRenderURL(uuid, 2)} ];
    629 
    630  await joinInterestGroup(
    631      test, uuid,
    632      { name: uuid,
    633        biddingLogicURL: createPrevWinsMsBiddingScriptURL([]),
    634        ads: [{renderURL: createRenderURL(uuid, 0), metadata: null}] });
    635  await runBasicFledgeAuctionAndNavigate(test, uuid);
    636  await waitForObservedRequests(uuid, [createSellerReportURL(uuid)]);
    637 
    638  await joinInterestGroup(
    639      test, uuid,
    640      { name: uuid,
    641        biddingLogicURL: createPrevWinsMsBiddingScriptURL(
    642            [[0, {renderURL: createRenderURL(uuid, 0), metadata: null}]]),
    643        ads: [{renderURL: createRenderURL(uuid, 1), metadata: ['1', 2, {3: 4}]}] });
    644  await runBasicFledgeAuctionAndNavigate(test, uuid);
    645  await waitForObservedRequests(uuid, [createSellerReportURL(uuid),
    646                                       createSellerReportURL(uuid)]);
    647 
    648  await joinInterestGroup(
    649      test, uuid,
    650      { name: uuid,
    651        biddingLogicURL: createPrevWinsMsBiddingScriptURL(
    652            [ [0, {renderURL: createRenderURL(uuid, 0), metadata: null}],
    653              [0, {renderURL: createRenderURL(uuid, 1), metadata: ['1', 2, {3: 4}]}] ]),
    654        ads: [{renderURL: createRenderURL(uuid, 2)}] });
    655  await runBasicFledgeAuctionAndNavigate(test, uuid);
    656  await waitForObservedRequests(uuid, [createSellerReportURL(uuid),
    657                                       createSellerReportURL(uuid),
    658                                       createSellerReportURL(uuid)]);
    659 
    660  await joinInterestGroup(
    661      test, uuid,
    662      { name: uuid,
    663        biddingLogicURL: createPrevWinsMsBiddingScriptURL(
    664            [ [0, {renderURL: createRenderURL(uuid, 0), metadata: null}],
    665              [0, {renderURL: createRenderURL(uuid, 1), metadata: ['1', 2, {3: 4}]}],
    666              [0, {renderURL: createRenderURL(uuid, 2)}] ]) });
    667  await runBasicFledgeTestExpectingWinner(test, uuid);
    668 }, 'browserSignals.prevWinsMs, different set of ads for each bid.');
    669 
    670 subsetTest(promise_test, async test => {
    671  const uuid = generateUuid(test);
    672 
    673  // Join an interest group and run an auction and navigate to the winning ad,
    674  // which should be logged in "prevWinsMs".
    675  await joinInterestGroup(
    676      test, uuid,
    677      { name: uuid,
    678        biddingLogicURL: createPrevWinsMsBiddingScriptURL([])});
    679  await runBasicFledgeAuctionAndNavigate(test, uuid);
    680  await waitForObservedRequests(uuid, [createSellerReportURL(uuid)]);
    681 
    682  await joinCrossOriginInterestGroupInTopLevelWindow(
    683      test, uuid, OTHER_ORIGIN1, window.location.origin,
    684      { name: uuid,
    685        biddingLogicURL: createPrevWinsMsBiddingScriptURL(
    686            [[0, {renderURL: createRenderURL(uuid)}]]) });
    687  await runBasicFledgeTestExpectingWinner(test, uuid);
    688 }, 'browserSignals.prevWinsMs persists across re-join from other top-level origin.');
    689 
    690 subsetTest(promise_test, async test => {
    691  const uuid = generateUuid(test);
    692 
    693  // Join an interest group and run an auction and navigate to the winning ad,
    694  // which should be logged in "prevWinsMs".
    695  await joinInterestGroup(
    696      test, uuid,
    697      { name: uuid,
    698        biddingLogicURL: createPrevWinsMsBiddingScriptURL([])});
    699  await runBasicFledgeAuctionAndNavigate(test, uuid);
    700  await waitForObservedRequests(uuid, [createSellerReportURL(uuid)]);
    701 
    702  // Leaving interest group should clear "prevWinsMs".
    703  await leaveInterestGroup({name: uuid});
    704 
    705  // Check that bid count was cleared.
    706  await joinInterestGroup(
    707      test, uuid,
    708      { name: uuid,
    709        biddingLogicURL: createPrevWinsMsBiddingScriptURL([])});
    710  await runBasicFledgeTestExpectingWinner(test, uuid);
    711 }, 'browserSignals.prevWinsMs leave and rejoin.');
    712 
    713 subsetTest(promise_test, async test => {
    714  const uuid = generateUuid(test);
    715 
    716  await joinInterestGroup(
    717      test, uuid,
    718      { name: uuid,
    719        biddingLogicURL: createPrevWinsMsBiddingScriptURL([])
    720      });
    721 
    722  // Run two auctions at once, without any navigations.
    723  // "prevWinsMs" should be empty for both auctions.
    724  let fencedFrameConfigs =
    725      await Promise.all([runBasicFledgeTestExpectingWinner(test, uuid),
    726                         runBasicFledgeTestExpectingWinner(test, uuid)]);
    727 
    728  // Start navigating to both auction winners.
    729  createAndNavigateFencedFrame(test, fencedFrameConfigs[0]);
    730  createAndNavigateFencedFrame(test, fencedFrameConfigs[1]);
    731 
    732  // Wait for navigations to have sent reports (and thus to have updated
    733  // "prevWinsMs").
    734  await waitForObservedRequests(uuid, [createSellerReportURL(uuid),
    735                                       createSellerReportURL(uuid)]);
    736 
    737  // Check that "prevWinsMs" has two URLs.
    738  await joinInterestGroup(
    739      test, uuid,
    740      { name: uuid,
    741        biddingLogicURL: createPrevWinsMsBiddingScriptURL(
    742          [[0, {renderURL: createRenderURL(uuid)}],
    743           [0, {renderURL: createRenderURL(uuid)}]])
    744      });
    745  await runBasicFledgeTestExpectingWinner(test, uuid);
    746 }, 'browserSignals.prevWinsMs two auctions at once.');
    747 
    748 subsetTest(promise_test, async test => {
    749  const uuid = generateUuid(test);
    750 
    751  // Use a tracker URL for the ad. It won't be successfully loaded, due to missing
    752  // the fenced frame header, but it should be fetched twice.
    753  let trackedRenderURL =
    754      createTrackerURL(window.location.origin, uuid, 'track_get', /*id=*/'ad');
    755  await joinInterestGroup(
    756      test, uuid,
    757      { name: uuid,
    758        biddingLogicURL: createPrevWinsMsBiddingScriptURL([]),
    759        ads: [{ renderURL: trackedRenderURL }]
    760      });
    761 
    762  let fencedFrameConfig = await runBasicFledgeTestExpectingWinner(test, uuid);
    763 
    764  // Start navigating two frames to the winning ad.
    765  createAndNavigateFencedFrame(test, fencedFrameConfig);
    766  createAndNavigateFencedFrame(test, fencedFrameConfig);
    767 
    768  // Wait for both navigations to have requested ads (and thus to have updated
    769  // "prevWinsMs").
    770  await waitForObservedRequests(uuid, [createSellerReportURL(uuid),
    771                                       trackedRenderURL,
    772                                       trackedRenderURL]);
    773 
    774  // Check that "prevWins" has only a single win.
    775  await joinInterestGroup(
    776      test, uuid,
    777      { name: uuid,
    778        biddingLogicURL: createPrevWinsMsBiddingScriptURL(
    779          [[0, {renderURL: trackedRenderURL}]]) });
    780  await runBasicFledgeTestExpectingWinner(test, uuid);
    781 }, 'browserSignals.prevWinsMs has only one win when winning ad used twice.');
    782 
    783 subsetTest(promise_test, async test => {
    784  const uuid = generateUuid(test);
    785 
    786  let bidderReportURL = createBidderReportURL(uuid, /*id=*/'winner');
    787 
    788  // Join an interest group named "uuid", which will bid 0.1, losing the first auction.
    789  await joinInterestGroup(
    790      test, uuid,
    791      { name: uuid,
    792        biddingLogicURL: createBiddingScriptURL(
    793          { bid: 0.1, reportWin: `sendReportTo('${createBidderReportURL(uuid, /*id=*/'loser')}')` })
    794      });
    795 
    796  // Join an interest group with the default name, which will bid 1 and win the first
    797  // auction, sending a bidder report.
    798  await joinInterestGroup(
    799      test, uuid,
    800      { biddingLogicURL: createBiddingScriptURL(
    801          { bid: 1, reportWin: `sendReportTo('${bidderReportURL}')` })
    802      });
    803 
    804  // Run an auction that both bidders participate in, and make sure the right bidder won.
    805  await runBasicFledgeAuctionAndNavigate(test, uuid);
    806  await waitForObservedRequests(uuid, [bidderReportURL, createSellerReportURL(uuid)]);
    807 
    808  // Leave the second interest group (which has the default name).
    809  await leaveInterestGroup();
    810 
    811  // Re-join the first interest group, with a bidding script that expects prevWinsMs to
    812  // be empty.
    813  await joinInterestGroup(
    814      test, uuid,
    815      { name: uuid,
    816        biddingLogicURL: createPrevWinsMsBiddingScriptURL([])
    817      });
    818 
    819  await runBasicFledgeTestExpectingWinner(test, uuid);
    820 }, 'browserSignals.prevWinsMs not updated when another interest group wins.');
    821 
    822 subsetTest(promise_test, async test => {
    823  const uuid = generateUuid(test);
    824 
    825  // Use default interest group, other than using a unique name. It will make a bid.
    826  await joinInterestGroup(test, uuid, { name: uuid });
    827  // Run auction with seller that rejects all bids.
    828  await runBasicFledgeTestExpectingNoWinner(
    829      test, uuid,
    830      { decisionLogicURL: createDecisionScriptURL(uuid, {scoreAd: `return 0;`})});
    831 
    832  await joinInterestGroup(
    833      test, uuid,
    834      { name: uuid,
    835        biddingLogicURL: createPrevWinsMsBiddingScriptURL([])
    836      });
    837  await runBasicFledgeTestExpectingWinner(test, uuid);
    838 }, 'browserSignals.prevWinsMs not updated when seller rejects bid.');
    839 
    840 subsetTest(promise_test, async test => {
    841  const uuid = generateUuid(test);
    842 
    843  // Use default interest group, other than using a unique name. It will make a bid.
    844  await joinInterestGroup(test, uuid, { name: uuid });
    845  // Run auction with seller that always throws.
    846  await runBasicFledgeTestExpectingNoWinner(
    847      test, uuid,
    848      { decisionLogicURL: createDecisionScriptURL(uuid, {scoreAd: `throw "a fit";`})});
    849 
    850  await joinInterestGroup(
    851      test, uuid,
    852      { name: uuid,
    853        biddingLogicURL: createPrevWinsMsBiddingScriptURL([])
    854      });
    855  await runBasicFledgeTestExpectingWinner(test, uuid);
    856 }, 'browserSignals.prevWinsMs not updated when seller throws.');
    857 
    858 subsetTest(promise_test, async test => {
    859  const uuid = generateUuid(test);
    860 
    861  // Interest group that does not bid.
    862  await joinInterestGroup(
    863    test, uuid,
    864    { name: uuid,
    865      biddingLogicURL: createBiddingScriptURL(
    866        { generateBid: 'return;' })
    867    });
    868  await runBasicFledgeTestExpectingNoWinner(test, uuid);
    869 
    870  // Check that "prevWinsMs" was not modified.
    871  await joinInterestGroup(
    872      test, uuid,
    873      { name: uuid,
    874        biddingLogicURL: createPrevWinsMsBiddingScriptURL([])
    875      });
    876  await runBasicFledgeTestExpectingWinner(test, uuid);
    877 }, 'browserSignals.prevWinsMs not updated when no bid.');
    878 
    879 subsetTest(promise_test, async test => {
    880  const uuid = generateUuid(test);
    881 
    882  let bidderReportURL = createBidderReportURL(uuid, /*id=*/'winner');
    883 
    884  await joinInterestGroup(
    885      test, uuid,
    886      { name: uuid,
    887        biddingLogicURL: createBiddingScriptURL(
    888          { bid: 42, reportWin: `sendReportTo('${createBidderReportURL(uuid, /*id=*/'loser')}')` })
    889      });
    890 
    891  // Join an interest group with the default name, which will bid 1 and win the first
    892  // auction, sending a bidder report.
    893  await joinInterestGroup(
    894      test, uuid,
    895      { biddingLogicURL: createBiddingScriptURL(
    896          { bid: 1, reportWin: `sendReportTo('${bidderReportURL}')` })
    897      });
    898 
    899  // Run an auction that both bidders participate in. The scoreAd script returns a low
    900  // score for the first interest group's bid.
    901  await runBasicFledgeAuctionAndNavigate(
    902      test, uuid,
    903      { decisionLogicURL: createDecisionScriptURL(
    904          uuid,
    905          { scoreAd: `if (bid === 42) return 0.1;`})});
    906  // Make sure the second interest group won.
    907  await waitForObservedRequests(uuid, [bidderReportURL]);
    908 
    909  // Leave the second interest group (which has the default name).
    910  await leaveInterestGroup();
    911 
    912  // Re-join the first interest group, with a bidding script that expects prevWinsMs to
    913  // be empty.
    914  await joinInterestGroup(
    915      test, uuid,
    916      { name: uuid,
    917        biddingLogicURL: createPrevWinsMsBiddingScriptURL([])
    918      });
    919 
    920  await runBasicFledgeTestExpectingWinner(test, uuid);
    921 }, 'browserSignals.prevWinsMs not updated when makes largest bid, but another interest group wins.');
    922 
    923 subsetTest(promise_test, async test => {
    924  const uuid = generateUuid(test);
    925 
    926  // Join an interest group with a WASM helper that exposes a single "increment" method,
    927  // and make sure that method can be invoked and behaves as expected.
    928  await joinGroupAndRunBasicFledgeTestExpectingWinner(
    929    test,
    930    { uuid: uuid,
    931      interestGroupOverrides: {
    932          biddingWasmHelperURL: `${RESOURCE_PATH}wasm-helper.py`,
    933          biddingLogicURL: createBiddingScriptURL(
    934            { generateBid:
    935                `if (!browserSignals.wasmHelper)
    936                   throw "No WASM helper";
    937 
    938                 let instance = new WebAssembly.Instance(browserSignals.wasmHelper);
    939                 if (!instance)
    940                   throw "Couldn't create WASM Instance";
    941 
    942                 if (!deepEquals(Object.keys(instance.exports), ["increment"]))
    943                   throw "Unexpected exports: " + JSON.stringify(instance.exports);
    944 
    945                 if (instance.exports.increment(1) !== 2)
    946                   throw "Unexpected increment result: " + instance.exports.increment(1);` })
    947      }
    948    });
    949 }, 'browserSignals.wasmHelper.');
    950 
    951 
    952 // Generates 0 or 1 clicks, dependent on `produceAttributionSrc` &
    953 // `produceUserAction`, and `numViews` views for `igOwner`, provided by
    954 // `viewClickProvider`.
    955 async function generateViewsAndClicks(
    956    test, uuid, viewClickProvider, igOwner, numViews, produceAttributionSrc,
    957    produceUserAction) {
    958  let iframe = await createIframe(test, viewClickProvider);
    959  let script = `
    960    // We use a wrapper iframe here so the original remains in communication.
    961    let frame = document.createElement('iframe');
    962    document.body.appendChild(frame);
    963    let frameDocument = frame.contentDocument;
    964    let a = frameDocument.createElement('a');
    965    a.href = '${RESOURCE_PATH}/record-click.py?' +
    966        'eligible_origin=${igOwner}&num_views=${numViews}';
    967    if (${produceAttributionSrc}) {
    968      a.attributionSrc = '';
    969    }
    970    a.target = '_self';
    971    a.appendChild(frameDocument.createTextNode('Click me'));
    972    frameDocument.body.appendChild(a);
    973 
    974    if (${produceUserAction}) {
    975      // Note: test_driver.click() seems to not work well with Chrome's
    976      // content_shell; while .bless() does... unreliably.
    977      // headless_shell/chrome path seems to work reliably. User activation
    978      // is used sparingly to work around content_shell flakiness.
    979      await test_driver.bless('User-initiated click', () => { a.click() });
    980    } else {
    981      a.click();
    982    }
    983  `;
    984 
    985  await runInFrame(test, iframe, script);
    986 }