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

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 FLEDGE 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 dailyUpdateUrl;
  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 interestGroup be a new interest group.

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

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

    2. Let ownerUrl be the result of running the URL parser on group["owner"].

      1. If ownerUrl is an error, or its scheme is not "https", throw a TypeError.

      2. Set interestGroup’s owner to ownerUrl’s origin.

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

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

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

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

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

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

    9. For each groupMember and interestGroupField in the following table

      Group member Interest group field
      "biddingLogicUrl" bidding url
      "biddingWasmHelperUrl" bidding wasm helper url
      "dailyUpdateUrl" daily 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:

        3. Set interestGroup’s interestGroupField to parsedUrl.

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

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

    12. 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.

    13. 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:

        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.

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

  5. Let p be a new promise.

  6. Return p and run the following steps in parallel: (TODO: Enqueue the steps to a parallel queue instead.)

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

    2. If the browser is currently storing an interest group with owner and name that matches interestGroup, then remove the currently stored one.

    3. TODO: Set interestGroup’s joining origin to top level page origin from where the interest group was joined.

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

    5. Queue a task to resolve p with undefined.

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 keyvalue 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 keyvalue 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 daily 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.

3. 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.

[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. If this's relevant global object's associated Document is not allowed to use the "run-ad-auction" policy-controlled feature, then throw a "NotAllowedError" DOMException.

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

  3. If config["signal"] exists:

    1. If config["signal"] is aborted, then return a promise rejected with config["signal"]'s abort reason.

  4. Add an algorithm (TODO: define it) to config["signal"].

  5. Let p be a new promise.

  6. Return p, and run the remaining steps in parallel.

  7. Run start bidding and scoring with auctionConfig.

  8. TODO: add missing steps.

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 seller be the result of running the URL parser on config["seller"].

    1. Throw a TypeError if seller is an error, or its scheme is not "https".

    2. Set auctionConfig’s seller to seller.

  3. 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.

  4. 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.

  5. 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.

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

  7. 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"].

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

    1. Throw a TypeError if any of:

      • 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".

    3. TODO: Figure out how to deal with DirectFromSellerSignals.

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

  10. If config["sellerExperimentGroupId"] exists:

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

  11. If config["perBuyerSignals"] exists, for each keyvalue 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.

  12. If config["perBuyerTimeouts"] exists, for each keyvalue 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.

  13. If config["perBuyerGroupLimits"] exists, for each keyvalue 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.

  14. If config["perBuyerExperimentGroupIds"] exists, for each keyvalue 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.

  15. If config["perBuyerPrioritySignals"] exists, for each keyvalue of config["perBuyerPrioritySignals"]:

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

    2. for each kv 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.

  16. 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.

  17. 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 start bidding and scoring given an auction config auctionConfig:

  1. If auctionConfig’s component auctions are empty:

    1. TODO: Create a seller worklet.

  2. Otherwise:

    1. For each component in auctionConfig’s component auctions:

      1. Start bidding and scoring with component.

    2. TODO: If the last component’s seller worklet is received, create a seller worklet.

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

  4. For each buyer in 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].

  5. For each buyerperBuyerGenerator of bidGenerators:

    1. For each signalsUrl -> perSignalsUrlGenerator of perBuyerGenerator:

      1. TODO: Fetch signalsUrl synchronously.

      2. For each joiningOrigin -> groups of perSignalsUrlGenerator:

        1. For each ig of groups:

          1. Let biddingScript be the result of fetch bidding or scoring javascript with ig’s bidding url.

          2. If biddingScript is an error, continue.

          3. TODO: Evaluate script with script set to biddingScript, functionName set to "generateBid", and arguments set to generateBid’s arguemnts.

To create a request given a URL url, and a string accept:

  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

    opaque origin

    referrer

    "no-referrer"

    credentials mode

    "omit"

    cache mode

    "no-store"

    redirect mode

    "error"

  2. Return request.

To fetch bidding or scoring javascript with given URL url:

  1. Let request be the result of creating a request with url, and accept set to "application/javascript".

  2. Fetch request.

  3. Wait for the processResponseConsumeBody algorithm to finish. TODO: use processResponseConsumeBody as argument to fetch.

  4. Let response be the result of fetch when it asynchronously completes.

  5. Let body be response’s body.

  6. If body is null, return failure.

  7. Let headers be response’s header list.

  8. If getting a structured field value "X-Allow-FLEDGE" from headers does not return true, return failure.

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

  10. If mimeType is failure or not a JavaScript MIME type, return failure.

  11. If mimeType’s parameters["charset"] exists, and is not "utf-8" or "us-ascii", return failure.

  12. Return body.

To evaluate script with given string script, string functionName, and a list arguments:

  1. TODO: Return an object or null, which is the result of evaluating script’s functionName with arguments.

4. Permissions Policy integration

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

Note: In the Chromium implementation the default allowlists for both features are temporarily set to * to ease testing.

5. Structures

5.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.

name

A string.

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".

bidding url

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

bidding wasm helper url

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

daily update url

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

trusted bidding signals url

Null or a URL. Provide a mechanism for making real-time data available for use at bidding time.

trusted bidding signals keys

Null or a list of string.

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.

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

5.2. Interest group ad

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

render url

A URL.

metadata

Null or a string.

5.3. Auction config

An auction config is a struct with the following items:

seller

An origin.

decision logic url

A URL.

trusted scoring signals url

Null or a URL.

interest group buyers

Null or a list of origin.

auction signals

Null or a string.

seller signals

Null or a string.

seller timeout

A duration. Defaulting to 50.

per buyer signals

Null or an ordered map whose keys are origins and whose values are strings.

per buyer timeouts

Null or an ordered map whose keys are origins and whose values are durations.

all buyers timeout

A duration. Defaulting to 50.

per buyer group limits

Null or an ordered map whose keys are origins and whose values are non-negative 16-bit integers.

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.

all buyers priority signals

Null or an ordered map whose keys are strings and whose values are double.

all buyers group limit

A non-negative 16-bit integer.

component auctions

A list of auction configs.

seller experiment group id

A non-negative 16-bit integer.

all buyer experiment group id

A non-negative 16-bit integer.

per buyer experiment group ids

An ordered map whose keys are origins and whose values are non-negative 16-bit integers.

5.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.

5.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.

TODO: Add short descriptions for fields of each structure above.

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/
[FETCH]
Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/
[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/
[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 dailyUpdateUrl;
  USVString trustedBiddingSignalsUrl;
  sequence<USVString> trustedBiddingSignalsKeys;
  any userBiddingSignals;
  sequence<AuctionAd> ads;
  sequence<AuctionAd> adComponents;
};

[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;
};