tor-browser

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

component-ads.https.window.js (22301B)


      1 // META: script=/resources/testdriver.js
      2 // META: script=/resources/testdriver-vendor.js
      3 // META: script=/common/utils.js
      4 // META: script=/common/subset-tests.js
      5 // META: script=resources/fledge-util.sub.js
      6 // META: timeout=long
      7 // META: variant=?1-5
      8 // META: variant=?6-10
      9 // META: variant=?11-15
     10 // META: variant=?16-last
     11 
     12 "use strict";
     13 
     14 // Creates a tracker URL for a component ad. These are fetched from component ad URLs.
     15 function createComponentAdTrackerURL(uuid, id) {
     16  return createTrackerURL(window.location.origin, uuid, 'track_get',
     17                          `component_ad_${id}`)
     18 }
     19 
     20 // Returns a component ad render URL that fetches the corresponding component ad
     21 // tracker URL.
     22 function createComponentAdRenderURL(uuid, id) {
     23  return createRenderURL(
     24      uuid,
     25      `fetch("${createComponentAdTrackerURL(uuid, id)}");`);
     26 }
     27 
     28 // Runs a generic component ad loading test. It joins an interest group with a
     29 // "numComponentAdsInInterestGroup" component ads. The IG will make a bid that
     30 // potentially includes some of them. Then an auction will be run, component
     31 // ads potentially will be loaded in nested fenced frame within the main frame,
     32 // and the test will make sure that each component ad render URL that should have
     33 // been loaded in an iframe was indeed loaded.
     34 //
     35 // Joins an interest group that has "numComponentAdsInInterestGroup" component ads.
     36 //
     37 // "componentAdsInBid" is a list of 0-based indices of which of those ads will be
     38 // included in the bid. It may contain duplicate component ads. If it's null then the
     39 // bid will have no adComponents field, while if it is empty, the bid will have an empty
     40 // adComponents field.
     41 //
     42 // "componentAdsToLoad" is another list of 0-based ad components, but it's the index of
     43 // fenced frame configs in the top frame ad's getNestedConfigs(). It may also contain
     44 // duplicates to load a particular ad twice.
     45 //
     46 // If "adMetadata" is true, metadata is added to each component ad. Only integer metadata
     47 // is used, relying on renderURL tests to cover other types of renderURL metadata.
     48 //
     49 // If "deprecatedRenderURLReplacements" is passed, the matches and replacements will be
     50 // used in the trackingURLs and the object will be passed into the auctionConfig, to
     51 // replace matching macros within the renderURLs.
     52 async function runComponentAdLoadingTest(test, uuid, numComponentAdsInInterestGroup,
     53  componentAdsInBid, componentAdsToLoad,
     54  adMetadata = false, deprecatedRenderURLReplacements = null) {
     55  let interestGroupAdComponents = [];
     56  // These are used within the URLs for deprecatedRenderURLReplacement tests.
     57  const renderURLReplacementsStrings = createStringBeforeAndAfterReplacements(deprecatedRenderURLReplacements);
     58  const beforeReplacementsString= renderURLReplacementsStrings.beforeReplacements;
     59  const afterReplacementsString = renderURLReplacementsStrings.afterReplacements;
     60 
     61  for (let i = 0; i < numComponentAdsInInterestGroup; ++i) {
     62    let componentRenderURL = createComponentAdRenderURL(uuid, i);
     63    if (deprecatedRenderURLReplacements !== null) {
     64      componentRenderURL = createTrackerURL(window.location.origin, uuid, 'track_get', beforeReplacementsString);
     65    }
     66    let adComponent = { renderURL: componentRenderURL };
     67    if (adMetadata)
     68      adComponent.metadata = i;
     69    interestGroupAdComponents.push(adComponent);
     70  }
     71 
     72  const renderURL = createRenderURL(
     73      uuid,
     74      `// "status" is passed to the beacon URL, to be verified by waitForObservedRequests().
     75       let status = "ok";
     76       const componentAds = window.fence.getNestedConfigs()
     77       if (componentAds.length !== 40)
     78         status = "unexpected getNestedConfigs() length";
     79       for (let i of ${JSON.stringify(componentAdsToLoad)}) {
     80         let fencedFrame = document.createElement("fencedframe");
     81         fencedFrame.mode = "opaque-ads";
     82         fencedFrame.config = componentAds[i];
     83         document.body.appendChild(fencedFrame);
     84       }
     85 
     86       window.fence.reportEvent({eventType: "beacon",
     87                                 eventData: status,
     88                                 destination: ["buyer"]});`);
     89 
     90  let bid = {bid:1, render:renderURL};
     91  if (componentAdsInBid) {
     92    bid.adComponents = [];
     93    for (let index of componentAdsInBid) {
     94      bid.adComponents.push(interestGroupAdComponents[index].renderURL);
     95    }
     96  }
     97 
     98  // In these tests, the bidder should always request a beacon URL.
     99  let expectedTrackerURLs = [`${createBidderBeaconURL(uuid)}, body: ok`];
    100  // Figure out which, if any, elements of "componentAdsToLoad" correspond to
    101  // component ads listed in bid.adComponents, and for those ads, add a tracker URL
    102  // to "expectedTrackerURLs".
    103  if (componentAdsToLoad && bid.adComponents) {
    104    for (let index of componentAdsToLoad) {
    105      let expectedURL = createComponentAdTrackerURL(uuid, componentAdsInBid[index]);
    106      if (deprecatedRenderURLReplacements != null) {
    107        expectedURL = createTrackerURL(window.location.origin, uuid, 'track_get',
    108                                       afterReplacementsString);
    109      }
    110      if (index < componentAdsInBid.length)
    111        expectedTrackerURLs.push(expectedURL);
    112    }
    113  }
    114 
    115  await joinInterestGroup(
    116      test, uuid,
    117      { biddingLogicURL:
    118          createBiddingScriptURL({
    119              generateBid:
    120                  `let expectedAdComponents = ${JSON.stringify(interestGroupAdComponents)};
    121                   let adComponents = interestGroup.adComponents;
    122                   if (adComponents.length !== expectedAdComponents.length)
    123                     throw "Unexpected adComponents";
    124                   for (let i = 0; i < adComponents.length; ++i) {
    125                    if (adComponents[i].renderURL !== expectedAdComponents[i].renderURL ||
    126                        adComponents[i].metadata !== expectedAdComponents[i].metadata) {
    127                      throw "Unexpected adComponents";
    128                    }
    129                   }
    130                   return ${JSON.stringify(bid)}`,
    131              reportWin:
    132                  `registerAdBeacon({beacon: '${createBidderBeaconURL(uuid)}'});` }),
    133        ads: [{renderURL: renderURL}],
    134        adComponents: interestGroupAdComponents});
    135 
    136  if (!bid.adComponents || bid.adComponents.length === 0) {
    137    await runBasicFledgeAuctionAndNavigate(
    138      test, uuid,
    139      {decisionLogicURL: createDecisionScriptURL(
    140        uuid,
    141        { scoreAd: `if (browserSignals.adComponents !== undefined)
    142                      throw "adComponents should be undefined"`})});
    143  } else {
    144    await runBasicFledgeAuctionAndNavigate(
    145      test, uuid,
    146      {decisionLogicURL: createDecisionScriptURL(
    147        uuid,
    148          { scoreAd:
    149              `if (JSON.stringify(browserSignals.adComponents) !==
    150                       '${JSON.stringify(bid.adComponents)}') {
    151                 throw "Unexpected adComponents: " + JSON.stringify(browserSignals.adComponents);
    152               }`}),
    153        deprecatedRenderURLReplacements: deprecatedRenderURLReplacements
    154      });
    155  }
    156 
    157  await waitForObservedRequests(uuid, expectedTrackerURLs);
    158 }
    159 
    160 subsetTest(promise_test, async test => {
    161  const uuid = generateUuid(test);
    162 
    163  const renderURL = createRenderURL(uuid, `let status = "ok";
    164     const nestedConfigsLength = window.fence.getNestedConfigs().length
    165     // "getNestedConfigs()" should return a list of 40 configs, to avoid leaking
    166     // whether there were any component URLs to the page.
    167     if (nestedConfigsLength !== 40)
    168       status = "unexpected getNestedConfigs() length: " + nestedConfigsLength;
    169     window.fence.reportEvent({eventType: "beacon",
    170                               eventData: status,
    171                               destination: ["buyer"]});`);
    172  await joinInterestGroup(test, uuid, {
    173    biddingLogicURL: createBiddingScriptURL({
    174      generateBid:
    175          'if (interestGroup.componentAds !== undefined) throw "unexpected componentAds"',
    176      reportWin: `registerAdBeacon({beacon: "${createBidderBeaconURL(uuid)}"});`
    177    }),
    178    ads: [{renderURL: renderURL}]
    179  });
    180  await runBasicFledgeAuctionAndNavigate(
    181      test, uuid,
    182      {decisionLogicURL: createDecisionScriptURL(
    183        uuid,
    184        { scoreAd: `if (browserSignals.adComponents !== undefined)
    185                      throw "adComponents should be undefined"`})});
    186  await waitForObservedRequests(uuid, [`${createBidderBeaconURL(uuid)}, body: ok`]);
    187 }, 'Group has no component ads, no adComponents in bid.');
    188 
    189 subsetTest(promise_test, async test => {
    190  const uuid = generateUuid(test);
    191 
    192  await joinGroupAndRunBasicFledgeTestExpectingNoWinner(
    193      test,
    194      {uuid: uuid,
    195       interestGroupOverrides: {
    196           biddingLogicURL:
    197           createBiddingScriptURL({
    198               generateBid:
    199                   `return {bid: 1,
    200                            render: interestGroup.ads[0].renderUrl,
    201                            adComponents: []};`})}});
    202 }, 'Group has no component ads, adComponents in bid is empty array.');
    203 
    204 subsetTest(promise_test, async test => {
    205  const uuid = generateUuid(test);
    206  await runComponentAdLoadingTest(
    207      test, uuid, /*numComponentAdsInInterestGroup=*/2, /*componentAdsInBid=*/null,
    208      // Try to load ad components, even though there are none. This should load
    209      // about:blank in those frames, though that's not testible.
    210      // The waitForObservedRequests() call may see extra requests, racily, if
    211      // component ads not found in the bid are used.
    212      /*componentAdsToLoad=*/[0, 1]);
    213 }, 'Group has component ads, but not used in bid (no adComponents field).');
    214 
    215 subsetTest(promise_test, async test => {
    216  const uuid = generateUuid(test);
    217  await runComponentAdLoadingTest(
    218      test, uuid, /*numComponentAdsInInterestGroup=*/2, /*componentAdsInBid=*/[],
    219      // Try to load ad components, even though there are none. This should load
    220      // about:blank in those frames, though that's not testible.
    221      // The waitForObservedRequests() call may see extra requests, racily, if
    222      // component ads not found in the bid are used.
    223      /*componentAdsToLoad=*/[0, 1]);
    224 }, 'Group has component ads, but not used in bid (adComponents field empty array).');
    225 
    226 subsetTest(promise_test, async test => {
    227  const uuid = generateUuid(test);
    228  await runComponentAdLoadingTest(
    229      test, uuid, /*numComponentAdsInInterestGroup=*/2, /*componentAdsInBid=*/null,
    230      // Try to load ad components, even though there are none. This should load
    231      // about:blank in those frames, though that's not testible.
    232      // The waitForObservedRequests() call may see extra requests, racily, if
    233      // component ads not found in the bid are used.
    234      /*componentAdsToLoad=*/[0, 1], /*adMetadata=*/true);
    235 }, 'Unused component ads with metadata.');
    236 
    237 subsetTest(promise_test, async test => {
    238  const uuid = generateUuid(test);
    239 
    240  await joinGroupAndRunBasicFledgeTestExpectingNoWinner(
    241      test,
    242      { uuid: uuid,
    243        interestGroupOverrides: {
    244            biddingLogicURL:
    245                createBiddingScriptURL({
    246                    generateBid:
    247                        `return {bid: 1,
    248                                 render: interestGroup.ads[0].renderUrl,
    249                                 adComponents: ["https://random.url.test/"]};`}),
    250            adComponents: [{renderURL: createComponentAdRenderURL(uuid, 0)}]}});
    251 }, 'Unknown component ad URL in bid.');
    252 
    253 subsetTest(promise_test, async test => {
    254  const uuid = generateUuid(test);
    255 
    256  await joinGroupAndRunBasicFledgeTestExpectingNoWinner(
    257      test,
    258      { uuid: uuid,
    259        interestGroupOverrides: {
    260            biddingLogicURL:
    261                createBiddingScriptURL({
    262                    generateBid:
    263                        `return {bid: 1,
    264                                 render: interestGroup.ads[0].renderUrl,
    265                                 adComponents: [interestGroup.ads[0].renderUrl]};`}),
    266            adComponents: [{renderURL: createComponentAdRenderURL(uuid, 0)}]}});
    267 }, 'Render URL used as component ad URL in bid.');
    268 
    269 subsetTest(promise_test, async test => {
    270  const uuid = generateUuid(test);
    271 
    272  await joinGroupAndRunBasicFledgeTestExpectingNoWinner(
    273      test,
    274      { uuid: uuid,
    275        interestGroupOverrides: {
    276            biddingLogicURL:
    277                createBiddingScriptURL({
    278                    generateBid:
    279                        `return {bid: 1, render: interestGroup.adComponents[0].renderURL};`}),
    280            adComponents: [{renderURL: createComponentAdRenderURL(uuid, 0)}]}});
    281 }, 'Component ad URL used as render URL.');
    282 
    283 subsetTest(promise_test, async test => {
    284  const uuid = generateUuid(test);
    285  await runComponentAdLoadingTest(test, uuid, /*numComponentAdsInInterestGroup=*/2,
    286                                  /*componentAdsInBid=*/[0, 1], /*componentAdsToLoad=*/[0, 1]);
    287 }, '2 of 2 component ads in bid and then shown.');
    288 
    289 subsetTest(promise_test, async test => {
    290  const uuid = generateUuid(test);
    291  await runComponentAdLoadingTest(test, uuid, /*numComponentAdsInInterestGroup=*/2,
    292                                  /*componentAdsInBid=*/[0, 1], /*componentAdsToLoad=*/[0, 1],
    293                                  /*adMetadata=*/true);
    294 }, '2 of 2 component ads in bid and then shown, with metadata.');
    295 
    296 subsetTest(promise_test, async test => {
    297  const uuid = generateUuid(test);
    298  await runComponentAdLoadingTest(test, uuid, /*numComponentAdsInInterestGroup=*/20,
    299                                  /*componentAdsInBid=*/[3, 10], /*componentAdsToLoad=*/[0, 1]);
    300 }, '2 of 20 component ads in bid and then shown.');
    301 
    302 subsetTest(promise_test, async test => {
    303  const uuid = generateUuid(test);
    304  const intsUpTo19 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19];
    305  await runComponentAdLoadingTest(test, uuid, /*numComponentAdsInInterestGroup=*/20,
    306                                  /*componentAdsInBid=*/intsUpTo19,
    307                                  /*componentAdsToLoad=*/intsUpTo19);
    308 }, '20 of 20 component ads in bid and then shown.');
    309 
    310 subsetTest(promise_test, async test => {
    311  const uuid = generateUuid(test);
    312  const intsUpTo39 = [];
    313  for (let i = 0; i < 40; ++i) {
    314    intsUpTo39.push(i);
    315  }
    316  await runComponentAdLoadingTest(
    317      test, uuid, /*numComponentAdsInInterestGroup=*/ 40,
    318      /*componentAdsInBid=*/ intsUpTo39,
    319      /*componentAdsToLoad=*/ intsUpTo39);
    320 }, '40 of 40 component ads in bid and then shown.');
    321 
    322 subsetTest(promise_test, async test => {
    323  const uuid = generateUuid(test);
    324  await runComponentAdLoadingTest(test, uuid, /*numComponentAdsInInterestGroup=*/20,
    325                                  /*componentAdsInBid=*/[1, 2, 3, 4, 5, 6],
    326                                  /*componentAdsToLoad=*/[1, 3]);
    327 }, '6 of 20 component ads in bid, 2 shown.');
    328 
    329 subsetTest(promise_test, async test => {
    330  const uuid = generateUuid(test);
    331  // It should be possible to load ads multiple times. Each loaded ad should request a new tracking
    332  // URLs, as they're fetched via XHRs, rather than reporting.
    333  await runComponentAdLoadingTest(test, uuid, /*numComponentAdsInInterestGroup=*/4,
    334                                  /*componentAdsInBid=*/[0, 1, 2, 3],
    335                                  /*componentAdsToLoad=*/[0, 1, 1, 0, 3, 3, 2, 2, 1, 0]);
    336 }, '4 of 4 component ads shown multiple times.');
    337 
    338 subsetTest(promise_test, async test => {
    339  const uuid = generateUuid(test);
    340  await runComponentAdLoadingTest(test, uuid, /*numComponentAdsInInterestGroup=*/2,
    341                                  /*componentAdsInBid=*/[0, 0, 0, 0],
    342                                  /*componentAdsToLoad=*/[0, 1, 2, 3]);
    343 }, 'Same component ad used multiple times in bid.');
    344 
    345 subsetTest(promise_test, async test => {
    346  const uuid = generateUuid(test);
    347  // The bid only has one component ad, but the renderURL tries to load 5 component ads.
    348  // The others should all be about:blank. Can't test that, so just make sure there aren't
    349  // more requests than expected, and there's no crash.
    350  await runComponentAdLoadingTest(test, uuid, /*numComponentAdsInInterestGroup=*/2,
    351                                  /*componentAdsInBid=*/[0],
    352                                  /*componentAdsToLoad=*/[4, 3, 2, 1, 0]);
    353 }, 'Load component ads not in bid.');
    354 
    355 subsetTest(promise_test, async test => {
    356  const uuid = generateUuid(test);
    357  const renderURL = createRenderURL(uuid);
    358 
    359  let adComponents = [];
    360  let adComponentsList = [];
    361  for (let i = 0; i < 41; ++i) {
    362    let componentRenderURL = createComponentAdTrackerURL(uuid, i);
    363    adComponents.push({renderURL: componentRenderURL});
    364    adComponentsList.push(componentRenderURL);
    365  }
    366 
    367  await joinGroupAndRunBasicFledgeTestExpectingNoWinner(
    368      test,
    369      { uuid: uuid,
    370        interestGroupOverrides: {
    371            biddingLogicURL:
    372                createBiddingScriptURL({
    373                    generateBid:
    374                        `return {bid: 1,
    375                                 render: "${renderURL}",
    376                                 adComponents: ${JSON.stringify(adComponentsList)}};`}),
    377            ads: [{renderURL: renderURL}],
    378            adComponents: adComponents}});
    379 }, '41 component ads not allowed in bid.');
    380 
    381 subsetTest(promise_test, async test => {
    382  const uuid = generateUuid(test);
    383  const renderURL = createRenderURL(uuid);
    384 
    385  let adComponents = [];
    386  let adComponentsList = [];
    387  for (let i = 0; i < 41; ++i) {
    388    let componentRenderURL = createComponentAdTrackerURL(uuid, i);
    389    adComponents.push({renderURL: componentRenderURL});
    390    adComponentsList.push(adComponents[0].renderURL);
    391  }
    392 
    393  await joinGroupAndRunBasicFledgeTestExpectingNoWinner(
    394      test,
    395      { uuid: uuid,
    396        interestGroupOverrides: {
    397            biddingLogicURL:
    398                createBiddingScriptURL({
    399            generateBid:
    400                `return {bid: 1,
    401                         render: "${renderURL}",
    402                         adComponents: ${JSON.stringify(adComponentsList)}};`}),
    403            ads: [{renderURL: renderURL}],
    404            adComponents: adComponents}});
    405 }, 'Same component ad not allowed 41 times in bid.');
    406 
    407 subsetTest(promise_test, async test => {
    408  const uuid = generateUuid(test);
    409 
    410  // The component ad's render URL will try to send buyer and seller reports,
    411  // which should not be sent (but not throw an exception), and then request a
    412  // a tracker URL via fetch, which should be requested from the server.
    413  const componentRenderURL =
    414      createRenderURL(
    415        uuid,
    416        `window.fence.reportEvent({eventType: "beacon",
    417                                   eventData: "Should not be sent",
    418                                   destination: ["buyer", "seller"]});
    419         fetch("${createComponentAdTrackerURL(uuid, 0)}");`);
    420 
    421  const renderURL = createRenderURL(
    422      uuid,
    423      `let fencedFrame = document.createElement("fencedframe");
    424       fencedFrame.mode = "opaque-ads";
    425       fencedFrame.config = window.fence.getNestedConfigs()[0];
    426       document.body.appendChild(fencedFrame);
    427 
    428       async function waitForRequestAndSendBeacons() {
    429         // Wait for the nested fenced frame to request its tracker URL.
    430         await waitForObservedRequests("${uuid}", ["${createComponentAdTrackerURL(uuid, 0)}"]);
    431 
    432         // Now that the tracker URL has been received, the component ad has tried to
    433         // send a beacon, so have the main renderURL send a beacon, which should succeed
    434         // and should hopefully be sent after the component ad's beacon, if it was
    435         // going to (incorrectly) send one.
    436         window.fence.reportEvent({eventType: "beacon",
    437                                   eventData: "top-ad",
    438                                   destination: ["buyer", "seller"]});
    439       }
    440       waitForRequestAndSendBeacons();`);
    441 
    442  await joinInterestGroup(
    443      test, uuid,
    444      { biddingLogicURL:
    445          createBiddingScriptURL({
    446              generateBid:
    447                  `return {bid: 1,
    448                           render: "${renderURL}",
    449                           adComponents: ["${componentRenderURL}"]};`,
    450              reportWin:
    451                  `registerAdBeacon({beacon: '${createBidderBeaconURL(uuid)}'});` }),
    452        ads: [{renderURL: renderURL}],
    453        adComponents: [{renderURL: componentRenderURL}]});
    454 
    455  await runBasicFledgeAuctionAndNavigate(
    456    test, uuid,
    457    {decisionLogicURL: createDecisionScriptURL(
    458        uuid,
    459        { reportResult: `registerAdBeacon({beacon: '${createSellerBeaconURL(uuid)}'});` }) });
    460 
    461  // Only the renderURL should have sent any beacons, though the component ad should have sent
    462  // a tracker URL fetch request.
    463  await waitForObservedRequests(uuid, [createComponentAdTrackerURL(uuid, 0),
    464                                       `${createBidderBeaconURL(uuid)}, body: top-ad`,
    465                                       `${createSellerBeaconURL(uuid)}, body: top-ad`]);
    466 
    467 
    468 }, 'Reports not sent from component ad.');
    469 
    470 subsetTest(promise_test, async test => {
    471  const uuid = generateUuid(test);
    472  await runComponentAdLoadingTest(test, uuid, /*numComponentAdsInInterestGroup=*/1,
    473                                  /*componentAdsInBid=*/[0], /*componentAdsToLoad=*/[0], false, { '%%EXAMPLE-MACRO%%': 'SSP' });
    474 }, 'component ad with render url replacements with percents.');
    475 
    476 subsetTest(promise_test, async test => {
    477  const uuid = generateUuid(test);
    478  await runComponentAdLoadingTest(test, uuid, /*numComponentAdsInInterestGroup=*/1,
    479                                  /*componentAdsInBid=*/[0], /*componentAdsToLoad=*/[0], false, { '${EXAMPLE-MACRO}': 'SSP' });
    480 }, 'component ad with render url replacements with brackets.');
    481 
    482 subsetTest(promise_test, async test => {
    483  const uuid = generateUuid(test);
    484  await runComponentAdLoadingTest(test, uuid, /*numComponentAdsInInterestGroup=*/1,
    485                                  /*componentAdsInBid=*/[0], /*componentAdsToLoad=*/[0], false, { '${EXAMPLE-MACRO-1}': 'SSP-1', '%%EXAMPLE-MACRO-2%%': 'SSP-2' });
    486 }, 'component ad with render url replacements with multiple replacements.');
    487 
    488 subsetTest(promise_test, async test => {
    489  const uuid = generateUuid(test);
    490  await runComponentAdLoadingTest(test, uuid, /*numComponentAdsInInterestGroup=*/3,
    491                                  /*componentAdsInBid=*/[0,1,2], /*componentAdsToLoad=*/[0,1,2], false, { '${EXAMPLE-MACRO-1}': 'SSP-1', '%%EXAMPLE-MACRO-2%%': 'SSP-2' });
    492 }, 'component ad with render url replacements with multiple replacements, and multiple component ads.');