Protected Audience (formerly FLEDGE)

Draft Community Group Report ,

This version:
https://wicg.github.io/turtledove/
Editor:
( Google )
Participate:
GitHub WICG/turtledove ( new issue , open issues )
Commits:
GitHub spec.bs commits
Not Ready For Implementation

This spec is not yet ready for implementation. It exists in this repository to record the ideas and promote discussion.

Before attempting to implement this spec, please contact the editors.


Abstract

Provides a privacy advancing API to facilitate interest group based advertising.

Status of this document

This specification was published by the Web Platform Incubator Community Group . It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups .

1. Introduction

This section is non-normative

The Protected Audience API facilitates selecting an advertisement to display to a user based on a previous interaction with the advertiser or advertising network.

When a user’s interactions with an advertiser indicate an interest in something, the advertiser can ask the browser to record this interest on-device by calling navigator . joinAdInterestGroup() . Later, when a website wants to select an advertisement to show to the user, the website can call navigator . runAdAuction() to ask the browser to conduct an auction where each of these on-device recorded interests are given the chance to calculate a bid to display their advertisement.

2. Joining Interest Groups

When a user’s interactions with a website indicate that the user may have a particular interest, an advertiser or someone working on behalf of the advertiser (e.g. a demand side platform, DSP) can ask the user’s browser to record this interest on-device by calling navigator . joinAdInterestGroup() . This indicates an intent to display an advertisement relevant to this interest to this user in the future. The user agent has an interest group set , a list of interest groups in which owner / name pairs are unique.

[SecureContext]
partial interface Navigator {
  Promise<undefined> joinAdInterestGroup(AuctionAdInterestGroup group, double durationSeconds);
};
dictionary AuctionAd {
  required USVString renderURL;
  any metadata;
};
dictionary AuctionAdInterestGroup {
  required USVString owner;
  required USVString name;
  double priority = 0.0;
  boolean enableBiddingSignalsPrioritization = false;
  record<DOMString, double> priorityVector;
  record<DOMString, double> prioritySignalsOverrides;
  DOMString executionMode = "compatibility";
  USVString biddingLogicURL;
  USVString biddingWasmHelperURL;
  USVString updateURL;
  USVString trustedBiddingSignalsURL;
  sequence<USVString> trustedBiddingSignalsKeys;
  any userBiddingSignals;
  sequence<AuctionAd> ads;
  sequence<AuctionAd> adComponents;
};

The joinAdInterestGroup( group , durationSeconds ) method steps are:

  1. If this 's relevant global object 's associated Document is not allowed to use the " join-ad-interest-group " policy-controlled feature , then throw a " NotAllowedError " DOMException .

  2. Let frameOrigin be the relevant settings object 's origin .

  3. Assert that frameOrigin is not an opaque origin and its scheme is "https".

  4. Let interestGroup be a new interest group .

  5. Validate the given group and set interestGroup ’s fields accordingly.

    1. Set interestGroup ’s expiry to now plus durationSeconds .

    2. Set interestGroup ’s next update after to now plus 24 hours.

    3. Set interestGroup ’s owner to the result of parsing an origin on group [" owner "].

    4. If interestGroup ’s owner is an error, or its scheme is not " https ", throw a TypeError .

    5. Set interestGroup ’s name to group [" name "].

    6. Set interestGroup ’s priority to group [" priority "].

    7. Set interestGroup ’s enable bidding signals prioritization to group [" enableBiddingSignalsPrioritization "].

    8. If group [" priorityVector "] exists , then set interestGroup ’s priority vector to group [" priorityVector "].

    9. If group [" prioritySignalsOverrides "] exists , then set interestGroup ’s priority signals overrides to group [" prioritySignalsOverrides "].

    10. Set interestGroup ’s execution mode to group [" executionMode "].

    11. For each groupMember and interestGroupField in the following table

      Group member Interest group field
      " biddingLogicURL " bidding url
      " biddingWasmHelperURL " bidding wasm helper url
      " updateURL " update url
      " trustedBiddingSignalsURL " trusted bidding signals url
      1. If group contains groupMember :

        1. Let parsedUrl be the result of running the URL parser on group [ groupMember ].

        2. Throw a TypeError if any of the following conditions hold:

        3. Set interestGroup ’s interestGroupField to parsedUrl .

    12. If interestGroup ’s trusted bidding signals url 's query is not null, then throw a TypeError .

    13. If group [" trustedBiddingSignalsKeys "] exists , then set interestGroup ’s trusted bidding signals keys to group [" trustedBiddingSignalsKeys "].

    14. If group [" userBiddingSignals "] exists :

      1. Let interestGroup ’s user bidding signals be the result of serializing a JavaScript value to a JSON string , given group [" userBiddingSignals "]. This can throw a TypeError .

    15. For each groupMember and interestGroupField in the following table

      Group member Interest group field
      " ads " ads
      " adComponents " ad components
      1. For each ad of group [ groupMember ]:

        1. Let igAd be a new interest group ad .

        2. Let renderURL be the result of running the URL parser on ad [" renderURL "].

        3. Throw a TypeError if any of the following conditions hold:

        4. Set igAd ’s render url to renderURL .

        5. If ad [" metadata "] exists , then let igAd ’s metadata be the result of serializing a JavaScript value to a JSON string , given ad [" metadata "]. This can throw a TypeError .

        6. Append igAd to interestGroup ’s interestGroupField .

  6. If interestGroup ’s estimated size is greater than 50 KB, then throw a TypeError .

  7. Let p be a new promise .

  8. Let queue be the result of starting a new parallel queue .

  9. Enqueue the following steps to queue :

    1. Let permission be the result of checking interest group permissions with interestGroup ’s owner , frameOrigin , and true.

    2. If permission is false, then queue a task to reject p with a " NotAllowedError " DOMException and do not run the remaining steps.

    3. Queue a task to resolve p with undefined .

    4. If the browser is currently storing an interest group with owner and name that matches interestGroup , then set the bid counts , join counts , and previous wins of interestGroup to the values of the currently stored one and remove the currently stored one from the browser.

    5. Set interestGroup ’s joining origin to this 's relevant settings object 's top-level origin .

    6. If the most recent entry in join counts corresponds to the current local day, increment its count. If not, insert a new entry with the time set to the current local day and a count of 1.

    7. Store interestGroup in the browser’s interest group set .

  10. Return p .

The estimated size of an interest group ig is the sum of:

  1. The length of the serialization of ig ’s owner .

  2. The length of ig ’s name .

  3. 8 bytes, which is the size of ig ’s priority .

  4. The length of ig ’s execution mode .

  5. 2 bytes, which is the size of ig ’s enable bidding signals prioritization .

  6. If ig ’s priority vector is not null, for each key value of priority vector :

    1. The length of key .

    2. 8 bytes, which is the size of value .

  7. If ig ’s priority signals overrides is not null, for each key value of priority signals overrides :

    1. The length of key .

    2. 8 bytes, which is the size of value .

  8. The size of execution mode .

  9. The length of the serialization of ig ’s bidding url , if the field is not null.

  10. The length of the serialization of ig ’s bidding wasm helper url , if the field is not null.

  11. The length of the serialization of ig ’s update url , if the field is not null.

  12. The length of the serialization of ig ’s trusted bidding signals url , if the field is not null.

  13. For each key of ig ’s trusted bidding signals keys :

    1. The length of key .

  14. The length of ig ’s user bidding signals .

  15. If ig ’s ads is not null, for each ad of it:

    1. The length of the serialization of ad ’s render url .

    2. The length of ad ’s metadata if the field is not null.

  16. If ig ’s ad components is not null, for each ad of it:

    1. The length of the serialization of ad ’s render url .

    2. The length of ad ’s metadata if the field is not null.

To check interest group permissions given an origin ownerOrigin , an origin frameOrigin , and a boolean isJoin :

  1. If ownerOrigin is same origin to frameOrigin , then return true.

  2. Let permissionsUrl be the result of building an interest group permissions url with ownerOrigin and frameOrigin .

  3. Let request be the result of creating a request with permissionsUrl , " application/json ", and null.

  4. Let resource be null.

  5. Fetch request with processResponseConsumeBody set to the following steps given a response response and responseBody :

    1. If responseBody is null or failure, set resource to failure and return.

    2. Let headers be response ’s header list .

    3. If getting a structured field value "X-Allow-Protected-Audience" from headers does not return true, set resource to failure and return.

    4. Let mimeType be the result of extracting a MIME type from headers .

    5. If mimeType is failure or is not a JSON MIME Type , throw, set resource to failure and return.

    6. Set resource to responseBody .

  6. Wait for resource to be set.

  7. If resource is failure, then return false.

  8. Let permissions be the result of parsing a JSON string to an Infra value , returning false on failure.

  9. If permissions is not an ordered map , then return false.

  10. If isJoin is true and permissions [" joinAdInterestGroup "] exists , then return permissions [" joinAdInterestGroup "].

  11. If isJoin is false and permissions [" leaveAdInterestGroup "] exists , then return permissions [" leaveAdInterestGroup "].

  12. Return false.

The browser may cache requests for permissionsUrl within a network partition.

In order to prevent leaking data, the browser must request permissionsUrl regardless of whether the user is a member of the ad interest group. This prevents a leak of the user’s ad interest group membership to the server.

To build an interest group permissions url given a origin ownerOrigin and a origin frameOrigin :

  1. Let serializedFrameOrigin be the result of serializing frameOrigin .

  2. Return the string formed by concatenating

3. Leaving Interest Groups

In order to remove a user from a particular interest group,

navigator . leaveAdInterestGroup() can be called.

TODO: Edit the following from the explainer. As a special case to support in-ad UIs, invoking navigator.leaveAdInterestGroup() from inside an ad that is being targeted at a particular interest group will cause the browser to leave that group, irrespective of permission policies. Note that calling navigator.leaveAdInterestGroup() without arguments isn’t supported inside a component ad frame.

[SecureContext]
partial interface Navigator {
  Promise<undefined> leaveAdInterestGroup(AuctionAdInterestGroupKey group);
};
dictionary AuctionAdInterestGroupKey {
  required USVString owner;
  required USVString name;
};

The leaveAdInterestGroup(group) method steps are:

  1. If this 's relevant global object 's associated Document is not allowed to use the " join-ad-interest-group " policy-controlled feature , then throw a " NotAllowedError " DOMException .

  2. Let owner be the result of parsing an origin with group [" owner "].

  3. If owner is failure, throw a TypeError .

  4. Let name be group [" name "].

  5. Let p be a new promise .

  6. Run these steps in parallel :

    1. TODO: document .well-known fetches for cross-origin leaves.

    2. Queue a task to resolve p with undefined .

    3. Remove interest groups from the user agent’s interest group set whose owner is owner and name is name .

  7. Return p .

4. Running Ad Auctions

When a website or someone working on behalf of the website (e.g. a supply side platform, SSP) wants to conduct an auction to select an advertisement to display to the user, they can call the navigator . runAdAuction() function, providing an auction configuration that tells the browser how to conduct the auction and which on-device recorded interests are allowed to bid in the auction for the chance to display their advertisement.

4.1. runAdAuction()

[SecureContext]
partial interface Navigator {
  Promise<USVString?> runAdAuction(AuctionAdConfig config);
};
dictionary AuctionAdConfig {
  required USVString seller;
  required USVString decisionLogicURL;
  USVString trustedScoringSignalsURL;
  sequence<USVString> interestGroupBuyers;
  any auctionSignals;
  any sellerSignals;
  USVString directFromSellerSignals;
  unsigned long long sellerTimeout;
  unsigned short sellerExperimentGroupId;
  record<USVString, any> perBuyerSignals;
  record<USVString, unsigned long long> perBuyerTimeouts;
  record<USVString, unsigned short> perBuyerGroupLimits;
  record<USVString, unsigned short> perBuyerExperimentGroupIds;
  record<USVString, record<USVString, double>> perBuyerPrioritySignals;
  sequence<AuctionAdConfig> componentAuctions = [];
  AbortSignal? signal;
};

The runAdAuction( config ) method steps are:

  1. Let global be this 's relevant global object .

  2. If global ’s associated Document is not allowed to use the " run-ad-auction " policy-controlled feature , then throw a " NotAllowedError " DOMException .

  3. Let auctionConfig be the result of running validate and convert auction ad config with config and isTopLevel set to true.

  4. If auctionConfig is failure, then throw a TypeError .

  5. Let p be a new promise .

  6. If config [" signal "] exists , then:

    1. Let signal be config [" signal "].

    2. If signal is aborted , then reject p with signal ’s abort reason and return p .

    3. Add the following abort steps to signal :

      1. Reject p with signal ’s abort reason .

      2. TODO: Update bidCount for interest groups that participated in the auction.

      3. Run interest group update with auctionConfig ’s interest group buyers .

  7. Let queue be the result of starting a new parallel queue .

  8. Enqueue the following steps to queue :

    1. Let winner be the result of running generate and score bids with auctionConfig , null, and global .

    2. If winner is null:

      1. Queue a global task on DOM manipulation task source , given global , to resolve p with null.

    3. Otherwise:

      1. Queue a global task on the DOM manipulation task source , given global , to resolve p

        with winner ’s ad descriptor . TODO: resolve p with urn-uuid, instead of a URL.

    4. Run interest group update with auctionConfig ’s interest group buyers .

    5. TODO: Update bidCount and prevWins for interest groups that participated in the auction.

  9. Return p .

To validate and convert auction ad config given an AuctionAdConfig config and a boolean isTopLevel :

  1. Let auctionConfig be a new auction config .

  2. Let auctionConfig ’s seller be the result of parsing an origin with config [" seller "].

  3. Throw a TypeError if auctionConfig ’s seller is an error, or its scheme is not " https ".

  4. Let decisionLogicURL be the result of running the URL parser on config [" decisionLogicURL "].

    1. Throw a TypeError if decisionLogicURL is an error, or it is not same origin with auctionConfig ’s seller .

    2. Assert : decisionLogicURL ’s scheme is " https ".

    3. Set auctionConfig ’s decision logic url to decisionLogicURL .

  5. If config [" trustedScoringSignalsURL "] exists :

    1. Let trustedScoringSignalsURL be the result of running the URL parser on config [" trustedScoringSignalsURL "].

    2. Throw a TypeError if trustedScoringSignalsURL is an error, or it is not same origin with auctionConfig ’s seller .

    3. Assert : trustedScoringSignalsURL ’s scheme is " https ".

    4. Set auctionConfig ’s trusted scoring signals url to trustedScoringSignalsURL .

  6. If config [" interestGroupBuyers "] exists , let buyers be a new empty list .

    1. For each buyerString in config [" interestGroupBuyers "]:

      1. Let buyer be the result of parsing an origin with buyerString . If buyer is an error, or buyer ’s scheme is not " https ", then throw a TypeError . Otherwise, append buyer to buyers .

    2. Set auctionConfig ’s interest group buyers to buyers .

  7. If config [" auctionSignals "] exists , let auctionConfig ’s auction signals be the result of serializing a JavaScript value to a JSON string , given config [" auctionSignals "].

  8. If config [" sellerSignals "] exists , let auctionConfig ’s seller signals of be the result of serializing a JavaScript value to a JSON string , given config [" sellerSignals "].

  9. If config [" directFromSellerSignals "] exists , let directFromSellerSignalsPrefix be the result of running the URL parser on config [" directFromSellerSignals "].

    1. Throw a TypeError if any of the following conditions hold:

      • directFromSellerSignalsPrefix is an error;

      • directFromSellerSignalsPrefix is not same origin with auctionConfig ’s seller ;

      • directFromSellerSignalsPrefix ’s query is not null.

    2. Assert : directFromSellerSignalsPrefix ’s scheme is " https ".

  10. If config [" sellerTimeout "] exists , set auctionConfig ’s seller timeout to min( config [" sellerTimeout "], 500) milliseconds.

  11. If config [" sellerExperimentGroupId "] exists :

    1. Set auctionConfig ’s seller experiment group id to config [" sellerExperimentGroupId "].

  12. If config [" perBuyerSignals "] exists , for each key value of config [" perBuyerSignals "]:

    1. Let buyer be the result of parsing an origin with key . If buyer is an error, throw a TypeError .

    2. Let signalsString be the result of serializing a JavaScript value to a JSON string , given value .

    3. Set auctionConfig ’s per buyer signals [ buyer ] to signalsString .

  13. If config [" perBuyerTimeouts "] exists , for each key value of config [" perBuyerTimeouts "]:

    1. If key equals to "*", then set auctionConfig ’s all buyers timeout to min( value , 500) milliseconds, and continue .

    2. Let buyer the result of parsing an origin with key . If buyer is an error, throw a TypeError .

    3. Set auctionConfig ’s per buyer timeouts [ buyer ] to min( value , 500) milliseconds.

  14. If config [" perBuyerGroupLimits "] exists , for each key value of config [" perBuyerGroupLimits "]:

    1. If value is 0, throw a TypeError .

    2. If key equals to "*", then set auctionConfig ’s all buyers group limit to value , and continue .

    3. Let buyer be the result of parsing an origin with key . If buyer is an error, throw a TypeError .

    4. Set auctionConfig ’s per buyer group limits [ buyer ] to value .

  15. If config [" perBuyerExperimentGroupIds "] exists , for each key value of config [" perBuyerExperimentGroupIds "]:

    1. If key equals to "*", then set auctionConfig ’s all buyer experiment group id to value , and continue .

    2. Let buyer the result of parsing an origin with key . If buyer is an error, throw a TypeError .

    3. Set auctionConfig ’s per buyer experiment group ids [ buyer ] to value .

  16. If config [" perBuyerPrioritySignals "] exists , for each key value of config [" perBuyerPrioritySignals "]:

    1. Let signals be an ordered map whose keys are strings and whose values are double .

    2. for each k v of value :

      1. If k starts with "browserSignals.", throw a TypeError .

      2. Set signals [ k ] to v .

    3. If key equals to "*", then set auctionConfig ’s all buyers priority signals to value , and continue .

    4. Let buyer be the result of parsing an origin with key . If it fails, throw a TypeError .

    5. Set auctionConfig ’s per buyer priority signals [ buyer ] to signals .

  17. For each component in config [" componentAuctions "]:

    1. If isTopLevel is false, throw a TypeError .

    2. Let componentAuction be the result of running validate and convert auction ad config with component and isTopLevel set to false.

    3. Append componentAuction to auctionConfig ’s component auctions .

  18. Return auctionConfig .

To parse an origin given a string input :

  1. Let url be the result of running the URL parser on input .

  2. If url is an error, then return failure.

  3. Return url ’s origin .

To build bid generators map given an auction config auctionConfig :

  1. Let bidGenerators be a new ordered map whose keys are origins and whose values are per buyer bid generators .

  2. For each buyer in auctionConfig ’s interest group buyers :

    1. For each ig of the user agent’s interest group set whose owner is buyer :

      1. Let signalsUrl be ig ’s trusted bidding signals url .

      2. Let joiningOrigin be ig ’s joining origin .

      3. If bidGenerators does not contain buyer :

        1. Let perBuyerGenerator be a new per buyer bid generator .

        2. Let perSignalsUrlGenerator be a new per signals url bid generator .

        3. Set perSignalsUrlGenerator [ joiningOrigin ] to « ig ».

        4. Set perBuyerGenerator [ signalsUrl ] to perSignalsUrlGenerator .

        5. Set bidGenerators [ buyer ] to perBuyerGenerator .

        6. TODO: add a perBiddingScriptUrlGenerator layer that replaces the list of IGs with a map from biddingScriptUrl to a list of IGs.

      4. Otherwise:

        1. Let perBuyerGenerator be bidGenerators [ buyer ].

        2. If perBuyerGenerator does not contain signalsUrl :

          1. Let perSignalsUrlGenerator be a new per signals url bid generator .

          2. Set perSignalsUrlGenerator [ joiningOrigin ] to « ig ».

          3. Set perBuyerGenerator [ signalsUrl ] to perSignalsUrlGenerator .

        3. Otherwise:

          1. Let perSignalsUrlGenerator be perBuyerGenerator [ signalsUrl ].

          2. If perSignalsUrlGenerator does not contain joiningOrigin :

            1. Set perSignalsUrlGenerator [ joiningOrigin ] to « ig ».

          3. Otherwise:

            1. Append ig to perSignalsUrlGenerator [ joiningOrigin ].

  3. Return bidGenerators .

To generate and score bids given an auction config auctionConfig , an auction config -or-null topLevelAuctionConfig , and a global object global :

  1. Assert that these steps are running in parallel .

  2. Let decisionLogicScript be the result of fetching script with auctionConfig ’s decision logic url .

  3. If decisionLogicScript is failure, return null.

  4. Let bidGenerators be the result of running build bid generators map with auctionConfig .

  5. Let leadingBidInfo be a new leading bid info .

  6. Let queue be the result of starting a new parallel queue .

  7. If auctionConfig ’s component auctions are not empty :

    1. Assert topLevelAuctionConfig is null.

    2. Let pendingComponentAuctions be the size of auctionConfig ’s component auctions .

    3. For each component in auctionConfig ’s component auctions , enqueue the following steps to queue :

      1. Let compWinner be the result of generate and score bids with component , auctionConfig , and global .

      2. If compWinner is not null:

        1. Run score and rank a bid with auctionConfig , compWinner , leadingBidInfo , decisionLogicScript , null, and true.

      3. Decrement pendingComponentAuctions by 1.

    4. Wait until pendingComponentAuctions is 0.

    5. If leadingBidInfo ’s leading bid is null, return null.

    6. Let winningComponentConfig be leadingBidInfo ’s auction config .

    7. Set leadingBidInfo ’s auction config to auctionConfig .

    8. Set leadingBidInfo ’s component seller to winningComponentConfig ’s seller .

    9. Let « topLevelSellerSignals , unusedTopLevelReportResultBrowserSignals » be the result of running report result with leadingBidInfo .

    10. Set leadingBidInfo ’s auction config to winningComponentConfig .

    11. Set leadingBidInfo ’s component seller to null.

    12. Set leadingBidInfo ’s top level seller to auctionConfig ’s seller .

    13. Set leadingBidInfo ’s top level seller signals to topLevelSellerSignals .

    14. Let « sellerSignals , reportResultBrowserSignals » be the result of running report result with leadingBidInfo .

    15. Run report win with leadingBidInfo , sellerSignals , and reportResultBrowserSignals .

    16. Return leadingBidInfo ’s leading bid .

  8. Let allBuyersExperimentGroupId be auctionConfig ’s all buyer experiment group id .

  9. Let auctionSignals be auctionConfig ’s auction signals .

  10. Let browserSignals be an ordered map whose keys are strings and whose values are any .

  11. Set browserSignals [" topWindowHostname "] to this 's relevant settings object 's top-level origin 's host .

  12. Set browserSignals ["seller"] to auctionConfig ’s seller .

  13. If topLevelAuctionConfig is not null:

    1. Set browserSignals ["topLevelSeller"] to the serialization of topLevelAuctionConfig ’s seller .

  14. Let pendingBuyers be the size of bidGenerators .

  15. For each buyer perBuyerGenerator of bidGenerators , enqueue the following steps to queue :

    1. Let buyerExperimentGroupId be allBuyersExperimentGroupId .

    2. Let perBuyerExperimentGroupIds be auctionConfig ’s per buyer experiment group ids .

    3. If perBuyerExperimentGroupIds is not null and perBuyerExperimentGroupIds [ buyer ] exists:

      1. Set buyerExperimentGroupId to perBuyerExperimentGroupIds [ buyer ].

    4. Let perBuyerSignals be null.

    5. If auctionConfig ’s per buyer signals is not null and per buyer signals [ buyer ] exists :

      1. Set perBuyerSignals to auctionConfig ’s per buyer signals [ buyer ].

    6. For each signalsUrl -> perSignalsUrlGenerator of perBuyerGenerator :

      1. Let keys be a new ordered set .

      2. Let igNames be a new ordered set .

      3. For each joiningOrigin -> groups of perSignalsUrlGenerator :

        1. For each ig of groups :

          1. Append ig ’s trusted bidding signals keys to keys .

          2. Append ig ’s name to igNames .

      4. Let biddingSignalsUrl be the result of building trusted bidding signals url with signalsUrl , keys , igNames , buyerExperimentGroupId .

      5. Let « allTrustedBiddingSignals , dataVersion » be the result of fetching trusted signals with biddingSignalsUrl and true.

      6. For each joiningOrigin -> groups of perSignalsUrlGenerator :

        1. For each ig of groups :

          1. TODO: Let interestGroup be ... from ig ... minus priority and prioritySignalsOverrides and any browser-defined pieces

          2. Set browserSignals [" joinCount "] to the sum of ig ’s join counts for all days within the last 30 days.

          3. Set browserSignals [" bidCount "] to the sum of ig ’s bid counts for all days within the last 30 days.

          4. Set browserSignals [" prevWins "] to a list containing tuple s of the time and the corresponding winning interest group ad from ig ’s previous wins field with a data within the last 30 days. The time field is specified in seconds relative to the start of the auction.

          5. TODO: Set browserSignals ’s "wasmHelper" ...need to fetch and prepare WebAssembly.Module object based on ig ’s bidding wasm helper url

          6. If dataVersion is not null:

            1. Set browserSignals [" dataVersion "] to dataVersion .

          7. Let biddingScript be the result of fetching script with ig ’s bidding url . If biddingScript is failure, continue .

          8. Let trustedBiddingSignals be an ordered map whose keys are strings and whose values are any .

          9. For each key of ig ’s trusted bidding signals keys :

            1. If allTrustedBiddingSignals [ key ] exists , then set trustedBiddingSignals [ key ] to allTrustedBiddingSignals [ key ].

          10. Let generatedBidResult be the result of evaluating a bidding script with biddingScript and « interestGroup , auctionSignals , perBuyerSignals , trustedBiddingSignals , browserSignals ».

          11. If generatedBidResult is an abrupt completion , continue .

          12. Let generatedBidIDL be the result of converting generatedBidResult to a GenerateBidOutput .

          13. If an exception was caught in the previous step, continue .

          14. Let generatedBid be the result of converting GenerateBidOutput to generated bid with generatedBidIDL .

          15. If generatedBid is failure, continue .

          16. Set generatedBid ’s interest group to ig .

          17. Let isComponentAuction be a boolean set to true when topLevelAuctionConfig is not null, otherwise false.

          18. Score and rank a bid with auctionConfig , generatedBid , leadingBidInfo , decisionLogicScript , dataVersion and isComponentAuction .

    7. Decrement pendingBuyers by 1.

  16. Wait until pendingBuyers is 0.

  17. If leadingBidInfo ’s leading bid is null, return null.

  18. If topLevelAuctionConfig is null:

    1. Let « sellerSignals , reportResultBrowserSignals » be the result of running report result with leadingBidInfo .

    2. Run report win with leadingBidInfo , sellerSignals , and reportResultBrowserSignals .

  19. Return leadingBidInfo ’s leading bid .

To score and rank a bid given an auction config auctionConfig , a generated bid generatedBid , a leading bid info leadingBidInfo , a string decisionLogicScript , a unsigned long -or-null biddingDataVersion , and a boolean isComponentAuction :

  1. TODO: Assemble and fetch trusted scoring signals

  2. TODO: Let adMetadata be ... maybe include in generated bid

  3. Let bidValue be generatedBid ’s bid .

  4. If generatedBid ’s modified bid is not null:

    1. Set bidValue to generatedBid ’s modified bid .

  5. TODO: Let trustedScoringSignals ... need to assemble and fetch these

  6. Let browserSignals be an ordered map whose keys are strings and whose values are any .

  7. Set browserSignals [" topWindowHostname "] to this 's relevant settings object 's top-level origin 's host .

  8. Set browserSignals ["interestGroupOwner"] to generatedBid ’s interest group 's owner .

  9. Set browserSignals ["renderURL"] to generatedBid ’s ad descriptor 's url .

  10. Set browserSignals ["adComponents"] to generatedBid ’s ad component descriptors .

  11. Set browserSignals ["biddingDurationMsec"] to generatedBid ’s bid duration .

  12. TODO: Set browserSignals ’s "dataVersion" ...to Data-Version value from the trusted scoring signals server’s response.

  13. TODO: Remove fields of auctionConfig that don’t pass through.

  14. Let scoreAdOutput be the result of evaluating a scoring script with decisionLogicScript and « adMetadata , bidValue , auctionConfig , trustedScoringSignals , browserSignals ».

  15. If isComponentAuction is true, and scoreAdOutput ’s allow component auction is false, return.

  16. Let score be scoreAdOutput ’s desirability .

  17. If score is negative or 0, return.

  18. If isComponentAuction is true and scoreAdOutput ’s bid is not null:

    1. Set generatedBid ’s modified bid to scoreAdOutput ’s bid .

  19. Let updateLeadingBid be false.

  20. If leadingBidInfo ’s leading bid is null:

    1. Set updateLeadingBid to true.

  21. Otherwise if score is greater than leadingBidInfo ’s top score :

    1. Set leadingBidInfo ’s top bids count to 1.

    2. Set updateLeadingBid to true.

  22. Otherwise if score equals leadingBidInfo ’s top score :

    1. Increment leadingBidInfo ’s top bids count by 1.

    2. Set updateLeadingBid to true with 1 in leadingBidInfo ’s top bids count chance.

    3. If updateLeadingBid is false:

      1. Update highest scoring other bid with score , leadingBidInfo ’s leading bid , and leadingBidInfo .

  23. Otherwise if score is greater than or equal to leadingBidInfo ’s second highest score :

    1. Update highest scoring other bid with score , bidValue , and leadingBidInfo .

  24. If updateLeadingBid is true:

    1. Update highest scoring other bid with leadingBidInfo ’s top score , leadingBidInfo ’s leading bid , and leadingBidInfo .

    2. Set leadingBidInfo ’s top score to score .

    3. Set leadingBidInfo ’s leading bid to generatedBid .

    4. Set leadingBidInfo ’s auction config to auctionConfig .

    5. Set leadingBidInfo ’s bidding data version to biddingDataVersion .

    6. TODO: Set leadingBidInfo ’s scoring data version .

To update highest scoring other bid given a double score , a generated bid -or-null bid , and a leading bid info leadingBidInfo :
  1. If bid is null, return.

  2. If score is greater than leadingBidInfo ’s second highest score :

    1. Set leadingBidInfo ’s highest scoring other bid to bid .

    2. Set leadingBidInfo ’s highest scoring other bids count to 1.

    3. Set leadingBidInfo ’s second highest score to score .

  3. Otherwise if score is equal to leadingBidInfo ’s second highest score :

    1. Increment leadingBidInfo ’s highest scoring other bids count by 1.

    2. Set leadingBidInfo ’s highest scoring other bid to bid with 1 in leadingBidInfo ’s highest scoring other bids count chance.

To create a request given a URL url , a string accept , and an origin or null origin :
  1. Let request be a new request with the following properties:

    URL

    url

    header list

    A new header list containing a header named " Accept " whose value is accept

    client

    null

    window

    " no-window " TODO: verify

    service-workers mode

    " none "

    origin

    If origin is null, then opaque origin , otherwise origin .

    referrer

    " no-referrer "

    credentials mode

    " omit "

    cache mode

    " no-store "

    redirect mode

    " error "

  2. Return request .

To validate fetching response given a response response and responseBody , and a string mimeType :
  1. If responseBody is null or failure, return false.

  2. Let headers be response ’s header list .

  3. If getting a structured field value "X-Allow-Protected-Audience" from headers does not return true, return false.

  4. Let headerMimeType be the result of extracting a MIME type from headers .

  5. Return false if any of the following conditions hold:

  6. Let mimeTypeCharset be headerMimeType ’s parameters [" charset "].

  7. Return false if any of the following conditions hold:

    • If mimeTypeCharset does not exist , or mimeTypeCharset is "utf-8", and responseBody is not UTF-8 encoded;

    • If mimeTypeCharset is "us-ascii", and responseBody is not ascii string .

  8. Return true.

To fetch script given a URL url :
  1. Let request be the result of creating a request with url , " text/javascript ", and null.

  2. Let script be null.

  3. Fetch request with processResponseConsumeBody set to the following steps given a response response and responseBody :

    1. If validate fetching response with response , responseBody and " text/javascript " returns false, set script to failure and return.

    2. Set script to responseBody .

  4. Wait for script to be set.

  5. Return script .

The Data-Version HTTP response header is a structured header whose value must be an integer . The X-protected-audience-bidding-signals-format-version HTTP response header is a structured header whose value must be an integer .

To fetch trusted signals given a URL url , and a boolean isBiddingSignal :
  1. Let request be the result of creating a request with url , " application/json ", and null.

  2. Let signals be null.

  3. Let dataVersion be null.

  4. Let formatVersion be null.

  5. Fetch request with processResponseConsumeBody set to the following steps given a response response and responseBody :

    1. If validate fetching response with response , responseBody and " application/json " returns false, set signals to failure and return.

    2. Let headers be response ’s header list .

    3. Set dataVersion to the result of getting a structured field value given ` Data-Version ` and " item " from headers .

    4. If dataVersion is not null:

      1. If dataVersion is not an integer, or is less than 0 or more than 4294967295, set signals to failure and return.

      2. TODO: Check whether version is consistent for all keys requested by this interest group.

    5. If isBiddingSignal is true:

      1. Set formatVersion to the result of getting a structured field value given ` X-protected-audience-bidding-signals-format-version ` and " item " from headers .

    6. Set signals to the result of parsing JSON bytes to an Infra value responseBody .

  6. Wait for signals to be set.

  7. If signals is a parsing exception, or if signals is not an ordered map , return « null, null ».

  8. If formatVersion is not null, is an integer, and is 2:

    1. If signals [" keys "] exists , then set signals to signals [" keys "].

    2. Otherwise, return « null, null ».

    3. If signals is not an ordered map , return « null, null ».

  9. Return « signals , dataVersion ».

To build trusted bidding signals url given a URL signalsUrl , an ordered set of strings keys , an ordered set of strings igNames , and a unsigned short experimentGroupId :

  1. Let queryParamsList be a new empty list .

  2. Append "hostname=" to queryParamsList .

  3. Append the result of UTF-8 percent-encoding this 's relevant settings object 's top-level origin using component percent-encode set to queryParamsList .

  4. If keys is not empty :

    1. Append "&keys=" to queryParamsList .

    2. Let keysStr be the result of concatenating keys with separator set to ",".

    3. Append the result of UTF-8 percent-encoding keysStr using component percent-encode set to queryParamsList .

  5. If igNames is not empty :

    1. Append "&interestGroupNames=" to queryParamsList .

    2. Let igNamesStr be the result of concatenating igNames with separator set to ",".

    3. Append the result of UTF-8 percent-encoding igNamesStr using component percent-encode set to queryParamsList .

  6. If experimentGroupId is not null:

    1. Append "&experimentGroupId=" to queryParamsList .

    2. Append serialized experimentGroupId to queryParamsList .

  7. Set signalsUrl ’s query to the result of concatenating queryParamsList .

  8. return signalsUrl .

To serialize an integer , represent it as a string of the shortest possible decimal number.

This would ideally be replaced by a more descriptive algorithm in Infra. See infra/201

To report result given a leading bid info leadingBidInfo :
  1. Let browserSignals be an ordered map whose keys are strings and whose values are any .

  2. Set browserSignals [" topWindowHostname "] to this 's relevant settings object 's top-level origin 's host .

  3. Let winner be leadingBidInfo ’s leading bid .

  4. Set browserSignals [" interestGroupOwner "] to winner ’s interest group 's owner .

  5. Set browserSignals [" renderUrl "] to winner ’s ad descriptor 's url .

  6. Set browserSignals [" bid "] to winner ’s bid .

  7. Set browserSignals [" desirability "] to leadingBidInfo ’s top score .

  8. Set browserSignals [" highestScoringOtherBid "] to leadingBidInfo ’s highest scoring other bid 's bid .

  9. TODO: if trusted scoring signals response data version is not null, set browserSignals [" dataVersion "].

  10. If leadingBidInfo ’s top level seller is not null, set browserSignals [" topLevelSeller "] to it.

  11. If leadingBidInfo ’s top level seller signals is not null, set browserSignals [" topLevelSellerSignals "] to it.

  12. If leadingBidInfo ’s component seller is not null:

    1. Set browserSignals [" componentSeller "] to leadingBidInfo ’s component seller .

    2. If winner ’s modified bid is not null, set browserSignals [" bid "] to it.

  13. Otherwise, if winner ’s modified bid is not null, set browserSignals [" modifiedBid "] to it.

  14. Set config to leadingBidInfo ’s auction config .

  15. Let sellerReportingScript be the result of fetching script with config ’s decision logic url .

  16. Let sellerSignals be the result of evaluating a reporting script with sellerReportingScript , " reportResult ", and « config ’s auction signals , browserSignals ».

  17. TODO: Set URLs passed to sendReportTo() into the FencedFrameConfig so they can be fetched when ad renders.

  18. Remove browserSignals [" dataVersion "].

  19. Remove browserSignals [" modifiedBid "].

  20. Remove browserSignals [" topLevelSellerSignals "].

  21. Return « sellerSignals , browserSignals ».

To report win given a leading bid info leadingBidInfo , a string sellerSignals and an ordered map browserSignals whose keys are strings and whose values are any :
  1. Let config be leadingBidInfo ’s auction config .

  2. Let winner be leadingBidInfo ’s leading bid .

  3. Let perBuyerSignals be config ’s per buyer signals .

  4. Let buyer be winner ’s interest group 's owner .

  5. Let perBuyerSignalsForBuyer be perBuyerSignals [ buyer ] if that member exists , and null otherwise.

  6. Remove browserSignals [" desirability "].

  7. TODO: Set browserSignals [" interestGroupName "] to winner ’s name if the tuple of interest group owner, name, bidding script URL and ad creative URL were jointly k-anonymous.

  8. TODO: Set browserSignals [" madeHighestScoringOtherBid "] are based on the auction the interest group was directly part of. If that was a component auction, they’re from the component auction. If that was the top-level auction, then they’re from the top-level auction. Component bidders do not get these signals from top-level auctions since it is the auction seller joining the top-level auction, instead of winning component bidders joining the top-level auction directly.

  9. If leadingBidInfo ’s bidding data version is not null:

    1. Set browserSignals ["dataVersion"] to leadingBidInfo ’s bidding data version .

  10. Set browserSignals ["adCost"] to winner ’s ad cost .

  11. Set browserSignals ["seller"] to config ’s seller .

  12. If leadingBidInfo ’s top level seller is not null:

    1. Set browserSignals [" topLevelSeller "] to leadingBidInfo ’s top level seller .

  13. If winner ’s modeling signals is not null:

    1. Set browserSignals [" modelingSignals "] to winner ’s modeling signals .

  14. Let buyerReportingScript be the result of fetching script with winner ’s interest group 's bidding url .

  15. Run evaluating a reporting script with buyerReportingScript , " reportWin ", and « leadingBidInfo ’s auction config 's auction signals , perBuyerSignalsForBuyer , sellerSignals , browserSignals ».

  16. TODO: Set URLs passed to sendReportTo() into the FencedFrameConfig so they can be fetched when ad renders.

5. Script Runners

This introduction sub-section is non-normative .

This specification defines a new type of script execution environment called a script runner . On the surface, these are similar to Worklets in that they too are used for running scripts independent of the main execution environment with a flexible implementation model.

However, some key differences from traditional Worklets motivate us to create a new kind of script execution environment. In particular, they:

5.1. Script evaluation

Concretely, a script runner is a JavaScript execution environment instantiated with one of the following global objects:

Each InterestGroupBiddingScriptRunnerGlobalScope has a bid , which is a generated bid , a priority , which is a double or null, and a priority signals , which is an ordered map whose keys are strings and whose values are double .

To evaluate a bidding script given a string script and a list of arguments arguments :
  1. Return the result of evaluating a script with InterestGroupBiddingScriptRunnerGlobalScope , script , " generateBid ", and arguments .

To evaluate a scoring script given a string script and a list of arguments arguments :
  1. Return the result of evaluating a script with InterestGroupScoringScriptRunnerGlobalScope , script , " scoreAd ", and arguments .

To evaluate a reporting script given a string script , a string functionName , and a list of arguments arguments :
  1. Return the result of evaluating a script with InterestGroupReportingScriptRunnerGlobalScope , script , functionName , and arguments .


To create a new script runner agent , run these steps:
  1. Let signifier be a new unique internal value.

  2. Let candidateExecution be a new candidate execution .

  3. Return a new agent whose [[CanBlock]] is false, [[Signifier]] is signifier , [[CandidateExecution]] is candidateExecution , and [[IsLockFree1]], [[IsLockFree2]], and [[LittleEndian]] are set at the implementation’s discretion.

Note: This algorithm is almost identical to [HTML] 's create an agent algorithm, with the exception that we do not give the returned agent a new event loop , since it does not process tasks within task sources in the usual way.

To obtain a script runner agent , run these steps:
  1. Let agentCluster be a new agent cluster .

  2. Let agent be the result of creating a new script runner agent .

  3. Add agent to agentCluster .

  4. Return agent .

To evaluate a script with a global type globalType , string script , string functionName , and a list arguments , run these steps. They return a Completion Record , which is either an abrupt completion (in the case of a parse failure or execution error), or a normal completion populated with the ECMAScript language value result of invoking functionName .
  1. Assert that these steps are running in parallel .

  2. Let agent be the result of obtaining a script runner agent given null, true, and false. Run the rest of these steps in agent .

    This exclusively creates a new agent cluster for the given script to run in, but we should make this work with execution mode somehow.

  3. Let realmExecutionContext be the result of creating a new realm given agent and the following customizations:

    • For the global object, create a new object of type globalType .

  4. Let realm be realmExecutionContext ’s Realm component.

  5. Let global be realm ’s global object , and run these steps:

    1. Perform ! global .[[Delete]](" Date ").

    2. If ! global .[[HasProperty]](" Temporal ") is true, then perform ! global .[[Delete]](" Temporal ").

    This is not the best way to perform such API neutering (see tc39/ecma262#1357 ), but at the moment it’s the way that host environments do this.

    Note: Removing time-referencing APIs from the global object is imperative for privacy, as script might otherwise be able to more easily exfiltrate data by using more accurate time measurements.

  6. Let result be ParseScript ( script , realm , empty ).

    Note: The resulting Script Record will have no [[HostDefined]] component, unlike traditional scripts on the web platform.

  7. If result is a list of errors, return Completion { [[Type]]: throw , [[Value]]: result , [[Target]]: empty }.

  8. Assert : result is a Script Record .

  9. Prepare to run script : Push realmExecutionContext onto the JavaScript execution context stack ; it is now the running JavaScript execution context .

  10. Let evaluationStatus be the result of ScriptEvaluation (result).

  11. If evaluationStatus is an abrupt completion , jump to the step labeled return .

  12. Let F be Get ( global , functionName ). If that returns a throw completion , set finalCompletion to F and jump to the step labeled return .

  13. Set finalCompletion be Completion ( Call (F, undefined , arguments )).

  14. Return : at this point finalCompletion will be set to a Completion Record .

    1. Clean up after script : Assert realmExecutionContext is the running JavaScript execution context , and remove it from the JavaScript execution context stack .

    2. Return finalCompletion .

5.2. Global scopes

An additional requirement to the interest group script runner globals defined in this specification is that they must not expose any interfaces from other specifications whose own exposure set is the special value "*". The only interfaces that can be exposed inside of the globals defined in this specification are those that explicitly list the global names provided here.

[Exposed=InterestGroupScriptRunnerGlobalScope]
interface InterestGroupScriptRunnerGlobalScope {
};
[Exposed=InterestGroupBiddingScriptRunnerGlobalScope,
 Global=(InterestGroupScriptRunnerGlobalScope,
         InterestGroupBiddingScriptRunnerGlobalScope)]
interface InterestGroupBiddingScriptRunnerGlobalScope
        : InterestGroupScriptRunnerGlobalScope {
  boolean setBid();
  boolean setBid(GenerateBidOutput generateBidOutput);
  undefined setPriority(double priority);
  undefined setPrioritySignalsOverride(DOMString key, double priority);
};
[Exposed=InterestGroupScoringScriptRunnerGlobalScope,
 Global=(InterestGroupScriptRunnerGlobalScope,
         InterestGroupScoringScriptRunnerGlobalScope)]
interface InterestGroupScoringScriptRunnerGlobalScope
        : InterestGroupScriptRunnerGlobalScope {
};
[Exposed=InterestGroupReportingScriptRunnerGlobalScope,
 Global=(InterestGroupScriptRunnerGlobalScope,
         InterestGroupReportingScriptRunnerGlobalScope)]
interface InterestGroupReportingScriptRunnerGlobalScope
        : InterestGroupScriptRunnerGlobalScope {
  undefined sendReportTo(DOMString url);
};
dictionary AdRender {
  required DOMString url;
  required DOMString width;
  required DOMString height;
};
dictionary GenerateBidOutput {
  required double bid;
  required (DOMString or AdRender) adRender;
  any ad;
  sequence<(DOMString or AdRender)> adComponents;
  double adCost;
  double modelingSignals;
  boolean allowComponentAuction = false;
};
The setBid() method steps are:
  1. Set this 's relevant global object 's bid to null.

  2. Return true.

To convert GenerateBidOutput to generated bid given a GenerateBidOutput generateBidOutput :

  1. If generateBidOutput [" bid "] is less than or equal to 0, return failure.

  2. If it’s a component auction (TODO: how to integrate with runAdAuction), but generateBidOutput [" allowComponentAuction "] is false, return failure.

  3. Let bid be a new generated bid .

  4. Set bid ’s bid to generateBidOutput [" bid "].

  5. If generateBidOutput [" ad "] exists :

    1. Let adJSON be the result of serializing a JavaScript value to a JSON string , given generateBidOutput [" ad "].

    2. If adJSON is failure, return failure.

    3. Set bid ’s ad to adJSON .

  6. Let adDescriptor be a new ad descriptor .

  7. If generateBidOutput [" adRender "] is a DOMString :

    1. Let adUrl be the result of running the URL parser on generateBidOutput [" adRender "].

    2. If adUrl is an error, return failure.

    3. If validating an ad url given adUrl returns false, return failure.

    4. Set adDescriptor ’s url to adUrl .

  8. Otherwise:

    1. Set adDescriptor to the result of converting an ad render given generateBidOutput [" adRender "].

    2. If adDescriptor is failure, return failure.

  9. Set bid ’s ad descriptor to adDescriptor .

  10. If generateBidOutput [" adComponents "] exists :

    1. Let adComponents be generateBidOutput [" adComponents "].

    2. Return failure if any of the following conditions hold:

      • biddingScriptRunner’s adComponents parameter does not exist; TODO: rewrite.

      • adComponents is not an array;

      • adComponents ’s size is greater than 20.

    3. Let adComponentDescriptors be a new list of ad descriptors .

    4. For component in adComponents :

      1. Let componentDescriptor be a new ad descriptor .

      2. If component is DOMString :

        1. Let componentUrl be the result of running the URL parser on component .

        2. If componentUrl is an error, return failure.

        3. If validating an ad url given componentUrl returns false, return failure.

        4. Set componentDescriptor ’s url to componentUrl .

      3. Otherwise:

        1. Set componentDescriptor to the result of converting an ad render given component .

        2. If componentDescriptor is failure, return failure.

      4. Append componentDescriptor to adComponentDescriptors .

    5. Set bid ’s ad component descriptors to adComponentDescriptors .

  11. If generateBidOutput [" adCost "] exists :

    1. Set bid ’s ad cost to generateBidOutput [" adCost "].

  12. If generateBidOutput [" modelingSignals "] exists :

    1. Let modelingSignals be generateBidOutput [" modelingSignals "].

    2. If modelingSignals is greater than or equal to 0 and less than 4096:

      1. Set bid ’s modeling signals to modelingSignals .

  13. TODO: calculate bid duration and set bid ’s bid duration .

  14. Return bid .

To parse an AdRender dimension value given a string input :
  1. Let position be a position variable , initially pointing at the start of input .

  2. Strip leading and trailing ASCII whitespace from input .

  3. If input starts with " 0 " but is not " 0 " and does not start with " 0. ", then return null as the dimension and the empty string as the dimension unit.

  4. Collect a sequence of code points that are ASCII digits or U+002E (.), given position . Let that be dimensionString .

  5. If dimensionString is the empty string, then return null as the dimension and the empty string as the dimension unit.

  6. Let dimension be the result of parsing dimensionString using the rules for parsing floating-point number values .

  7. If dimension is an error, then return null as the dimension and the empty string as the dimension unit.

  8. Collect a sequence of code points that are ASCII lower alpha , given position . Let that be dimensionUnit .

  9. If position is not past the end of input , then return null as the dimension and the empty string as the dimension unit.

  10. If dimensionUnit is the empty string, then set dimensionUnit to "px".

  11. If dimensionUnit is not "px", "sh", or "sw", then return null as the dimension and the empty string as the dimension unit.

  12. Return dimension as the dimension and dimensionUnit as the dimension unit.

To convert an ad render given an AdRender adRender :
  1. If adRender [" url "] does not exist , return false.

  2. Let adUrl be the result of running the URL parser on adRender [" url "].

  3. If adUrl is an error, return failure.

  4. If validating an ad url given adUrl returns false, return failure.

  5. Let adDescriptor be a new ad descriptor .

  6. Set adDescriptor ’s url to adUrl .

  7. If adRender [" width "] exists :

    1. If adRender [" height "] does not exist , return failure.

    2. Let width and widthUnit be the dimension and dimension unit that results from running parse an AdRender dimension value with adRender [" width "], respectively.

    3. If width is null, return failure.

    4. Let height and heightUnit be the dimension and dimension unit that results from running parse an AdRender dimension value with adRender [" height "], respectively.

    5. If height is null, return failure.

    6. Let adSize be a new ad size .

    7. Set adSize ’s width to width , width units to widthUnit , height to height , height units to heightUnit .

    8. Set adDescriptor ’s size to adSize .

  8. Return adDescriptor .

To validate an ad url given a URL adUrl :
  1. If adUrl ’s scheme is not " https ", return false.

  2. TODO: If it’s not in the related interest group’s ads, or is excluded, return false.

  3. Return true.

The setBid( generateBidOutput ) method steps are:
  1. Set this 's relevant global object 's bid to null.

  2. Let bidToSet be the result of converting GenerateBidOutput to generated bid with generateBidOutput .

  3. If bidToSet is failure, return false.

  4. Set this 's relevant global object 's bid to bidToSet .

  5. Return true.

The setPriority( priority ) method steps are:
  1. If this 's relevant global object 's priority is not null:

    1. Set this 's relevant global object 's priority to null.

    2. Return.

  2. Set this 's relevant global object 's priority to priority .

The setPrioritySignalsOverride( key , priority ) method steps are:
  1. Set this 's relevant global object 's priority signals [ key ] to priority .

The sendReportTo( url ) method steps are:
  1. Throw a TypeError and clears report url if any of the following conditions hold:

    • The API is called more than once;

    • url ’s scheme is not " https ".

  2. If called in reportWin(), set reportWinURL to url.

  3. If called in reportResult(), set reportResultURL to url.

6. Interest Group Updates

Interest groups have a update url field that allows updating the interest group definition stored on disk with information periodically retrieved from the update url . The interest group update steps are triggered during runAdAuction() and by calls to updateAdInterestGroups() API:

[SecureContext]
partial interface Navigator {
  undefined updateAdInterestGroups();
};

The updateAdInterestGroups() method steps are:

  1. In parallel , run interest group update with « relevant settings object 's top-level origin »

To update interest groups given a list of origins owners :
  1. For each owner of owners :

    1. For each originalInterestGroup of the user agent’s interest group set whose owner is owner and next update after is before now:

      Note: Implementations can consider loading only a portion of these interest groups at a time to avoid issuing too many requests at once.

      1. Let ig be a deep copy of originalInterestGroup .

      2. Let request be the result of creating a request with ig ’s update url , " application/json ", and owner .

      3. Let update be null.

      4. Fetch request with processResponseConsumeBody set to the following steps given a response response and responseBody :

        1. If validate fetching response with response , responseBody and " application/json " returns false, set update to failure and return.

        2. Set update to responseBody .

      5. Wait for update to be set.

      6. If update is failure, continue .

      7. Let parsedUpdate be the result of parsing JSON bytes to an Infra value , given update .

      8. If parsedUpdate is failure, continue .

      9. If parsedUpdate is not an ordered map , continue .

      10. If parsedUpdate [" name "] exists and doesn’t match ig ’s name , continue .

      11. If parsedUpdate [" owner "] exists and doesn’t match ig ’s owner , continue .

      12. For each key value of parsedUpdate :

        1. Switch on key :

          " priority "
          1. If value is a double , set ig ’s priority to value .

          2. Otherwise, jump to the step labeled Abort update .

          " enableBiddingSignalsPrioritization "
          1. If value is a boolean , set ig ’s enable bidding signals prioritization to value .

          2. Otherwise, jump to the step labeled Abort update .

          " priorityVector "
          1. If value is null or an ordered map whose keys are strings and whose values are double , set ig ’s priority vector to value .

          2. Otherwise, jump to the step labeled Abort update .

          " prioritySignalsOverrides "
          1. If value is an ordered map whose keys are strings and whose values are double or null:

            1. For each pvKey pvValue of value :

              1. If pvValue is null, remove ig ’s priority signals overrides [ pvKey ].

              2. Otherwise, set ig ’s priority signals overrides [ pvKey ] to pvValue .

          2. Otherwise, jump to the step labeled Abort update .

          " executionMode "
          1. If value is " compatibility " or " group-by-origin ", set ig ’s execution mode to value .

          2. Otherwise, jump to the step labeled Abort update .

          " biddingLogicURL "
          " biddingWasmHelperURL "
          " updateURL "
          " trustedBiddingSignalsURL "
          1. For each groupMember and interestGroupField in the following table

            Group member Interest group field
            " biddingLogicURL " bidding url
            " biddingWasmHelperURL " bidding wasm helper url
            " updateURL " update url
            " trustedBiddingSignalsURL " trusted bidding signals url
            1. Let parsedURL be the result of running the URL parser on value .

            2. If key is not groupMember , continue .

            3. Jump to the step labeled Abort update if any of the following conditions hold:

            4. Set ig ’s interestGroupField to parsedURL .

          " trustedBiddingSignalsKeys "
          1. If value is a list of strings , set ig ’s trusted bidding signals keys to value .

          2. Otherwise, jump to the step labeled Abort update .

          " ads "
          " adComponents "
          1. For each groupMember and interestGroupField in the following table

            Group member Interest group field
            " ads " ads
            " adComponents " ad components
            1. If key is not groupMember , continue .

            2. If value is not a list of AuctionAd , jump to the step labeled Abort update .

            3. For each ad of value :

              1. Let igAd be a new interest group ad .

              2. Let renderURL be the result of running the URL parser on ad [" renderURL "].

              3. Jump to the step labeled Abort update if any of the following conditions hold:

              4. Set igAd ’s render url to renderURL .

              5. If ad [" metadata "] exists , then let igAd ’s metadata be the result of serializing a JavaScript value to a JSON string , given ad [" metadata "]. If this throws , jump to the step labeled Abort update .

              6. Append igAd to ig ’s interestGroupField .

      13. Set ig ’s next update after to the current time plus 24 hours.

      14. Replace originalInterestGroup with ig in the browser’s interest group set .

      15. Abort update : We jump here if some part of the interest group update failed. Continue to the next interest group update.

7. Permissions Policy integration

This specification defines two policy-controlled features identified by the strings " join-ad-interest-group ", and " run-ad-auction ". Their default allowlists are " * ".

Issue #522 on GitHub: “Move "join-ad-interest-group" & "run-ad-auction" default allowlists to `self`”

Currently they're * .

8. Structures

8.1. Interest group

An interest group is a struct with the following items:

expiry

A point in time at which the browser will forget about this interest group.

owner

An origin . Frames that join interest groups owned by owner must either be served from origin owner , or another origin delegated by owner (TODO: update to link to delegation section when done). (See checking interest group permissions for details). The scheme must be " https ".

name

A string . The ( owner , name ) tuple is a key that uniquely defines each interest group.

priority

A double . Defaulting to 0.0. Used to select which interest groups participate in an auction when the number of interest groups are limited by perBuyerGroupLimits .

enable bidding signals prioritization

A boolean . Defaulting to false. Being true if the interest group’s priority should be calculated using vectors from bidding signals fetch.

priority vector

Null or an ordered map whose keys are strings and whose values are double . Its dot product with the perBuyerPrioritySignals will be used in place of priority , if set.

priority signals overrides

Null or an ordered map whose keys are strings and whose values are double . Overrides the AuctionAdConfig 's corresponding priority signals.

execution mode

A string , defaulting to " compatibility ". Acceptable values are " compatibility " and " group-by-origin ".

bidding url

Null or a URL . The URL to fetch the buyer’s JavaScript from.

The bidding url origin will always be same origin with owner .

bidding wasm helper url

Null or a URL . Lets the bidder provide computationally-expensive subroutines in WebAssembly, in addition to JavaScript, to be driven from the JavaScript function provided by bidding url .

The bidding wasm helper url origin will always be same origin with owner .

update url

Null or a URL . Provides a mechanism for the group’s owner to periodically update the attributes of the interest group. See interest group updates .

The update url origin will always be same origin with owner .

trusted bidding signals url

Null or a URL . Provide a mechanism for making real-time data available for use at bidding time. See building trusted bidding signals url .

Note: The trusted bidding signals url origin will always be same origin with owner .

trusted bidding signals keys

Null or a list of string . See building trusted bidding signals url .

user bidding signals

Null or a string . Additional metadata that the owner can use during on-device bidding.

ads

Null or a list of interest group ad . Contains various ads that the interest group might show.

ad components

Null or a list of interest group ad . Contains various ad components (or "products") that can be used to construct ads composed of multiple pieces — a top-level ad template "container" which includes some slots that can be filled in with specific "products".

joining origin

An origin . The top level page origin from where the interest group was joined.

join counts

A list containing tuple s of the day and per day join count. The day is calculated based on local time. The join count is a count of the number of times joinAdInterestGroup() was called for this interest group on the corresponding day.

bid counts

A list containing tuple s of the day and per day bid count. The day is calculated based on local time. The bid count is a count of the number of times the bid calculated during runAdAuction() was greater than 0.

previous wins

A list containing tuple s of the time and the corresponding interest group ad for each instance that this interest group won an auction.

next update after

A point in time at which the browser will permit updating this interest group.

TODO: Specify the short descriptions of some fields above and below, and add links when runAdAuction() section is ready.

8.2. Interest group ad

An interest group ad is a struct with the following items:

render url

A URL . If this ad wins the auction, this URL (or a urn uuid that maps to this URL) will be returned by runAdAuction() . This URL is intended to be loaded into an ad iframe (or a fenced frame).

metadata

Null or a string . Extra arbitary information about this ad, passed to generateBid() .

8.3. Auction config

An auction config is a struct with the following items:

seller

An origin . The origin of the seller running the ad auction. The scheme must be " https ".

decision logic url

A URL . The URL to fetch the seller’s JavaScript from.

The decision logic url origin will always be same origin with seller .

trusted scoring signals url

Null or a URL . Provide a mechanism for making real-time data (information about a specific creative) available for use at scoring time, e.g. the results of some ad scanning system.

The trusted scoring signals url origin will always be same origin with seller .

interest group buyers

Null or a list of origin . Owners of interest groups allowed to participate in the auction. Each origin’s scheme must be " https ".

auction signals

Null or a string . Opaque JSON data passed to both sellers' and buyers' script runners.

seller signals

Null or a string . Opaque JSON data passed to the seller’s script runner.

seller timeout

A duration in milliseconds. Defaulting to 50 milliseconds. Restricts the runtime of the seller’s scoreAd() script.

per buyer signals

Null or an ordered map whose keys are origins and whose values are strings . Keys are buyers and must be valid HTTPS origins. Values are opaque JSON data passed to corresponding buyer’s script runner.

per buyer timeouts

Null or an ordered map whose keys are origins and whose values are durations in milliseconds. Keys are buyers and must be valid HTTPS origins. Values restrict the runtime of corresponding buyer’s generateBid() script.

all buyers timeout

A duration in milliseconds. Defaulting to 50 milliseconds. Restricts the generateBid() script’s runtime for all buyers without a timeout specified in per buyer timeouts .

per buyer group limits

Null or an ordered map whose keys are origins and whose values are unsigned short s. Keys are buyers and must be valid HTTPS origins. Values restrict the number of bidding interest groups for a particular buyer that can participate in an auction.

all buyers group limit

A unsigned short . Limit on the number of bidding interest groups for all buyers without a limit specified in per buyer group limits .

per buyer priority signals

Null or an ordered map whose keys are origins and whose values are ordered maps , whose keys are strings and whose values are double . Per-buyer sparse vector whose dot product with priority vector is used to calculate interest group priorities. No signal’s key starts with "browserSignals.", which is reserved for values coming from the browser.

all buyers priority signals

Null or an ordered map whose keys are strings and whose values are double . Merged with per buyer priority signals before calculating per-interest group priorities. In the case both have entries with the same key, the entry in per_buyer_priority_signals takes precedence. No signals key start with "browserSignals.", which is reserved for values coming from the browser.

component auctions

A list of auction config s. Nested auctions whose results will also participate in a top level auction. Only the top level auction config can have component auctions.

seller experiment group id

A unsigned short . Optional identifier for an experiment group to support coordinated experiments with the seller’s trusted server.

per buyer experiment group ids

An ordered map whose keys are origins and whose values are unsigned short s. Keys are buyers and must be valid HTTPS origins. Values are identifiers for experiment groups, to support coordinated experiments with buyers' trusted servers.

all buyer experiment group id

A unsigned short . Optional identifier for an experiment group to support coordinated experiments with buyers' trusted servers for buyers without a specified experiment group.

8.4. Per buyer bid generator

A per buyer bid generator is an ordered map whose keys are URLs representing trusted bidding signals urls , and whose values are per signals url bid generators .

8.5. Per signals url bid generator

A per signals url bid generator is an ordered map whose keys are origins representing joining origins , and whose values are lists of interest groups .

8.6. Generated bid

The output of running a Protected Audience generateBid() script, which needs to be scored by the seller.

bid

A double . If the bid is zero or negative, then this interest group will not participate in the auction.

ad

A string . JSON string to be passed to the scoring function.

ad descriptor

An ad descriptor . Render URL and size of the bid’s ad.

ad component descriptors

Null or a list of ad descriptors . Ad components associated with bid, if any. May have at most 20 URLs. Must be null if the interest group making this bid has a null ad components field.

ad cost

Null or a double . Advertiser click or conversion cost passed from generateBid() to reportWin(). Invalid values, such as negative, infinite, and NaN values, will be ignored and not passed. Only the lowest 12 bits will be passed.

modeling signals

Null or a unsigned short . A 0-4095 integer (12-bits) passed to reportWin() , with noising.

interest group

An interest group , whose generateBid() invocation generated this bid.

modified bid

Null or a double . Being null for top level auction. The bid value a component auction’s scoreAd() script returns.

bid duration

A duration in milliseconds. How long it took to run the generateBid() script.

8.7. Ad descriptor

The render URL and size of an ad.

url

A URL , which will be rendered to display the creative if this bid wins the auction.

size

Null or an ad size . Defaulting to null.

8.8. Ad size

Width and height of an ad.

width

A double .

width units

A string . Can only be one of "px" (pixel), "sh" (screen height), and "sw" (screen width).

height

A double .

height units

A string . Can only be one of "px" (pixel), "sh" (screen height), and "sw" (screen width).

8.9. Score ad output

The output of running a Protected Audience scoreAd() script.

desirability

A double . Numeric score of the bid. Must be positive or the ad will be rejected. The winner of the auction is the bid which was given the highest score.

allow component auction

A boolean . If the bid being scored is from a component auction and this value is not true, the bid is ignored. This field must be present and true both when the component seller scores a bid, and when that bid is being scored by the top-level auction.

bid

Null or a double . Is null if the auction has no component auction, or if the auction is a top-level auction. Modified bid value to provide to the top-level seller script. If present, this will be passed to the top-level seller’s scoreAd() and reportResult() methods instead of the original bid, if the ad wins the component auction and top-level auction, respectively.

8.10. Leading bid info

Information of the auction’s leading bid so far when ranking scored bids.

top score

A double . Defaulting to 0.0. The highest score so far.

top bids count

An integer. Defaulting to 0. The number of bids with the same top score .

leading bid

Null or a generated bid . The leading bid of the auction so far.

auction config

An auction config . The auction config of the auction which generated this leading bid .

second highest score

A double . Defaulting to 0.0. The second highest score so far.

highest scoring other bids count

An integer. Defaulting to 0. The number of bids with the same second highest score .

highest scoring other bid

Null or a generated bid . The second highest scoring other bid.

top level seller

Null or a string . The seller in the top level auction. Only set for component auctions, null otherwise.

top level seller signals

Null or a string . Signals from the seller in the top level auction, produced as the output of the top-level seller’s reportResult() method. Only set for component auctions, null otherwise.

component seller

Null or a string . Seller in component auction which generated this leading bid . Only set the top level auction when component auctions are present, null otherwise.

bidding data version

Null or a unsigned long . Data-Version value from the trusted bidding signals server’s response(s). Will only be not null if the Data-Version header was provided and had a consistent value for all of the trusted bidding signals server responses used to construct the trustedBiddingSignals.

scoring data version

Null or a unsigned long . Data-Version value from the trusted scoring signals server’s response. Will only be not null if the Data-Version header was provided in the response headers from the trusted scoring signals server.

9. Privacy Considerations

Protected Audience aims to advance the privacy of remarketing and custom audience advertising on the web, so naturally privacy considerations are paramount to Protected Audience’s design. Partitioning data by site is the central mechanism to prevent joining a user’s identity across sites:

10. Security Considerations

Protected Audience involves the browser running untrusted JavaScript downloaded from multiple parties, so security concerns are top of mind. Fortunately Protected Audience is a highly constrained API not attempting to be a general purpose execution environment. Execution of this JavaScript is controlled and limited as follows:

Protected Audience has the browser pass in several “browserSignals” to the bidding script that give the script unforgeable information about the context that the script is being executed in. This way bidders and sellers have the choice to only participate in auctions where they are comfortable working with the involved parties.

The execution environment available to these scripts is the absolute minimum necessary to calculate

a bid. It supports only ECMAScript. It does not support network, storage, timer, date, DOM, Workers, postMessage, Navigator or Window APIs.

Protected Audience adds Permission-Policies to control access to the Protected Audience APIs to give sites and embedders the ability to clamp down on use of the APIs as they see fit.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[DOM]
Anne van Kesteren. DOM Standard . Living Standard. URL: https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript Language Specification . URL: https://tc39.es/ecma262/multipage/
[ENCODING]
Anne van Kesteren. Encoding Standard . Living Standard. URL: https://encoding.spec.whatwg.org/
[FENCED-FRAME]
Fenced frame URL: https://wicg.github.io/fenced-frame/
[FETCH]
Anne van Kesteren. Fetch Standard . Living Standard. URL: https://fetch.spec.whatwg.org/
[HR-TIME-3]
Yoav Weiss. High Resolution Time . URL: https://w3c.github.io/hr-time/
[HTML]
Anne van Kesteren; et al. HTML Standard . Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard . Living Standard. URL: https://infra.spec.whatwg.org/
[MIMESNIFF]
Gordon P. Hemsley. MIME Sniffing Standard . Living Standard. URL: https://mimesniff.spec.whatwg.org/
[PERMISSIONS-POLICY-1]
Ian Clelland. Permissions Policy . URL: https://w3c.github.io/webappsec-permissions-policy/
[RFC8941]
M. Nottingham; P-H. Kamp. Structured Field Values for HTTP . February 2021. Proposed Standard. URL: https://www.rfc-editor.org/rfc/rfc8941
[URL]
Anne van Kesteren. URL Standard . Living Standard. URL: https://url.spec.whatwg.org/
[WebIDL]
Edgar Chen; Timothy Gu. Web IDL Standard . Living Standard. URL: https://webidl.spec.whatwg.org/

IDL Index

[SecureContext]
partial interface Navigator {
  Promise<undefined> joinAdInterestGroup(AuctionAdInterestGroup group, double durationSeconds);
};
dictionary AuctionAd {
  required USVString renderURL;
  any metadata;
};
dictionary AuctionAdInterestGroup {
  required USVString owner;
  required USVString name;
  double priority = 0.0;
  boolean enableBiddingSignalsPrioritization = false;
  record<DOMString, double> priorityVector;
  record<DOMString, double> prioritySignalsOverrides;
  DOMString executionMode = "compatibility";
  USVString biddingLogicURL;
  USVString biddingWasmHelperURL;
  USVString updateURL;
  USVString trustedBiddingSignalsURL;
  sequence<USVString> trustedBiddingSignalsKeys;
  any userBiddingSignals;
  sequence<AuctionAd> ads;
  sequence<AuctionAd> adComponents;
};
[SecureContext]
partial interface Navigator {
  Promise<undefined> leaveAdInterestGroup(AuctionAdInterestGroupKey group);
};
dictionary AuctionAdInterestGroupKey {
  required USVString owner;
  required USVString name;
};
[SecureContext]
partial interface Navigator {
  Promise<USVString?> runAdAuction(AuctionAdConfig config);
};
dictionary AuctionAdConfig {
  required USVString seller;
  required USVString decisionLogicURL;
  USVString trustedScoringSignalsURL;
  sequence<USVString> interestGroupBuyers;
  any auctionSignals;
  any sellerSignals;
  USVString directFromSellerSignals;
  unsigned long long sellerTimeout;
  unsigned short sellerExperimentGroupId;
  record<USVString, any> perBuyerSignals;
  record<USVString, unsigned long long> perBuyerTimeouts;
  record<USVString, unsigned short> perBuyerGroupLimits;
  record<USVString, unsigned short> perBuyerExperimentGroupIds;
  record<USVString, record<USVString, double>> perBuyerPrioritySignals;
  sequence<AuctionAdConfig> componentAuctions = [];
  AbortSignal? signal;
};
[Exposed=InterestGroupScriptRunnerGlobalScope]
interface InterestGroupScriptRunnerGlobalScope {
};
[Exposed=InterestGroupBiddingScriptRunnerGlobalScope,
 Global=(InterestGroupScriptRunnerGlobalScope,
         InterestGroupBiddingScriptRunnerGlobalScope)]
interface InterestGroupBiddingScriptRunnerGlobalScope
        : InterestGroupScriptRunnerGlobalScope {
  boolean setBid();
  boolean setBid(GenerateBidOutput generateBidOutput);
  undefined setPriority(double priority);
  undefined setPrioritySignalsOverride(DOMString key, double priority);
};
[Exposed=InterestGroupScoringScriptRunnerGlobalScope,
 Global=(InterestGroupScriptRunnerGlobalScope,
         InterestGroupScoringScriptRunnerGlobalScope)]
interface InterestGroupScoringScriptRunnerGlobalScope
        : InterestGroupScriptRunnerGlobalScope {
};
[Exposed=InterestGroupReportingScriptRunnerGlobalScope,
 Global=(InterestGroupScriptRunnerGlobalScope,
         InterestGroupReportingScriptRunnerGlobalScope)]
interface InterestGroupReportingScriptRunnerGlobalScope
        : InterestGroupScriptRunnerGlobalScope {
  undefined sendReportTo(DOMString url);
};
dictionary AdRender {
  required DOMString url;
  required DOMString width;
  required DOMString height;
};
dictionary GenerateBidOutput {
  required double bid;
  required (DOMString or AdRender) adRender;
  any ad;
  sequence<(DOMString or AdRender)> adComponents;
  double adCost;
  double modelingSignals;
  boolean allowComponentAuction = false;
};
[SecureContext]
partial interface Navigator {
  undefined updateAdInterestGroups();
};

Issues Index

This would ideally be replaced by a more descriptive algorithm in Infra. See infra/201
This exclusively creates a new agent cluster for the given script to run in, but we should make this work with execution mode somehow.
Issue #522 on GitHub: “Move "join-ad-interest-group" & "run-ad-auction" default allowlists to `self`”

Currently they're * .