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.');