Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FLEDGE: Send creative scanning metadata when enabled #50247

Merged
merged 1 commit into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions fledge/tentative/resources/fledge_http_server_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,15 @@ def decode_trusted_scoring_signals_params(request):
adComponentRenderURLs = list(map(unquote_plus, pair[1].split(",")))
urlLists.append({"type":"adComponentRenderURLs", "urls":adComponentRenderURLs})
continue
# Ignore the various creative scanning params; they're expected, but we
# don't parse them here.
if (pair[0] == 'adCreativeScanningMetadata' or
pair[0] == 'adComponentCreativeScanningMetadata' or
pair[0] == 'adSizes' or
pair[0] == 'adComponentSizes' or
pair[0] == 'adBuyer' or
pair[0] == 'adComponentBuyer'):
continue
raise ValueError("Unexpected query parameter: " + param)

# "hostname" and "renderUrls" are mandatory.
Expand Down
313 changes: 312 additions & 1 deletion fledge/tentative/trusted-scoring-signals.https.window.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
// META: variant=?36-40
// META: variant=?41-45
// META: variant=?46-50
// META: variant=?51-last
// META: variant=?51-55
// META: variant=?56-last

"use strict";

Expand Down Expand Up @@ -786,3 +787,313 @@ subsetTest(promise_test, async test => {

await runBasicFledgeTestExpectingWinner(test, uuid, auctionConfigOverrides);
}, 'Trusted scoring signals splits the request if the combined URL length exceeds the limit of small value.');

// A little helper to extract out trusted signals query params echoed back
// by trusted-scoring-signals.py; sticks them in a map named `parsed`.
function makeParseHelper(renderURL) {
return `
let payload = trustedScoringSignals.renderURL['${renderURL}'];
payload = payload.substring(payload.indexOf('?') + 1).split('&');
let parsed = new Map();
for (let entry of payload) {
let kv = entry.split('=');
parsed.set(kv[0], decodeURIComponent(kv[1]));
}
`;
}

subsetTest(promise_test, async test => {
const uuid = generateUuid(test);
const renderURL =
createRenderURL(uuid, /*script=*/null, /*signalsParam=*/'url');

const bidder_origin = OTHER_ORIGIN1;

await joinCrossOriginInterestGroup(
test, uuid, bidder_origin,
{ads: [{renderURL: renderURL, creativeScanningMetadata: 'hello'}]});

const scoreAdBody = `
${makeParseHelper(renderURL)}
if (parsed.get('renderUrls') !== '${renderURL}')
throw 'Wrong URL';
if (parsed.get('adCreativeScanningMetadata') !== 'hello')
throw 'Wrong creative scanning metadata';
if (parsed.get('adSizes') !== ',')
throw 'Wrong adSizes';
if (parsed.get('adBuyer') !== '${bidder_origin}')
throw 'Wrong adBuyer';
if (parsed.has('adComponentRenderUrls'))
throw 'Unexpected adComponentRenderUrls';
if (parsed.has('adComponentCreativeScanningMetadata'))
throw 'Unexpected adComponentCreativeScanningMetadata';
if (parsed.has('adComponentSizes'))
throw 'Unexpected adComponentSizes';
if (parsed.has('adComponentBuyer'))
throw 'Unexpected adComponentBuyer';
`;

const auctionConfigOverrides = {
interestGroupBuyers : [bidder_origin],
trustedScoringSignalsURL: TRUSTED_SCORING_SIGNALS_URL,
sendCreativeScanningMetadata: true,
decisionLogicURL: createDecisionScriptURL(uuid, {scoreAd: scoreAdBody})
};

await runBasicFledgeTestExpectingWinner(test, uuid, auctionConfigOverrides);
}, 'Creative scanning metadata - basic data flow');

subsetTest(promise_test, async test => {
const uuid = generateUuid(test);
const renderURL =
createRenderURL(uuid, /*script=*/null, /*signalsParam=*/'url');

const bidder_origin = OTHER_ORIGIN1;

await joinCrossOriginInterestGroup(
test, uuid, bidder_origin,
{ads: [{renderURL: renderURL}]});

const scoreAdBody = `
${makeParseHelper(renderURL)}
if (parsed.get('renderUrls') !== '${renderURL}')
throw 'Wrong URL';
if (parsed.get('adCreativeScanningMetadata') !== '')
throw 'Wrong creative scanning metadata';
if (parsed.get('adSizes') !== ',')
throw 'Wrong adSizes';
if (parsed.get('adBuyer') !== '${bidder_origin}')
throw 'Wrong adBuyer';
if (parsed.has('adComponentRenderUrls'))
throw 'Unexpected adComponentRenderUrls';
if (parsed.has('adComponentCreativeScanningMetadata'))
throw 'Unexpected adComponentCreativeScanningMetadata';
if (parsed.has('adComponentSizes'))
throw 'Unexpected adComponentSizes';
if (parsed.has('adComponentBuyer'))
throw 'Unexpected adComponentBuyer';
`;

const auctionConfigOverrides = {
interestGroupBuyers : [bidder_origin],
trustedScoringSignalsURL: TRUSTED_SCORING_SIGNALS_URL,
sendCreativeScanningMetadata: true,
decisionLogicURL: createDecisionScriptURL(uuid, {scoreAd: scoreAdBody})
};

await runBasicFledgeTestExpectingWinner(test, uuid, auctionConfigOverrides);
}, 'Creative scanning metadata - sending enabled but no metadata specified');

subsetTest(promise_test, async test => {
const uuid = generateUuid(test);
const renderURL =
createRenderURL(uuid, /*script=*/null, /*signalsParam=*/'url');

await joinInterestGroup(
test, uuid,
{ads: [{renderURL: renderURL, creativeScanningMetadata: 'hello'}]});

const scoreAdBody = `
${makeParseHelper(renderURL)}
if (parsed.get('renderUrls') !== '${renderURL}')
throw 'Wrong URL';
if (parsed.has('adCreativeScanningMetadata'))
throw 'Unexpected creative scanning metadata';
if (parsed.has('adSizes'))
throw 'Unexpected adSizes';
if (parsed.has('adBuyer'))
throw 'Unexpected adBuyer';
if (parsed.has('adComponentRenderUrls'))
throw 'Unexpected adComponentRenderUrls';
if (parsed.has('adComponentCreativeScanningMetadata'))
throw 'Unexpected adComponentCreativeScanningMetadata';
if (parsed.has('adComponentSizes'))
throw 'Unexpected adComponentSizes';
if (parsed.has('adComponentBuyer'))
throw 'Unexpected adComponentBuyer';
`;

const auctionConfigOverrides = {
trustedScoringSignalsURL: TRUSTED_SCORING_SIGNALS_URL,
sendCreativeScanningMetadata: false,
decisionLogicURL: createDecisionScriptURL(uuid, {scoreAd: scoreAdBody})
};

await runBasicFledgeTestExpectingWinner(test, uuid, auctionConfigOverrides);
}, 'Creative scanning metadata - disabled');

subsetTest(promise_test, async test => {
const uuid = generateUuid(test);
const renderURL =
createRenderURL(uuid, /*script=*/null, /*signalsParam=*/'url');

const generateBidBody = `
return {
bid: 1,
render: {
url: interestGroup.ads[0].renderURL,
width: '100px',
height: '10sh'
}
};
`;

await joinInterestGroup(test, uuid, {
ads: [{
renderURL: renderURL,
creativeScanningMetadata: 'hello',
sizeGroup: 'flexible'
}],
sizeGroups: {'flexible': ['small', 'big']},
adSizes: {
'small': {width: '100px', height: '10sh'},
'big': {width: '50sw', height: '200px'},
},
biddingLogicURL: createBiddingScriptURL({generateBid: generateBidBody})
});

const scoreAdBody = `
${makeParseHelper(renderURL)}
if (parsed.get('renderUrls') !== '${renderURL}')
throw 'Wrong URL';
if (parsed.get('adCreativeScanningMetadata') !== 'hello')
throw 'Wrong creative scanning metadata';
if (parsed.get('adSizes') !== '100px,10sh')
throw 'Wrong adSizes';
if (parsed.get('adBuyer') !== '${window.location.origin}')
throw 'Wrong adBuyer';
if (parsed.has('adComponentRenderUrls'))
throw 'Unexpected adComponentRenderUrls';
if (parsed.has('adComponentCreativeScanningMetadata'))
throw 'Unexpected adComponentCreativeScanningMetadata';
if (parsed.has('adComponentSizes'))
throw 'Unexpected adComponentSizes';
if (parsed.has('adComponentBuyer'))
throw 'Unexpected adComponentBuyer';
`;

const auctionConfigOverrides = {
trustedScoringSignalsURL: TRUSTED_SCORING_SIGNALS_URL,
sendCreativeScanningMetadata: true,
decisionLogicURL: createDecisionScriptURL(uuid, {scoreAd: scoreAdBody})
};

await runBasicFledgeTestExpectingWinner(test, uuid, auctionConfigOverrides);
}, 'Creative scanning metadata - ad size');

subsetTest(promise_test, async test => {
const uuid = generateUuid(test);
const renderURL =
createRenderURL(uuid, /*script=*/null, /*signalsParam=*/'url');
const componentURL1 = createRenderURL(
uuid, /*script=*/null, /*signalsParam=*/'url,component1');
const componentURL2 = createRenderURL(
uuid, /*script=*/null, /*signalsParam=*/'url,component2');

const generateBidBody = `
return {
bid: 1,
render: {
url: interestGroup.ads[0].renderURL,
},
'adComponents': [
{url: '${componentURL1}'},
{url: '${componentURL2}', width: '50sw', height: '200px'},
]
};
`;

await joinInterestGroup(test, uuid, {
ads: [{
renderURL: renderURL,
creativeScanningMetadata: 'hello',
sizeGroup: 'flexible'
}],
adComponents: [
{
renderUrl: componentURL1,
sizeGroup: 'flexible',
creativeScanningMetadata: 'c1'
},
{
renderUrl: componentURL2,
sizeGroup: 'flexible',
creativeScanningMetadata: 'c2'
}
],
sizeGroups: {'flexible': ['small', 'big']},
adSizes: {
'small': {width: '100px', height: '10sh'},
'big': {width: '50sw', height: '200px'},
},
biddingLogicURL: createBiddingScriptURL({generateBid: generateBidBody})
});

const scoreAdBody = `
${makeParseHelper(renderURL)}
if (parsed.get('renderUrls') !== '${renderURL}')
throw 'Wrong URL';
if (parsed.get('adCreativeScanningMetadata') !== 'hello')
throw 'Wrong creative scanning metadata';
if (parsed.get('adSizes') !== ',')
throw 'Wrong adSizes';
if (parsed.get('adBuyer') !== '${window.location.origin}')
throw 'Wrong adBuyer';

// We have to be careful here since we don't order which order the
// components are going to be reported in; so we normalize them and sort
// them.
let adComponentRenderUrls = parsed.get('adComponentRenderUrls').split(',');
let adComponentCreativeScanningMetadata =
parsed.get('adComponentCreativeScanningMetadata').split(',');
let adComponentSizes = parsed.get('adComponentSizes').split(',');
let adComponentBuyer = parsed.get('adComponentBuyer').split(',');

if (adComponentCreativeScanningMetadata.length !=
adComponentRenderUrls.length) {
throw 'Wrong adComponentCreativeScanningMetadata.length';
}

if (adComponentSizes.length !== 2 * adComponentRenderUrls.length) {
throw 'Wrong adComponentSizes.length';
}

if (adComponentBuyer.length !== adComponentRenderUrls.length) {
throw 'Wrong adComponentBuyer.length';
}

let composed = [];
for (let i = 0; i < adComponentRenderUrls.length; ++i) {
let entry = 'url:' + adComponentRenderUrls[i] +
'; creativeScanningMetadata:' +
adComponentCreativeScanningMetadata[i] +
'; size:' + adComponentSizes[2*i] + 'x' + adComponentSizes[2*i+1] +
'; buyer:' + adComponentBuyer[i];
entry = entry.replaceAll('${componentURL1}', 'componentURL1');
entry = entry.replaceAll('${componentURL2}', 'componentURL2');
entry = entry.replaceAll('${window.location.origin}', 'buyer');
composed.push(entry);
}
composed.sort();
if (composed.length !== 2) {
throw 'Wrong # of component entries overall';
}
if (composed[0] !==
'url:componentURL1; creativeScanningMetadata:c1; size:x; buyer:buyer') {
throw 'Wrong component 0';
}
if (composed[1] !==
'url:componentURL2; creativeScanningMetadata:c2; size:50swx200px; ' +
'buyer:buyer') {
throw 'Wrong component 1';
}
`;

const auctionConfigOverrides = {
trustedScoringSignalsURL: TRUSTED_SCORING_SIGNALS_URL,
sendCreativeScanningMetadata: true,
decisionLogicURL: createDecisionScriptURL(uuid, {scoreAd: scoreAdBody})
};

await runBasicFledgeTestExpectingWinner(test, uuid, auctionConfigOverrides);
}, 'Creative scanning metadata - ad components');
Loading