diff --git a/FLEDGE.md b/FLEDGE.md index f741717ca..f6cf97e2d 100644 --- a/FLEDGE.md +++ b/FLEDGE.md @@ -44,7 +44,7 @@ See [the in progress FLEDGE specification](https://wicg.github.io/turtledove/). During 2021, Chrome plans to run an Origin Trial for a first experiment which includes: * Interest Groups, stored by the browser, and associated with arbitrary metadata that can affect how these groups bid and render ads. -* A mechanism for periodic background updating of these interest groups, available as long as the number of people in the interest group exceeds a k-anonymity threshold. +* A mechanism for updating these interest groups. The updates are rate limited, fetched from buyers’ servers, performed off the ad serving critical-path, and contain no contextual information. * On-device bidding by buyers (DSPs or advertisers), based on interest-group metadata and on data loaded from a trusted server at the time of the on-device auction — with a _temporary and untrusted_ "Bring Your Own Server" model, until a trusted-server framework is settled and in place. * On-device ad selection by the seller (an SSP or publisher), based on bids and metadata entered into the auction by the buyers. * Microtargeting protection based on the browser ensuring that the same ad or ad component is being shown to at least some minimum number of people. @@ -138,7 +138,35 @@ The `userBiddingSignals` is for storage of additional metadata that the owner ca The `biddingWasmHelperURL` field is optional, and lets the bidder provide computationally-expensive subroutines in WebAssembly, rather than JavaScript, to be driven from the JavaScript function provided by `biddingLogicURL`. If provided, it must point to a WebAssembly binary, delivered with an `application/wasm` mimetype. The corresponding `WebAssembly.Module` will be made available by the browser to the `generateBid` function. -The `updateURL` provides a mechanism for the group's owner to periodically update the attributes of the interest group: any new values returned in this way overwrite the values previously stored (except that the `name` and `owner` cannot be changed, and `prioritySignalsOverrides` will be merged with the previous value, with `null` meaning a value should be removed from the interest group's old dictionary). However, the browser will only allow updates when a sufficiently large number of people have the same `updateURL` , e.g. at least 100 browsers with the same update URL. This will not include any metadata, so data such as the interest group `name` should be included within the URL, so long as the URL exceeds the minimum count threshold. (Without this sort of limit, a single-person interest group could be used to observe that person's coarse-grained IP-Geo location over time.) +The `updateURL` provides a mechanism for the group's owner to update the attributes +of the interest group: any new values returned in this way overwrite the values +previously stored (except that the `name` and `owner` cannot be changed, and +`prioritySignalsOverrides` will be merged with the previous value, with `null` +meaning a value should be removed from the interest group's old dictionary). This +will not include any metadata, so data such as the interest group `name` should be +included within the URL. The updates are done after auctions so as not to slow down +the auctions themselves. The updates are rate limited to running at most daily to +conserve resources. An update request only contains information from the single site +where the user was added to the interest group. At a later date we can consider +potential side channel mitigations (e.g. +[IP address privacy](https://github.com/GoogleChrome/ip-protection/) or a trusted +update server as mentioned in [#333](https://github.com/WICG/turtledove/issues/333) +to mitigate timing attacks) when the related technologies are more developed, +deployed and adopted. K-anonymity requirements on `updateURL` were originally +considered to improve the privacy of interest group updates, but they were not a +particularly strong privacy protection, mostly because the cost to add a user to an +interest group (and increase the chance of passing the k-anonymity requirement on +updating) is not high. K-anonymity requirements on `updateURL` were also found to +cause a proliferation of interest groups which degraded auction performance +significantly, and degrade the usefulness of interest group updates, as further +discussed in [#333](https://github.com/WICG/turtledove/issues/333) and +[#361](https://github.com/WICG/turtledove/issues/361). Updating interest groups +after the auction does not suffer from these problems, and because each interest +group update only contains information from a single site, the cross-site identity +join risks occur from side channels like IP address and timing correlation. The +k-anonymity protection for the auction winning ad creative URL is still important as +the URL potentially contains information from two sites, the joining and auction +sites. The `executionMode` attribute is optional, and may contain one of the following supported values: @@ -157,9 +185,17 @@ The `executionMode` attribute is optional, and may contain one of the following mode does not have the same limitations on what top-level sites can join or leave the interest group. -The `ads` list contains the various ads that the interest group might show. Each entry is an object that includes both a rendering URL and arbitrary metadata that can be used at bidding time. +The `ads` list contains the various ads that the interest group might show. Each entry is an object that must contain a `renderURL` property with the URL for the ad that would be shown. An ad may also have the following optional properties: -The `adComponents` field contains the various ad components (or "products") that can be used to construct ["Ads Composed of Multiple Pieces"](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#34-ads-composed-of-multiple-pieces)). Similarly to the `ads` field, each entry is an object that includes both a rendering URL and arbitrary metadata that can be used at bidding time. Thanks to `ads` and `adsComponents` being separate fields, the buyer is able to update the `ads` field via the `updateUrl` without losing `adComponents` stored in the interest group. + * `adRenderId`: A short [Bytestring](https://webidl.spec.whatwg.org/#es-ByteString) up to 8 characters long serving as an identifier for this ad in this interest group. When this field is specified it will be sent instead of the full ad object for [B&A server auctions](https://github.com/WICG/turtledove/blob/main/FLEDGE_browser_bidding_and_auction_API.md). + + * `buyerAndSellerReportingId`: If set, the value is used instead of the interest group name or `buyerReportingId` for reporting in `reportWin` and `reportResult`. Note that this field needs to be jointly k-anonymous with the interest group owner, bidding script URL, and render URL to be provided to these reporting fuctions (in the same way that the interest group name would have needed to be). + + * `buyerReportingId`: If set, the value is used instead of the interest group name for reporting in `reportWin`. Note that this field needs to be jointly k-anonymous with the interest group owner, bidding script URL, and render URL to be provided to these reporting fuctions (in the same way that the interest group name would have needed to be). + + * `metadata`: Arbitrary metadata that can be used at bidding time. + +The `adComponents` field contains the various ad components (or "products") that can be used to construct ["Ads Composed of Multiple Pieces"](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#34-ads-composed-of-multiple-pieces)). Similar to the `ads` field, each entry is an object that includes a `renderURL` and optional `adRenderId`, and `metadata` fields. Thanks to `ads` and `adComponents` being separate fields, the buyer is able to update the `ads` field via the `updateURL` without losing `adComponents` stored in the interest group. All fields that accept arbitrary metadata objects (`userBiddingSignals` and `metadata` field of ads) must be JSON-serializable. All fields that specify URLs for loading scripts or JSON (`biddingLogicURL`, @@ -168,7 +204,7 @@ same-origin with `owner` and must point to URLs whose responses include the HTTP response header `X-Allow-FLEDGE: true` to ensure they are allowed to be used for loading FLEDGE resources. -The browser will provide protection against microtargeting, by only rendering an ad if the same rendering URL is being shown to a sufficiently large number of people (e.g. at least 100 people would have seen the ad, if it were allowed to show). While in the [Outcome-Based TURTLEDOVE](https://github.com/WICG/turtledove/blob/master/OUTCOME_BASED.md) proposal this threshold applied only to the rendered creative, FLEDGE has the additional requirement that the tuple of the interest group owner, bidding script URL, and rendered creative must be k-anonymous for an ad to be shown (this is necessary to ensure the current event-level reporting for interest group win reporting is sufficiently private). For interest groups that have component ads, all of the component ads must also separately meet this threshold for the ad to be shown. Since a single interest group can carry multiple possible ads that it might show, the group will have an opportunity to re-bid another one of its ads to act as a "fallback ad" any time its most-preferred choice is below threshold. This means that a small, specialized interest group that is still below the `updateUrl` threshold could still choose to participate in auctions, bidding with a more-generic ad until the group becomes large enough. +The browser will provide protection against microtargeting, by only rendering an ad if the same rendering URL is being shown to a sufficiently large number of people (e.g. at least 100 people would have seen the ad, if it were allowed to show). While in the [Outcome-Based TURTLEDOVE](https://github.com/WICG/turtledove/blob/master/OUTCOME_BASED.md) proposal this threshold applied only to the rendered creative, FLEDGE has the additional requirement that the tuple of the interest group owner, bidding script URL, and rendered creative must be k-anonymous for an ad to be shown (this is necessary to ensure the current event-level reporting for interest group win reporting is sufficiently private). For interest groups that have component ads, all of the component ads must also separately meet this threshold for the ad to be shown. Since a single interest group can carry multiple possible ads that it might show, the group will have an opportunity to re-bid another one of its ads to act as a "fallback ad" any time its most-preferred choice is below threshold. This means that a small, specialized ad that is still below the k-anonymity threshold could still choose to participate in auctions, and its interest group has a way to fall back to a more generic ad until the more specialized one has a large enough audience. #### 1.3 Permission Delegation @@ -359,7 +395,7 @@ The output of `scoreAd()` is an object with the following fields: * allowComponentAuction: (optional) If the bid being scored is from a component auction and this value is not true, the bid is ignored. If not present, this value is considered false. 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. * incomingBidInSellerCurrency: (optional) Provides a conversion of a bid in a multi-currency auction to seller's own currency. Please see [the section on this functionality](#53-reporting-in-multi-currency-auctions) for more details. -If `scoreAd()` returns only a numeric value, it's equivalent to returning {'score': numericValue, `allowComponentAuction`: false}. +If `scoreAd()` returns only a numeric value, it's equivalent to returning {`desirability`: numericValue, `allowComponentAuction`: false}. The logic in `scoreAd()` has access to the full auction configuration object, which means the seller can pass in arbitrary information from the publisher page. In particular, the configuration object's `sellerSignals` field is exclusively for passing information into `scoreAd()`. This field can include information based on looking up publisher settings, based on making a contextual ad request, and so on. Examples of logic that could live in the `scoreAd()` function include: @@ -555,7 +591,7 @@ The output of `generateBid()` contains the following fields: * ad: (optional) Arbitrary metadata about the ad which this interest group wants to show. The seller uses this information in its auction and decision logic. If not present, it's treated as if the value were null. * adCost: (optional) A numerical value used to pass reporting advertiser click or conversion cost from generateBid to reportWin. The precision of this number is limited to an 8-bit mantissa and 8-bit exponent, with any rounding performed stochastically. -* bid: A numerical bid that will enter the auction. The seller must be in a position to compare bids from different buyers, therefore bids must be in some seller-chosen unit (e.g. "USD per thousand"). If the bid is zero or negative, then this interest group will not participate in the seller's auction at all. With this mechanism, the buyer can implement any advertiser rules for where their ads may or may not appear. +* bid: A numerical bid that will enter the auction. The seller must be in a position to compare bids from different buyers, therefore bids must be in some seller-chosen unit (e.g. "USD per thousand"). If the bid is zero or negative, then this interest group will not participate in the seller's auction at all. With this mechanism, the buyer can implement any advertiser rules for where their ads may or may not appear. While this returned value is expected to be a JavaScript Number, internal calculations dealing with currencies should be done with integer math that more accurately represent powers of ten. * bidCurrency: (optional) The currency for the bid, used for [currency-checking](#36-currency-checking). * render: A URL which will be rendered to display the creative if this bid wins the auction. * adComponents: (optional) A list of up to 20 adComponent strings from the InterestGroup's adComponents field. Each value must match an adComponent renderURL exactly. This field must not be present if the InterestGroup has no adComponent field. It is valid for this field not to be present even when adComponents is present. (See ["Ads Composed of Multiple Pieces"](#34-ads-composed-of-multiple-pieces) below.) @@ -723,8 +759,9 @@ The arguments to this function are: The `browserSignals` argument must be handled carefully to avoid tracking. It certainly cannot include anything like the full list of interest groups, which would be too identifiable as a tracking signal. The `renderURL` can be included since it has already passed a k-anonymity check. The browser may limit the precision of the bid and desirability values by stochastically rounding them so that they fit into a floating point number with an 8 bit mantissa and 8 bit exponent to avoid these numbers exfiltrating information from the interest group's `userBiddingSignals`. On the upside, this set of signals can be expanded to include useful additional summary data about the wider range of bids that participated in the auction, e.g. the number of bids. Additionally, the `dataVersion` will only be present if the `Data-Version` header was provided in the response headers from the Trusted Scoring server. -The `reportResult()` function's reporting happens by directly calling network APIs in the short-term, but will eventually go through the Private Aggregation API once it has been developed. The output of this function is not used for reporting, but rather as an input to the buyer's reporting function. +In the short-term, the `reportResult()` function's reporting happens by calling a `sendReportTo()` API which takes a single string argument representing a URL. The `sendReportTo()` function can be called at most once during a worklet function's execution. The URL is fetched when the frame displaying the ad begins navigating to the ad. Eventually reporting will go through the Private Aggregation API once it has been developed. +The output of `reportResult()` is not used for reporting, but rather as an input to the buyer's reporting function. #### 5.2 Buyer Reporting on Render and Ad Events @@ -757,13 +794,13 @@ The arguments to this function are: * `perBuyerSignals`: Like `auctionConfig.perBuyerSignals`, but passed via the [directFromSellerSignals](#25-additional-trusted-signals-directfromsellersignals) mechanism. These are the signals whose subresource URL ends in `?perBuyerSignals=[origin]`. * `auctionSignals`: Like `auctionConfig.auctionSignals`, but passed via the [directFromSellerSignals](#25-additional-trusted-signals-directfromsellersignals) mechanism. These are the signals whose subresource URL ends in `?auctionSignals`. -The `reportWin()` function's reporting happens by directly calling network APIs in the short-term, but will eventually go through the Private Aggregation API once it has been developed. Once the Private Aggregation API has been integrated with FLEDGE the `interestGroup` object passed to `generateBid()` will be available to `reportWin()`. +The `reportWin()` function's reporting happens by calling `sendReportTo()`, same as for `reportResult()`, in the short-term, but will eventually go through the Private Aggregation API once it has been developed. Once the Private Aggregation API has been integrated with FLEDGE the `interestGroup` object passed to `generateBid()` will be available to `reportWin()`. Ads often need to report on events that happen once the ad is rendered. One common example is reporting on whether an ad became viewable on-screen. We will need a communications channel to allow the publisher page or the Fenced Frame to pass such information into the worklet responsible for reporting. Some additional design work is needed here. ##### 5.2.1 Noised and Bucketed Signals -Some privacy-sensitive information (browser signals `joinCount`, `recency`, and the field `modelingSignals` passed from `generateBid()` to `reportWin()`) are made available to `reportWin()` under a special noising and bucketing scheme. +Some privacy-sensitive information (browser signals `joinCount`, `recency`, and the field `modelingSignals` passed from `generateBid()` to `reportWin()`) are made available to `reportWin()` under a special noising and bucketing scheme. All such fields use a noising scheme where, in a randomly-selected 1% of `reportWin()` calls, a uniformly-generated random value in the range of the field's bucketing scheme is returned instead of the true value. @@ -797,5 +834,3 @@ Note that `incomingBidInSellerCurrency` is different from the modified bid retur We also need to provide a mechanism for the _losing_ bidders in the auction to learn aggregate outcomes. Certainly they should be able to count the number of times they bid, and losing ads should also be able to learn (in aggregate) some seller-provided information about e.g. the auction clearing price. Likewise, a reporting mechanism should be available to buyers who attempted to bid with a creative that had not yet reached the k-anonymity threshold. This could be handled by a `reportLoss()` function running in the worklet. Alternatively, the model of [SPURFOWL](https://github.com/AdRoll/privacy/blob/main/SPURFOWL.md) (an append-only datastore and later aggregate log processing) could be a good fit for this use case. The details here are yet to be determined. - - diff --git a/FLEDGE_browser_bidding_and_auction_API.md b/FLEDGE_browser_bidding_and_auction_API.md index 4d2e5d88e..4876438f7 100644 --- a/FLEDGE_browser_bidding_and_auction_API.md +++ b/FLEDGE_browser_bidding_and_auction_API.md @@ -10,9 +10,9 @@ This document seeks to propose an API for web pages to perform FLEDGE auctions u #### Step 1: Get auction blob from browser -To execute an on-server FLEDGE auction, sellers begin by calling `navigator.startServerAdAuction()`: +To execute an on-server FLEDGE auction, sellers begin by calling `navigator.startServerAdAuction()` which returns a `Promise`: ``` -const auctionBlob = navigator.startServerAdAuction({ +const auctionBlob = await navigator.startServerAdAuction({ // ‘seller’ works the same as for runAdAuction. 'seller': 'https://www.example-ssp.com', }); diff --git a/FLEDGE_extended_PA_reporting.md b/FLEDGE_extended_PA_reporting.md index f867da829..7ae0dcffe 100644 --- a/FLEDGE_extended_PA_reporting.md +++ b/FLEDGE_extended_PA_reporting.md @@ -38,7 +38,7 @@ reportResult()/reportWin() methods using a new API available in the worklet: ``` function reportResult(auctionConfig, browserSignals) { … - privateAggregation.sendHistogramReport({ + privateAggregation.contributeToHistogram({ bucket: convertBuyerToBucketId(browserSignals.interestGroupOwner), value: convertBidToReportingValue(browserSignals.bid) }); @@ -80,11 +80,11 @@ The buyer can then do the following during generateBid (when the above informati ``` function generateBid(interestGroup, auctionSignals, perBuyerSignals, trustedBiddingSignals, browserSignals) { … - privateAggregation.reportContributionForEvent(“reserved.win”, { + privateAggregation.contributeToHistogramOnEvent(“reserved.win”, { bucket: getImpressionReportBucket(), value: 1 }); - privateAggregation.reportContributionForEvent("click", { + privateAggregation.contributeToHistogramOnEvent("click", { bucket: getClickReportBuckets(), // 128-bit integer as BigInt value: 1 }); @@ -118,7 +118,7 @@ The following example shows how to return the gap between an ad bid and the winn ``` function generateBid(...) { bid = 100; - privateAggregation.reportContributionForEvent( + privateAggregation.contributeToHistogramOnEvent( "reserved.loss", { bucket: 1596n, // represents a bucket for interest group x winning bid price @@ -154,7 +154,7 @@ example allows the buyer to keep track of how many times their bid was rejected ``` function generateBid(...) { - privateAggregation.reportContributionForEvent( + privateAggregation.contributeToHistogramOnEvent( "reserved.loss", { bucket: { @@ -177,7 +177,7 @@ value: 1 ## Reporting API informal specification ``` -privateAggregation.reportContributionForEvent(eventType, contribution) +privateAggregation.contributeToHistogramOnEvent(eventType, contribution) ``` The parameters consist of: @@ -225,7 +225,7 @@ by calling into a new API: window.fence.reportEvent("click"); ``` -This will cause any contributions associated with a call to `reportContributionForEvent()` +This will cause any contributions associated with a call to `contributeToHistogramOnEvent()` with an event-type of `click` to be reported/sent. In this example, `"click"` is an event-name chosen by the auction bidder. There are a number diff --git a/Fenced_Frames_Ads_Reporting.md b/Fenced_Frames_Ads_Reporting.md index a57de7c5d..a286b2668 100644 --- a/Fenced_Frames_Ads_Reporting.md +++ b/Fenced_Frames_Ads_Reporting.md @@ -28,7 +28,7 @@ The following summarizes the sequence of events for the buyer and seller. Distin 2. SSP will provide the sellerEventId to the auction via auctionConfig, which will be available in the reporting worklet for the SSP. 3. In reportResult(), the sellerEventId, along with any other contextual response fields relevant for reporting, will be available so that the result can be joined to the corresponding contextual query. 4. Once the winning ad is rendered in the fenced frame, the fenced frame can also communicate other events to the browser e.g. what user interaction happened. Since the fenced frames run the buyer’s scripts, the buyer can also decide what information to be volunteered to the seller via the reportEvent API. -5. The solution proposed in this document allows the seller’s reporting worklet to register a url to which data should be sent for events reported by the fenced frame. +5. The solution proposed in this document allows the seller’s reporting worklet to register a URL to which data should be sent for events reported by the fenced frame. ## Buyer (DSP) flow of events @@ -37,7 +37,7 @@ The following summarizes the sequence of events for the buyer and seller. Distin 1. If the DSP participates in the contextual ad request, it will generate an event identifier, say buyerEventId, at contextual ad request/response time which is returned to the SSP as part of the perBuyerSignals, which the SSP in turn would specify in navigator.runAdAuction call. For DSPs that do not participate, buyerEventId could just be an ID that the worklet creates. 2. Browser will provide the buyerEventId to the reporting worklet for the DSP via reportWin() as part of the perBuyerSignals. This enables the result to be joined with the corresponding contextual query. 3. Fenced frame can also communicate other events to the browser e.g. a click happened along with click coordinates. -4. The solution proposed in this document allows the buyer’s reporting worklet to register a url in reportWin(), to which data should be sent, when events happen in the fenced frame. +4. The solution proposed in this document allows the buyer’s reporting worklet to register a URL in reportWin(), to which data should be sent, when events happen in the fenced frame. # APIs @@ -76,7 +76,7 @@ destination).` ### Example - +To send a request with the POST request body `'{"clickX":"123","clickY":"456"}'` to the URL registered for `buyer` and `seller` when a user click happens: ``` window.fence.reportEvent({ 'eventType': 'click', @@ -85,6 +85,8 @@ window.fence.reportEvent({ }); ``` + +To send a request with the POST request body `'an example string'` to the URL registered for `component-seller` when a user click happens: ``` window.fence.reportEvent({ 'eventType': 'click', @@ -93,6 +95,16 @@ window.fence.reportEvent({ }); ``` + +To send a request with the POST request body `''` to the URL registered for `buyer` when a user click happens: +``` +window.fence.reportEvent({ + 'eventType': 'click', + 'destination':['buyer'] +}); +``` + + Note `window.fence` here is a new namespace for APIs that are only available from within a fenced frame. In the interim period when FLEDGE supports rendering the winning ad in an iframe, `window.fence` will also be available in such an iframe. ## registerAdBeacon @@ -161,8 +173,46 @@ Currently, the only `eventType` that `setReportEventDataForAutomaticBeacons` all If invoked multiple times, the latest invocation before the top-level navigation would be the one that’s honored. -`eventData` can be empty, in which case the automatic beacon will still be sent but without an event data body in the HTTP request. +`eventData` is optional, and can be empty. If `eventData` is not specified, or is empty, the automatic beacon will still be sent but without an event data body in the HTTP request. If `setReportEventDataForAutomaticBeacons` is not invoked, the browser will not send an automatic beacon because the `destination` is unknown. +`setReportEventDataForAutomaticBeacons` can also be invoked in the click handler of an anchor tag, and will be sent on navigation: +``` + +Click me! +``` + +The beacon data will be in place by the time that the navigation starts. When the navigation commits, the automatic beacon will be sent out with event data set to "link1 was clicked.". + +# Support for Ad Components +## Goal +When a rendered ad is composed of [multiple pieces](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#34-ads-composed-of-multiple-pieces), it is useful to detect user clicks that happened on ad components. + +## Design +### Event Type and Reporting Destination +For fenced frames rendering the ad components under the top-level ad fenced frame, the `reserved.top_navigation` event type and corresponding reporting destination registered for the top-level fenced frame are reused when beacons are sent from the ad component fenced frames. + +### Restricted to send `reserved.top_navigation` beacons only +* Invocation of the `reportEvent` API from an ad component fenced frame is disallowed. +* The only supported beacon to be sent from an ad component fenced frame is the `reserved.top_navigation` automatic beacon. Note this beacon is gated on a user activation (e.g. click). +* To ensure that there is no arbitrary data that can be received at the server from the component ad, the `eventData` field via `window.fence.setReportEventDataForAutomaticBeacons`, if specified, will be ignored. This ensures that information from the component ad URL is not revealed in the event report, or else it could lead to the join of two independently k-anonymous URLs (parent and component ad) at the receiving server. +* To send the beacon from a component fenced frame, `window.fence.setReportEventDataForAutomaticBeacons` must be invoked within the ad component fenced frame with `eventType` set to `'reserved.top_navigation'`. The beacon will be sent when there is a user activation (e.g. click) on the ad component fenced frame, which results in a top-level navigation. + +``` +window.fence.setReportEventDataForAutomaticBeacons({ + 'eventType': 'reserved.top_navigation', + 'destination':['seller', 'buyer'] +}); +``` diff --git a/Proposed_First_FLEDGE_OT_Details.md b/Proposed_First_FLEDGE_OT_Details.md index d55919041..c2fd1126a 100644 --- a/Proposed_First_FLEDGE_OT_Details.md +++ b/Proposed_First_FLEDGE_OT_Details.md @@ -189,7 +189,7 @@ The plan is for the FOT#1 to include support for multi-seller/SSP auctions. This To make Chrome’s FLEDGE implementation function as described in this document, use a very recent version of Chrome, e.g. Chrome Canary, and pass this parameter to Chrome on the command line: -`--enable-features=InterestGroupStorage,AdInterestGroupAPI,Fledge,AllowURNsInIframes,BiddingAndScoringDebugReportingAPI` +`--enable-features=InterestGroupStorage,AdInterestGroupAPI,Fledge,AllowURNsInIframes,BiddingAndScoringDebugReportingAPI,OverridePrivacySandboxSettingsLocalTesting,PrivacySandboxAdsAPIsOverride` #### Privacy Limitations of FOT#1 diff --git a/Release_Notes.md b/Release_Notes.md index 90677638c..7fcd2029e 100644 --- a/Release_Notes.md +++ b/Release_Notes.md @@ -2,10 +2,11 @@ # FLEDGE Release Notes +## Chrome M114 -## Chrome M109 - +* Functions that are called from Protected Audience worklets are now only accessible from inside the worklets, not from the global scope. See [#489](https://github.com/WICG/turtledove/issues/489) for more information. +## Chrome M109 * Since version 109.0.5414.16, the [`sendReports` parameter to `navigator.deprecatedURNToURL()`](https://github.com/WICG/turtledove/blob/main/Proposed_First_FLEDGE_OT_Details.md#advertisement-rendering) is respected. diff --git a/fledge-tester-list.md b/fledge-tester-list.md index e02b925cc..cc65a08b7 100644 --- a/fledge-tester-list.md +++ b/fledge-tester-list.md @@ -2,16 +2,16 @@ # FLEDGE Tester List -## Ecosystem Testing of FLEDGE +## Ecosystem Use of FLEDGE -The purpose of this page is to consolidate testing information which is currently distributed across various GitHub issues, company blogs, social posts, etc. +The purpose of this page is to consolidate information which is currently distributed across various GitHub issues, company blogs, social posts, etc. The usefulness of this page depends on testers sharing information and updates. ## Disclaimers -- Not a complete list. Testers are strongly encouraged to share their activities and insights publicly for the benefit of the broader community, but sharing is voluntary and therefore this page is not expected to reflect all testing activity. +- Not a complete list. Participants are strongly encouraged to share their activities and insights publicly for the benefit of the broader community, but sharing is voluntary and therefore this page is not expected to reflect all activity. -- Not evaluative. The purpose of this page is to consolidate links to information published by Privacy Sandbox testers. Editors will review submissions for relevance and to ensure general conformance to the guidelines above, but are not evaluating or endorsing the information provided. +- Not evaluative. The purpose of this page is to consolidate links to information published by Privacy Sandbox participants. Editors will review submissions for relevance and to ensure general conformance to the guidelines above, but are not evaluating or endorsing the information provided. - Editors will regularly review and approve submissions that meet the guidelines below. If you believe that an error has been submitted, please create an issue in the FLEDGE repository with the words ‘[Tester List]‘ in the subject and the Editors will respond in short order. @@ -32,17 +32,18 @@ The usefulness of this page depends on testers sharing information and updates. 1. On the FLEDGE GitHub page, navigate to the document in the main table called ‘fledge-tester-list.md’ (If you can read this sentence then you have already completed this step!) -1. Click the ‘pencil’ icon on the right side to edit the table and add your information +1. Click the ‘pencil’ icon on the right side to edit the appropriate table and add your information 1. Use the | to make sure that the information that you provide correctly shows up in each cell 1. After you select ‘Propose changes’ an editor will review and publish your updates in the coming days. 1. You can find additional details here for [editing tables in GitHub](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-tables). -## Table +## Table - Direct Integrations | Company / Party | Role in testing | Est. Testing Timeframe | Link to testing plan and/or learnings | How to contact you | | --------------- | --------------- | ---------------------- | ------------------------------------- | ------------------ | | Criteo | DSP | | | privacy-sandbox-testing@criteo.com | +| Teads | SSP & DSP | | | privacysandbox@teads.com | | NextRoll | DSP | | | privacysandbox@nextroll.com | | OpenX | SSP | Limited testing in progress | | joel.meyer@openx.com | | RTB House | DSP | Continuous testing ongoing; long term commitment. | https://blog.rtbhouse.com/whitepaper-deep-insights-from-early-fledge-experiments/ | privacysandbox@rtbhouse.com | @@ -54,3 +55,19 @@ The usefulness of this page depends on testers sharing information and updates. | MicroAd | SSP & DSP | | | privacysandbox@microad.co.jp | | Neodata Group | SSP & DSP | | | privacysandbox@neodatagroup.com | | Adlook (subsidiary of RTB House) | DSP | Continuous testing ongoing; long term commitment. | | privacysandbox@adlook.com | +| Microsoft (Xandr, MSAN) | SSP + DSP(s) | Testing | | privacy_sandbox@microsoft.com | +| Tremor International | SSP & DSP| 2023-2024 | coming soon | subhag.oak@amobee.com | +| Triplelift | SSP | Testing in progress | | prod-privacysandbox@triplelift.com | +| Seedtag | SSP & DSP| 2023-2024 | coming soon | privacysandbox@seedtag.com | + +## Table - Publishers and Advertisers Interested in Testing or Early Adoption +Companies who may be interested in participating in tests and early adoption opportunities provided by ad tech companies. + +| Company / Party | Role (publisher/sellside, advertiser/buyside, etc.) | Additional details about your interest (Optional) | How to contact you | +| --------------- | --------------------------------------------------- | ------------------------------------------------- | ------------------ | +| Mail Metro Media | Publisher | Transact through Google Ad Manager and Prebid fledgeForGpt module | programmatic.platforms@assocnews.co.uk | +| Vocento | Publisher | | privacysandbox@vocento.com | +| Clarin | Publisher | | mfranco@clarin.com | +| Terra Networks | Publisher | | adtech.terra.br@telefonica.com | +| OLX Brasil | Publisher | | adtech@olxbr.com | + diff --git a/meetings/2023-06-07-FLEDGE-call-minutes.md b/meetings/2023-06-07-FLEDGE-call-minutes.md new file mode 100644 index 000000000..2b450f4ca --- /dev/null +++ b/meetings/2023-06-07-FLEDGE-call-minutes.md @@ -0,0 +1,187 @@ +# Protected Audience (formerly FLEDGE) WICG Calls: Agenda & Notes + +Calls take place on some Wednesdays, at 11am US Eastern time. + +That's 8am California = 5pm Paris time = 3pm UTC (during summer). + +This notes doc will be editable during the meeting — if you can only comment, hit reload + +Notes from past calls are all on GitHub [in this directory](https://github.com/WICG/turtledove/tree/main/meetings). + + +# Next meeting: Wednesday June 7, 2023 + + +## Attendees: please sign yourself in! + + + +1. Michael Kleber (Google Privacy Sandbox) +2. Gianni Campion (Google Ads) +3. Fabian Höring (Criteo) +4. Sven May (Google Privacy Sandbox) +5. Roni Gordon (Index Exchange) +6. Harshad Mane (PubMatic) +7. Sid Sahoo (Google Chrome) +8. Paul Jensen (Google Chrome) +9. Russ Hamilton (Google Chrome) +10. Maciek Zdanowicz (RTB House) +11. Andrew Aikens (TripleLift) +12. Kevin Lee (Google Chrome) +13. Risako Hamano (Yahoo Japan) +14. Jonasz Pamula (RTB House) +15. Stan Belov (Google Ads) +16. Orr Bernstein (Google Chrome) +17. Tianyang Xu(Google Privacy Sandbox) +18. Youssef Bourouphael (Google Privacy Sandbox) +19. Isaac Schechtman (BidSwitch) +20. Tamara Yaeger (BidSwitch) +21. Caleb Raitto (Google Chrome) +22. Alfred Wong (Remerge) +23. David Dabbs (Epsilon) +24. Isaac Foster (MSFT[Xandr]) +25. Supraja Sekhar (Google Ads) +26. Ardian Poernomo (Google Ads) + + +## Note taker: Orr Bernstein + + +## To join the speaker queue: + +Please use the "Raise My Hand" feature in Google Meet. + + +# Agenda + + +### Process reminder: Join WICG + +If you want to participate in the call, please make sure you join the WICG: https://www.w3.org/community/wicg/ + + +## [Suggest agenda items here — no agenda no meeting!] + + + +* [How to handle k-anonymity fallback #592](https://github.com/WICG/turtledove/issues/592) [Fabian Höring] +* [Support more than one ad candidate and bid from generateBid #595](https://github.com/WICG/turtledove/issues/595) [Gianni Campion] +* [Leave all IGs for a given domain #475](https://github.com/WICG/turtledove/issues/475) [Gianni Campion] +* [Click related data in browserSignals #579](https://github.com/WICG/turtledove/issues/579) [Maciek Zdanowicz] + + +# Notes + + + +* [How to handle k-anonymity fallback #592](https://github.com/WICG/turtledove/issues/592) + * Fabian Höring + * Started looking into different renderURLs with different granularities + * Two URLs - one targets a very broad audience, less targeted, easy to pass the kAnon threshold, pretty sure we’ll have something to display; another that’s more targeted, harder to pass the kAnon threshold. + * What would be the strategy to implement to be sure to always display something with the kAnon requirements implemented? + * How to bootstrap at the very beginning when I have no URL to present. + * What happens whenI stop displaying one URL and want to make sure that I’m still above the threshold. + * Michael Kleber + * Background for kAnonymity in FLEDGE. + * Here was the plan for how to deal with kAnon in FLEDGE when I wrote the explainer in 2021. + * One of the privacy protections in what was once called FLEDGE is that ads shown to users cannot be targetted to a single user, needs to be targetted at a large enough group of user. + * Especially important with event level reporting. If there were a Michael Kleber ad that was shown only to Michael Kleber, they’d immediately know that I’d visited that page, could reconstruct my browsing history. Need a better technique. + * In the long-term, the better technique is that all reporting coming out of the Protected Audiences API is aggregate reporting, so people don’t know exactly what ad appeared at each page. + * But in the meantime, kAnon provides some initial protection. Requires that an ad is shown to at least 50 people in a particular week. If you see the ad, only a 2% chance it’s person X because it’s also been shown to another 49 people. + * But has a bootstrap problem. How can you show an ad for the first time? + * Proposed was, you can bid and say I want to show this ad, and if it’s not kAnon, it still counts as 1. So, you _try_ to show the ad to 50 people, and then on the 51st, you’ve met the threshold and you can show the ad. + * But during the ramp up, it seems unfair that the InterestGroup can’t show any ad. So there’s this fallback option, where the InterestGroup gets a second opportunity to bid when it tries to show an ad that’s not kAnon. The second time it gets an opportunity to bid, it can only put forth ads that are kAnon. + * That’s the two-step bidding process we have today. You bid once using all your ads, and a second time using only the ads that are kAnonymous. + * Fabian points out, if your strategy is, first try a very targeted ad, and then fallback to a generic ad, how do you get the generic ad past the kAnon threshold in the first place? + * Fabian + * Proposed solution. Some discussion in the ticket. Idea to increase the counter of multiple ads. Agree that it wouldn’t be a good idea to increase the counter for many ads, ok to increase the counter for only the ads which I have bid. + * Discussed strategy would be to start with the most generic ad. Then look at logs, and when it starts to be displayed, then start displaying the more specific ads. + * Let’s say I start on day 1, and I put forth my generic ad 50 times, but if on day 2, I display a less generic ad, on day 3 the displays from day 1 will expire. 3 day rolling window. + * Michael + * True. If you put forth the effort to bid with the more generic ad to begin with, then after seven days, it will pass the cliff, and now again you don’t have a fallback ad to fallback to. + * David Dabbs + * Would you be open if we could construct some sort of traceability mechanism to allow us to understand how we’re failing as an early temporary mechanism? + * Fabian + * Don’t see a difference of having a fallback option. Everything happens on the device. + * Michael + * If you allow the bidding function to know which ad is kAnonymous and which is not, then you’re asking the bidding function to say how much money you would put forth, but there’s no chance they’d have to pay that amount of money because it isn’t kAnonymous, their behavior would not reflect real bid behavior. You’d bet a million dollars for an ad that’s not kAnonymous because you know it can’t win the auction. + * Isaac Foster + * Degree to which kAnonymity is protected, up front by design. Has many positives, but also, whoever owns this introduces some level of accountability into the system. Why couldn’t you take a post-hoc or a mixed approach where ads can have a second state, which is “under review” or something that you could inspect, where the keys or the bidding function could be shut down. + * Michael + * At a higher level, the idea would be that anyone abusing the system by driving up numbers for bids for non-kAnon bids could be penalized. Certainly, this is something that’s technically feasible. Unfortunately, if someone were trying to do something abusive like that and also be subtle, they could do so. Not sure that something that is risky will necessarily be as flagrant as the attack I just described. + * Better for there to be technical limitations than to have to create structures and penalties around the lack of those. + * Isaac + * Certainly could detect straight-up abuse in that way. But also could do something like, a newly created URL that would have to go into this interesting bidding scenario, different state, if it didn’t get kAnonymity in a certain time, it goes back to the current state. May be cases where the tradeoffs benefit from the technical limitations not being so bulletproof. + * Michael + * Would prefer something like a solid technical answer first. Can fallback to the latter. + * David Dabbs + * Can we reach about the behavior of PAAPI auction mechanics if we had two concrete implementations? Doubletap two-tier - all creatives are eligible and then only the kAnon ones are eligible. The B&A case would look the same? + * Michael + * Yes, we don’t want people to have to think about both cases separately. + * Jonasz + * Would it be enough to attach the metadata, observed through reporting mechanisms, about how often each ad was shown to the InterestGroup? + * Fabian + * When you see display, you don’t know if the kAnon counts as 1 or 50. + * David Dabbs + * Also, the goal would be to move away from anything that makes a network access, move to things that use web bundles? + * Jonasz + * Even if one impression of an ad is being observed. You can play it safe and you can observe that there is at least 100 impressions, and then display the more granular ad. This would also work in future with aggregated reporting, though perhaps with different dynamics. + * Fabian + * Works for the beginning. When you see the ad for the first time, you know it passed the threshold. You can check the logs. + * Michael + * This is the hacked-together strategy I proposed in issue 592 for using the key-value server to see if the fallback ad is good, or should I occasionally use the fallback ad to ensure it stays above the threshold. + * Maybe related is the next topic on the agenda, about allowing each InterestGroup to return a list of ads, ordered by preference, it’s possible there is a way to do that, where it’s possible that the most highly preferred ad that’s above the threshold is the one you bid with in the auction, and the one that’s the least highly preferred ad is the one that gets to increase its kAnon. + * Paul + * Want to better understand the problem. Is the problem, if we’re looking for a kAnon of 50 counts of 7 days. That when the next 7 days starts, it gets reset? + * Fabian + * Let’s say I show the ad 50 times, and then over the next 6 days nothing, then on day 8, it would be ineligible. + * Paul + * Yes, if you do all of the displays at one time. Seems like an odd use case. + * Fabian + * Yes, but the strategy Michael recommended would work with existing APIs - ping to see if the generic ad is above the threshold. + * Michael + * Less of a concern if you have a lot of campaigns using the same fallback ad, because then each of the more specific ads helps to increment the kAnon number of the fallback ads. +* [Support more than one ad candidate and bid from generateBid #595](https://github.com/WICG/turtledove/issues/595) + * Gianni Campion + * Imagine you have two InterestGroups - one very big, one very small + * We return top candidate from each of these IGs + * The one from the big one loses to the one from the small one. The big IG can have a lot of other ads that would have done better. We lose the ability to make money from the big IG. + * Three problems + * With bigger IG, might bid with lower quality ad. + * In a sense, we’re throwing away a lot of ads when we return only one ads. With kAnon, chances are in the kAnon-enforced pass, you’ll do the same thing as you did in the non-kAnon enforced run. + * Would reduce the effect of kAnonymity, because if we have 10 ads, the first five not kAnon and then the next five kAnon. Could return them all. + * Michael + * If you have an IG that has 50 different ads. Not enthusiastic about taking all 50 of those and passing them to the SSP’s ScoreAds function. Now the SSP has to score 50 times as many ads, need to load SSP bidding signals for 50 times as many ads. + * Gianni + * Resource constraint + * Michael + * Yes, and surely something that DSPs have to deal with in RTB land today. DSPs can’t return an unlimited number of bids for the SSP to pick through. IIUC, DSP returns one or two ads to an SSP today, not more. You all would know this better than I do. + * Issac Foster + * Curious what the design concern is here. From a privacy architecture perspective. Outside of the resource constraints. I’ve worked at Microsoft and Xander for the past 11 years. Typically try to handle this by some sort of object limit, and not just 0 or 1. Bids come in, there’s some cap - not 50 but not 1 either - strike the right balance between publishers, advertisers, SSPs, ADSPs, and consumer. Come up with a number - maybe 5? + * Michael + * Not talking about a privacy issue here. As Gianni pointed out, the same as taking one big IG with 50 ads and turning it into 50 IGs with one ad each. So primarily talking about resourcing issue, and what’s feasible. + * Isaac + * Might have missed the core of this request. Didn’t understand the request to be that each bid would be submitted with one ad. But rather, that there’s matching that’s returned with some keys, and determines some set of ads to bid. + * Gianni + * If you have large IGs, might have large number of ads, some of them are dropped, some go off to bidding. Agree the number is 50. Minimum number is 2 - my idea would be that the generic bid picks all of the ads, and the browser picks the maximum one and the maximum kAnonymous one. Throw a couple more just for safety, and now you’re up to the five. + * Michael + * On the resource constraint side, if there are 50 ads, and you attach a bid to each of the 50 ads - to be clear, Chrome is not doing any evaluation of the merits of the ad - the DSP can rank those by whatever they want to rank them by. After the SSP has evaluated the top five bids coming out of the IG, if the SSP doesn’t like these as much as bids from some other IG, no value of the SSP evaluating the remaining ads from that IG. Don’t want the DSPs dropping any responsibility for selecting relevant ads, and leaving it to the SSPs. + * Issac + * Agree, we don’t want to make the DSPs return each ad with a shrug emoji. Ranking is not always straightforward. The idea that SSP would rank the DSPs bids in a different way than the DSP is not surprising. We would return to the SSP, which does the ranking. + * Even with a reasonably low object limit, if you can return up to five, increasing the degrees of freedom on how people can use this in a privacy safe way. + * Russ (in chat) + * To balance it, could the SSP place a limit on the number of ads per owner like they do now on the number of interest groups that bid? + * Michael + * Maybe top five and top five kAnonymous ones, then those are the ones we have the SSP evaluate. Right now, it’s one per InterestGroup. Obviously we need some change to the API that allows generateBid() to return more than one bid - that’s a purely mechanical issue - if you see a reason that’s a bad idea, please speak up. + * Gianni + * The cost of running more than one generateBid is more expensive than returning more than one bid from a single call to generateBid. So, we’re saving resources. And in cases of no kAnonymity, would save the cost of rerunning. + * Michael + * Agreed. Many ads that get evaluated twice in the rewriting of kAnonymity case. As long as we can avoid the case of, you didn’t like the first five bids, but I’ll have the SSP evaluate the rest of the 45 bids. + * Paul Jansen + * Like this for several reasons. Especially if some are kAnon and some are not, could yield good performance. We already group together TrustedScoringSignal fetches. The per-buyer limit - makes sense for the SSP to pick the number - if they picked 5, and it’s a per-buyer thing - it’s complicated if the buyer is multiple InterestGroups - how you distribute those five across InterestGroups. It could be different deals for different InterestGroups. If one IG uses the whole allotment of bids for a bidder, and there’s another IG that has lower value ads but ads that the SSP would evaluate as stronger. + * Paul/Michael + * Could use different strategies. For example, could pick the top 5 bids by value, or ensure that we spread out bids across more IGs. + * Isaac + * Enable the participants to solve their own problems sometimes through configurations or whatever. If this creates problems through different IGs, the answer doesn’t need to be that the implementation limits things perfectly. + * Michael + * Looking forward to continued conversation on GitHub. Hope to see you all back here in two weeks. diff --git a/meetings/2023-06-21-FLEDGE-call-minutes.md b/meetings/2023-06-21-FLEDGE-call-minutes.md new file mode 100644 index 000000000..f8968f37c --- /dev/null +++ b/meetings/2023-06-21-FLEDGE-call-minutes.md @@ -0,0 +1,226 @@ +# Protected Audience (formerly FLEDGE) WICG Calls: Agenda & Notes + +Calls take place on some Wednesdays, at 11am US Eastern time. + +That's 8am California = 5pm Paris time = 3pm UTC (during summer). + +This notes doc will be editable during the meeting — if you can only comment, hit reload + +Notes from past calls are all on GitHub [in this directory](https://github.com/WICG/turtledove/tree/main/meetings). + + +# Next meeting: Wednesday June 21, 2023 + + +## Attendees: please sign yourself in! + + + +1. Michael Kleber (Google Privacy Sandbox) +2. Youssef Bourouphael (Google Privacy Sandbox) +3. Nick Llerandi (Triplelift) +4. Andrew Aikens (TripleLift) +5. Sven May (Google Privacy Sandbox) +6. David Dabbs (Epsilon) +7. Orr Bernstein (Google Chrome) +8. Paul Jensen (Google Chrome) +9. Joel Meyer (OpenX) +10. Maciej Zdanowicz (RTB House) +11. Fabian Höring (Criteo) +12. Sid Sahoo (Google Chrome) +13. Michal Kalisz (RTB House) +14. Jonasz Pamula (RTB House) +15. Abishai Gray (Google Privacy Sandbox) +16. Manny Isu (Google Chrome) +17. Kevin Lee (Google Chrome) +18. Marco Lugo (NextRoll) +19. Tamara Yaeger (BidSwitch) +20. Don Marti (Raptive) (the company that used to be CafeMedia) +21. Caleb Raitto (Google Chrome) +22. Risako Hamano (Yahoo Japan) +23. Martin Pal (Google Privacy Sandbox) +24. Andrew Pascoe (NextRoll) +25. Stan Belov (Google Ads) + + +## Note taker: Joel Meyer + + +## To join the speaker queue: + +Please use the "Raise My Hand" feature in Google Meet. + + +# Agenda + + +### Process reminder: Join WICG + +If you want to participate in the call, please make sure you join the WICG: https://www.w3.org/community/wicg/ + + +## [Suggest agenda items here — no agenda no meeting!] + + + +* [Leave all IGs for a given domain #475](https://github.com/WICG/turtledove/issues/475) [Gianni Campion] +* [Click related data in browserSignals #579](https://github.com/WICG/turtledove/issues/579) [Maciek Zdanowicz] +* "[Mode a](https://developer.chrome.com/en/blog/shipping-privacy-sandbox/#mode-a)" plans, timeline [Jonasz Pamuła] + + +# Notes + +Michael Kleber: Please sign in. If you speak, please take a moment to verify that the notes reflect what you intended to say - scribes are only humans. Three items on the agenda, before we get there, a few notes on schedule: the meeting two weeks from now falls on July 5th, which will likely have a lot of people on vacation, including myself and Paul Jensen. So we will follow up in a month on July 19th, when Paul Jensen will lead the meeting. (David Dabbs notes this will be right after Chrome M115). While there are a lot of APIs intended to ship in 115, that does not automatically mean they will be enabled. As with many APIs in Chrome, the API will generally ramp up over the course of the month between M115 and the next release, as traffic fractions ramp up around the world. Exciting times ahead. + + + +* [Leave all IGs for a given domain #475](https://github.com/WICG/turtledove/issues/475) [Gianni Campion] + +Michael Kleber: First item is from Gianni - is he on? [Silence.] Moving on and may come back to it if Gianni comes back. This was a holdover from last time. + + + +### * [Click related data in browserSignals #579](https://github.com/WICG/turtledove/issues/579) [Maciek Zdanowicz] + +Maciej Zdanowicz: I work at RTBHouse and this issue is about click related data included in the browser signals. In the browser signals we have some data related to previous events related to the IG. In this case this is previous wins. We would be very glad and have found that it would be useful if previous clicks were also included. [PJ asks what this means in Chat]. This would be previous clicks on the FF for the same ad. Question from Alsonso? + +Alonso Vasquez: Can you please help us understand a little bit more about the use case you’re considering? My hope is that this is related to the testing happening in Private Aggregation. Are you talking about troubleshooting and op monitoring or bidding optimisation? A little more color would help. + +Maciej Zdanowicz: Sure - I’m a machine learning researcher at RTBHouse and this would be for the bidding optimization scenario. It would be helpful input to the optimization algorithms. + +Michael Kleber: Sometimes when people talk about clicks they mean navigation events. Other times it can be other mouse related interactions. Are you specifically talking about navigation or more general prior interactions? + +Maciej Zdanowicz: I believe navigation events would be sufficient for our purposes. + +David Dabbs:Yes, what would sync up with the FF private aggregation reporting. + +Michael Kleber: For FF we tried to make it possible to define other types of interactions that may line up with MRC Viewability, for example. However, the more complex the use case you support, the more risk there is for abuse… eg, insert a captcha that can be used to join info on another site down the road. But for a navigable click, there doesn’t seem to be as much concern. Paul - what do you think? + +Paul Jensen: Well, this is cross-site information of a sort. + +Michael Kleber: We already have this in the case of previous wins, the simplest case I can think of would be adding a boolean to this that tracks if the ad was also clicked on. + +Paul Jensen: Okay, in the issue there’s discussion of a separate structure to track this information. + +David Dabbs: Seems like MK Is making a counter-proposal that differs from the issue. + +Michael Kleber: Yes, I proposed a simpler solution. + +Jonasz Pamula: Yeah, the boolean would work. + +Paul Jensen: From a privacy perspective that doesn’t seem too bad since there’s fewer clicks. + +Michael Kleber: Once you’re already recording wins, I don’t think there’s any incremental privacy loss(risk?) from recording if it was clicked on. + +Paul Jensen: If you’re recording clicks separately then it seems like it could be more prone to privacy loss, especially if that can be joined with other signals advertisers get. + +Michael Kleber: Yeah, but if it’s just a signal on an existing win, I think it’s less of an issue. + +David Dabbs: Would this information be subject to DP noise? + +Paul Jensen: I think we’re talking about bidding inputs, which are generally un-noised. The reporting data is typically what gets noised. And worth noting that we recently switched to milliseconds vs seconds for some data, because that’s the standard jS timestamp. + +Michael Kleber: Confirming no noise involved here. As a feature request, seems like there is no problem with this but we can’t promise anything regarding delivery timeline. But in principle there is no problem and we’ll just have to see when we get around to implementing. + +Maciej Zdanowicz: Great, thank you! + +Paul Jensen: Will this break people who use the existing API? It’s a vector of two items right now, and we’ll be adding a third. + +Michael Kleber: Hmmm, it’s a list of two element lists, not a struct…. Hmm, who designed that? (spoiler alert: MK) + +Paul Jensen: Anyone who counts on the length of the list being two could be impacted. + +David Dabbs: I have a meta-question about Maciej’s (brief explanation of Polish names). There was a feature request, some dialogue, and then agreement. Where do we memorialize that this type of thing happened and track it so that more than just those who are present know about it? + +Alonso Vasquez: As far as the intake mechanism, our current method seems to work. We have the calls, GH, and we have deployed resources through our partnerships team. Whenever we have these meetings that result in good feedback, we have a mechanism for reconciling that with GH and our internal process. So the TL;DR: is that we have a process and it’ll all be tracked. In a nutshell, that’s my job. + +Michael Kleber: The important takeaway from David’s question is that it would be nice if there was some publicly available list of backlog feature requests that have been accepted. Some way that doesn’t require people to troll through all the notes, etc. + +Michael Kleber: Is Gianni here? [Nope.] Moving on. + + + +### * “[Mode A](https://developer.chrome.com/en/blog/shipping-privacy-sandbox/#mode-a)” Plans/Timeline + +Michael Kleber: Jonasz put this here? + +Jonasz Pamula:Yes, thank you. I have a comment and a question. The comment: as RTBHouse we would like to signal our readiness and willingness to participate in Mode A and Mode B. We would like to start small. One of the proposed labels was 0.25% - that would work for us. 0.1% would work as well. The question: Is there a reason to start it in Q4, or could we start it earlier? If we could start earlier, we would like to do so. We imagine it will take some time to integrate with these labels and pass them from the sell side to the buy side, which reduces the time for actual testing. + +Michael Kleber: Let me provide some context first: there are two different types of testing of the Privacy Sandbox APIs. If you attend here, we’ve talked about it before, and there was a difference of opinion both here and elsewhere on which way testing should be done. As a result, we chose to do both. Perhaps everyone will be happy, or no one will be happy. Reference material is https://developer.chrome.com/docs/privacy-sandbox/chrome-testing/ + + + +* In Mode A, there are some branches of traffic at small/varying percentages that are labeled as “this is a 0.1% branch” indicating that 0.1% of people are in this experiment. And there’s another branch that indicates this is the control group. This lets people who voluntarily want to ignore 3p cookies and try the PS APIs for one group of people to have an A/B comparison. The labeling allows all participating to agree on the experiment and control groups. +* This is contrasted with the Mode B approach, which is happening in Q1 of 2024 when some slice of traffic will have 3rd party cookies turned off. In this case, the loss of cookies is not voluntary. + +Mode A is scheduled to happen earlier, and the question is how soon can it start? The answer is that we don’t have the right people in the room to talk about the start date and the prereqs that must happen beforehand. So I don’t actually know if we can answer that question right now. I understand the desire to enable Mode A as soon as possible and I will pass along your comments to the people who are running the experiments across all of Privacy Sandbox and we will update our timelines as soon as they are nailed down. We definitely appreciate the feedback on traffic fraction sizes is helpful and would be best put in GitHub where it was asked for. Putting the timeline comment there is also helpful. + +Jonasz Pamula: Thanks, we’ve already put comments in the GH issue at https://github.com/GoogleChromeLabs/privacy-sandbox-dev-support/issues/112 and we are happy to start. + +Michael Kleber: Fabian - did you have a question? + +FH: I have a question related to Mode B. Can you explain how you will expire the cookies from one day to another? And how does this relate to CHiPS? + +Michael Kleber: I don’t know exactly when Mode B traffic will start, but the way you should think about it is that for the people who are in the Mode B branch, it will be as if that user had toggled the Chrome setting to disable 3P cookies. So all the 3P cookies previously stored will stop being sent/visible/available in any way and new cookies won’t be set. If you want to experiment, feel free to check the “remove 3P cookies” box and see how things behave. I don’t believe that CHIPS should be impacted at all. The partitioned cookies that are a part of CHIPS should be carried through the way they are today. David? + +David Dabbs: Going back to the question earlier about where we’ll discuss these changes, I asked Rowan Merewood and he said there would be some forum created where it could be discussed. I would strongly encourage that to happen soon. My question about Mode B - you didn’t say if you would label Mode B, but I think you need a good before/after measure, so will you label Mode B in advance so you can see the change in performance before/after the removal of cookies? + +Michael Kleber: I’m not sure - that’s a good question. Asking on GH seems to be the right place, which you have done. + +David Dabbs: Related to that, it would be useful to everyone if someone who is in that cohort has all the PS features enabled. + +Michael Kleber: Yes, I think what you’re describing is the intention. The Mode B happens when all the PS features have reached GA and are available. That should be the same for people in any of the branches of experiment. Well, mostly — I will mention that there is a sub-branch in Mode B in which the APIs are not available just as a hold-back for further measurement. + +David Dabbs: You described Mode B as someone who elected to turn off cookies - is that the same as the state after 3P cookies are deprecated? + +Michael Kleber: I believe there is no difference between those states. If something comes up then I will have to revisit that answer, but I believe these are the same states. + +David Dabbs: [Some question about canary and a feature that allows sites to use 3P cookies.] + +Michael Kleber: Unclear on what you’re talking about. There are user controls for a lot of things. I don’t know if there is a user control along the lines of what you’re discussing. + +Sid Sahoo: [Questions in chat.] + +Michael Kleber: CHIPS is entirely separate from the discussion. Everything else is unpartitioned, where CHIPS is partitioned by site. Anything that is unpartitioned will go away. + +Alonso Vasquez: You were asking about user experience and changes to user controls… + +David Dabbs: No, not really. I thought there was some control where users could enable 3P cookies on some sites. I could be wrong. But if that goes away, then it doesn’t matter. + +Michael Kleber: Right now in Chrome Stable settings there is a switch to allow 3P cookies or block 3P cookies. But there is not a site specific control to enable 3P cookies. Wait.. might be a setting to “always allow cookies”? Hmm, I don’t know the answer. Very frequently there are obscure controls that won’t get used outside Enterprise Policy settings. EG - to enable some intranet sites to work. The mere fact that there is a UI with a list of sites that make an exception doesn’t guarantee it will stay or go, but I can’t tell you exactly if it will stay or go in the post 3P cookie era. + + + +### * [Leave all IGs for a given domain #475](https://github.com/WICG/turtledove/issues/475) [Gianni Campion] + +Michael Kleber: Based on reading Gianni’s original request I think I know the answer to Paul’s question. This looks like a request for the ability to leave all the IGs that a user was added to by a particular ad-tech on a particular site. That seems reasonable to enable. + +Paul Jensen: Yeah, if they all have the same owner and were on the same site, it seems reasonable to enable them to leave. + +Michael Kleber: Yeah, this is something you could’ve done with a 1P cookie, so seems reasonable. This is another example of a feature request we would be happy to support and just need to put on the backlog. + +David Dabbs: You could use the clear-site data header or something similar. + +Michael Kleber: I think we’d prefer to take requests from people who actually made the request, so I don’t feel like I want to make a header and attributes without the use case in hand. + +Paul Jensen: I’d like to learn more about Gianni’s motivation. He mentioned two things, and i’m curious if other people have similar sort of needs: 1) Resets the state of device IGs to ensure it’s in sync with the server. This would let you know that they can be in new IGs without being in old ones. + +Michael Kleber: Sounds like it’s worth discussing so I’ll carry it over to the call from a month from now, or we can talk internally, unless other people have input and similar requests - we can engage over GH or a future call. + +Michael Kleber: David - did you want to add something? + +David Dabbs:I think there’s a document that someone maintains call FOT (First Origin Trial) release notes. One of those documents contains the list of what I call asterisks in this feature and also other ones. I guess, specific to PAAPI, since we’re just about to be beyond the first OT, it would be really handy to have a “here’s the known knowns” that are a part of the product not intended to last forever so we can reason about the tech debt that’s built into the product that we’ll have to address over time. Right now it feels like it’s in the spec, in the explainer, in diff places. + +Michael Kleber: There is a blog post from Tris that provides the info you’re looking for. There it is, it’s on the developer site: [https://developer.chrome.com/en/docs/privacy-sandbox/fledge-api/feature-status/]. The page indicates feature statuses, including things that are there now and will go away sometime in the future. I’ll make sure that link is in the notes also. + +David Dabbs:[Question regarding other headers, etc that might change.] + +Paul Jensen: I published the intent to ship to the email list yesterday, so we’re currently in a transition period. But once we’re rolled out to everyone we’ll be unable to willy-nilly make changes to the API as it’s shipped. Going forward, after we deploy to stable branch, we’ll be subject to Chrome’s general release process for changes we make. This is called the blink-release process for visible changes. Beyond 115 any change will result in publishing an intent, eg intent to prototype, intent to experiment, intent to ship, etc. That will be the way to find out about what’s changing. Each of those should keep the spec up-to-date on where it’s at. So we’ll move away from updating release notes and into the blink-release process. + +Michael Kleber: In the same way that the future central repository for requests is a good idea, I think a central repository for coming changes would be a good idea. That seems like a reasonable request. + +Paul Jensen: The Chrome Status page offers something like that. + +Michael Kleber: That’s right, so the Chrome Status for Privacy Sandbox will have that info. But it would be nice if we had something better than that. + +Michael Kleber: Great, thank you all for the discussion. Much of it focused on better sources of information, which is great feedback. And I think we’re set. As noted at the start, no call in two weeks (July 5th). Hope all of you have a good start to the month. Our next call is in four weeks on July 19th when I’ll be on vacation - have a good time without me! And Paul will run the meeting IF there is an agenda. diff --git a/spec.bs b/spec.bs index 6dc0382c8..584d933dd 100644 --- a/spec.bs +++ b/spec.bs @@ -22,25 +22,69 @@ Assume Explicit For: yes
 urlPrefix: https://www.ietf.org/rfc/rfc4122.txt
   type: dfn; text: urn uuid
+spec: RFC6234; urlPrefix: https://www.ietf.org/rfc/rfc6234.txt
+  type: dfn; text: SHA-256
 spec: html; urlPrefix: https://html.spec.whatwg.org/C
   type: dfn
     text: create an agent; url: create-an-agent
-spec: ECMASCRIPT; urlPrefix: https://tc39.es/ecma262/
+    text: immediately; url: immediately
+    text: valid floating-point number; url: valid-floating-point-number
+spec: infra; urlPrefix: https://infra.spec.whatwg.org/
   type: dfn
-    text: ParseScript; url: sec-parse-script
-    text: abrupt completion; url: sec-completion-record-specification-type
-    text: throw completion; url: sec-completion-record-specification-type
-    text: ScriptEvaluation; url: sec-runtime-semantics-scriptevaluation
+    text: convert an Infra value to a JSON-compatible JavaScript value; url: #convert-an-infra-value-to-a-json-compatible-javascript-value
 spec: RFC8941; urlPrefix: https://httpwg.org/specs/rfc8941.html
   type: dfn
     text: structured header; url: top
     for: structured header
       text: integer; url: integer
+spec: WebAssembly; urlPrefix: https://webassembly.github.io/spec/core/
+  type: dfn
+    urlPrefix: appendix/embedding.html
+      text: error; url: embed-error
 spec: WebAssembly-js-api; urlPrefix: https://webassembly.github.io/spec/js-api/
   type: dfn
     text: compiling a WebAssembly module; url: #compile-a-webassembly-module
+spec: WebIDL; urlPrefix: https://webidl.spec.whatwg.org/
+  type: dfn
+    text: convert a Web IDL arguments list to an ECMAScript arguments list; url: #web-idl-arguments-list-converting
+spec: Fenced Frame; urlPrefix: https://wicg.github.io/fenced-frame/
+  type: dfn
+    for: browsing context
+      text: fenced frame config instance; url: #browsing-context-fenced-frame-config-instance
 
+ + # Introduction # {#intro} This section is non-normative @@ -71,22 +115,23 @@ advertisement relevant to this interest to this user in the future. The user age [SecureContext] partial interface Navigator { - Promise<undefined> joinAdInterestGroup(AuctionAdInterestGroup group, double durationSeconds); + Promise<undefined> joinAdInterestGroup(AuctionAdInterestGroup group); }; dictionary AuctionAd { required USVString renderURL; any metadata; + USVString buyerReportingId; + USVString buyerAndSellerReportingId; }; -dictionary AuctionAdInterestGroup { +dictionary GenerateBidInterestGroup { required USVString owner; required USVString name; + required double lifetimeMs; - double priority = 0.0; boolean enableBiddingSignalsPrioritization = false; record<DOMString, double> priorityVector; - record<DOMString, double> prioritySignalsOverrides; DOMString executionMode = "compatibility"; USVString biddingLogicURL; @@ -98,93 +143,117 @@ dictionary AuctionAdInterestGroup { sequence<AuctionAd> ads; sequence<AuctionAd> adComponents; }; + +dictionary AuctionAdInterestGroup : GenerateBidInterestGroup { + double priority = 0.0; + record<DOMString, double> prioritySignalsOverrides; +}; +{{AuctionAdInterestGroup}} is used by {{Window/navigator}}.{{Navigator/joinAdInterestGroup()}}, and +when an interest group is stored to [=interest group set=]. +`priority` and `prioritySignalsOverrides` are not passed to `generateBid()` because they can be +modified by `generatedBid()` calls, so could theoretically be used to create a cross-site profile of +a user accessible to `generateBid()` methods, otherwise. +
-The joinAdInterestGroup(|group|, |durationSeconds|) method steps -are: +The joinAdInterestGroup(|group|) method steps are: +
+ +Temporarily, Chromium does not include the required keyword +for {{GenerateBidInterestGroup/lifetimeMs}}, and instead starts this algorithm with the step + +1. If |group|["{{GenerateBidInterestGroup/lifetimeMs}}"] does not [=map/exist=], throw a {{TypeError}}. + +This is detectable because it can change the set of fields that are read from the argument when a +{{TypeError}} is eventually thrown, but it will never change whether the call succeeds or fails. +
1. If [=this=]'s [=relevant global object=]'s [=associated Document=] is not [=allowed to use=] the "[=join-ad-interest-group=]" [=policy-controlled feature=], then [=exception/throw=] a "{{NotAllowedError}}" {{DOMException}}. -1. Let |frameOrigin| be the [=relevant settings object=]'s [=environment settings object/origin=]. -1. [=Assert=] that |frameOrigin| is not an [=opaque origin=] and its [=origin/scheme=] is "https". +1. Let |frameOrigin| be [=this=]'s [=relevant settings object=]'s [=environment settings object/origin=]. +1. [=Assert=] that |frameOrigin| is not an [=opaque origin=] and its [=origin/scheme=] is "`https`". 1. Let |interestGroup| be a new [=interest group=]. 1. Validate the given |group| and set |interestGroup|'s fields accordingly. - 1. Set |interestGroup|'s [=interest group/expiry=] to now plus |durationSeconds|. - 1. Set |interestGroup|'s [=interest group/next update after=] to now plus 24 hours. + 1. Set |interestGroup|'s [=interest group/expiry=] to the [=current wall time=] plus + |group|["{{GenerateBidInterestGroup/lifetimeMs}}"] milliseconds. + 1. Set |interestGroup|'s [=interest group/next update after=] to the [=current wall time=] plus 24 + hours. 1. Set |interestGroup|'s [=interest group/owner=] to the result of [=parsing an origin=] on - |group|["{{AuctionAdInterestGroup/owner}}"]. - 1. If |interestGroup|'s [=interest group/owner=] is an error, or its [=url/scheme=] is not + |group|["{{GenerateBidInterestGroup/owner}}"]. + 1. If |interestGroup|'s [=interest group/owner=] is failure, or its [=url/scheme=] is not "`https`", [=exception/throw=] a {{TypeError}}. - 1. Set |interestGroup|'s [=interest group/name=] to |group|["{{AuctionAdInterestGroup/name}}"]. + 1. Set |interestGroup|'s [=interest group/name=] to |group|["{{GenerateBidInterestGroup/name}}"]. 1. Set |interestGroup|'s [=interest group/priority=] to |group|["{{AuctionAdInterestGroup/priority}}"]. 1. Set |interestGroup|'s [=interest group/enable bidding signals prioritization=] to - |group|["{{AuctionAdInterestGroup/enableBiddingSignalsPrioritization}}"]. - 1. If |group|["{{AuctionAdInterestGroup/priorityVector}}"] [=map/exists=], then set + |group|["{{GenerateBidInterestGroup/enableBiddingSignalsPrioritization}}"]. + 1. If |group|["{{GenerateBidInterestGroup/priorityVector}}"] [=map/exists=], then set |interestGroup|'s [=interest group/priority vector=] to - |group|["{{AuctionAdInterestGroup/priorityVector}}"]. + |group|["{{GenerateBidInterestGroup/priorityVector}}"]. 1. If |group|["{{AuctionAdInterestGroup/prioritySignalsOverrides}}"] [=map/exists=], then set |interestGroup|'s [=interest group/priority signals overrides=] to |group|["{{AuctionAdInterestGroup/prioritySignalsOverrides}}"]. 1. Set |interestGroup|'s [=interest group/execution mode=] to - |group|["{{AuctionAdInterestGroup/executionMode}}"]. - 1. For each |groupMember| and |interestGroupField| in the following table + |group|["{{GenerateBidInterestGroup/executionMode}}"]. + 1. For each |groupMember| and |interestGroupField| in the following table +
- + - + - + - +
Group memberInterest group field
"{{AuctionAdInterestGroup/biddingLogicURL}}""{{GenerateBidInterestGroup/biddingLogicURL}}" [=interest group/bidding url=]
"{{AuctionAdInterestGroup/biddingWasmHelperURL}}""{{GenerateBidInterestGroup/biddingWasmHelperURL}}" [=interest group/bidding wasm helper url=]
"{{AuctionAdInterestGroup/updateURL}}""{{GenerateBidInterestGroup/updateURL}}" [=interest group/update url=]
"{{AuctionAdInterestGroup/trustedBiddingSignalsURL}}""{{GenerateBidInterestGroup/trustedBiddingSignalsURL}}" [=interest group/trusted bidding signals url=]
1. If |group| [=map/contains=] |groupMember|: 1. Let |parsedUrl| be the result of running the [=URL parser=] on |group|[|groupMember|]. 1. [=exception/Throw=] a {{TypeError}} if any of the following conditions hold: - * |parsedUrl| is an error; + * |parsedUrl| is failure; * |parsedUrl| is not [=same origin=] with |interestGroup|'s [=interest group/owner=]; * |parsedUrl| [=includes credentials=]; * |parsedUrl| [=url/fragment=] is not null. 1. Set |interestGroup|'s |interestGroupField| to |parsedUrl|. 1. If |interestGroup|'s [=interest group/trusted bidding signals url=]'s [=url/query=] is not null, then [=exception/throw=] a {{TypeError}}. - 1. If |group|["{{AuctionAdInterestGroup/trustedBiddingSignalsKeys}}"] [=map/exists=], then set + 1. If |group|["{{GenerateBidInterestGroup/trustedBiddingSignalsKeys}}"] [=map/exists=], then set |interestGroup|'s [=interest group/trusted bidding signals keys=] to - |group|["{{AuctionAdInterestGroup/trustedBiddingSignalsKeys}}"]. - 1. If |group|["{{AuctionAdInterestGroup/userBiddingSignals}}"] [=map/exists=]: - 1. Let |interestGroup|'s [=interest group/user bidding signals=] be the result of + |group|["{{GenerateBidInterestGroup/trustedBiddingSignalsKeys}}"]. + 1. If |group|["{{GenerateBidInterestGroup/userBiddingSignals}}"] [=map/exists=]: + 1. Set |interestGroup|'s [=interest group/user bidding signals=] to the result of [=serializing a JavaScript value to a JSON string=], given - |group|["{{AuctionAdInterestGroup/userBiddingSignals}}"]. This can [=exception/throw=] a + |group|["{{GenerateBidInterestGroup/userBiddingSignals}}"]. This can [=exception/throw=] a {{TypeError}}. - 1. For each |groupMember| and |interestGroupField| in the following table + 1. For each |groupMember| and |interestGroupField| in the following table +
- + - +
Group memberInterest group field
"{{AuctionAdInterestGroup/ads}}""{{GenerateBidInterestGroup/ads}}" [=interest group/ads=]
"{{AuctionAdInterestGroup/adComponents}}""{{GenerateBidInterestGroup/adComponents}}" [=interest group/ad components=]
- 1. [=list/For each=] |ad| of |group|[|groupMember|]: + 1. If |group| [=map/contains=] |groupMember|, [=list/for each=] |ad| of |group|[|groupMember|]: 1. Let |igAd| be a new [=interest group ad=]. 1. Let |renderURL| be the result of running the [=URL parser=] on |ad|["{{AuctionAd/renderURL}}"]. 1. [=exception/Throw=] a {{TypeError}} if any of the following conditions hold: - * |renderURL| is an error; + * |renderURL| is failure; * |renderURL| [=url/scheme=] is not "`https`"; * |renderURL| [=includes credentials=]. 1. Set |igAd|'s [=interest group ad/render url=] to |renderURL|. @@ -192,14 +261,19 @@ are: |igAd|'s [=interest group ad/metadata=] be the result of [=serializing a JavaScript value to a JSON string=], given |ad|["{{AuctionAd/metadata}}"]. This can [=exception/throw=] a {{TypeError}}. + 1. If |groupMember| is "{{GenerateBidInterestGroup/ads}}": + 1. If |ad|["{{AuctionAd/buyerReportingId}}"] [=map/exists=], then set + |igAd|'s [=interest group ad/buyer reporting ID=] to it. + 1. If |ad|["{{AuctionAd/buyerAndSellerReportingId}}"] [=map/exists=], + then set |igAd|'s [=interest group ad/buyer and seller reporting ID=] to it. 1. [=list/Append=] |igAd| to |interestGroup|'s |interestGroupField|. 1. If |interestGroup|'s [=interest group/estimated size=] is greater than 50 KB, then [=exception/throw=] a {{TypeError}}. 1. Let |p| be [=a new promise=]. 1. Let |queue| be the result of [=starting a new parallel queue=]. 1. [=parallel queue/enqueue steps|Enqueue the following steps=] to |queue|: - 1. Let |permission| be the result of [=checking interest group permissions=] with - |interestGroup|'s [=interest group/owner=], |frameOrigin|, and true. + 1. Let |permission| be the result of [=checking interest group permissions=] with + |interestGroup|'s [=interest group/owner=], |frameOrigin|, and "`join`". 1. If |permission| is false, then [=queue a task=] to [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and do not run the remaining steps. 1. [=Queue a task=] to [=resolve=] |p| with `undefined`. @@ -210,9 +284,9 @@ are: the currently stored one from the browser. 1. Set |interestGroup|'s [=interest group/joining origin=] to [=this=]'s [=relevant settings object=]'s [=environment/top-level origin=]. - 1. If the most recent entry in [=interest group/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. + 1. If the most recent entry in |interestGroup|'s [=interest group/join counts=] corresponds to + the current day in UTC, increment its count. If not, [=list/insert=] a new [=tuple=] + the time set to the current UTC day and a count of 1. 1. Store |interestGroup| in the browser’s [=interest group set=]. 1. Return |p|. @@ -231,8 +305,8 @@ The estimated size of an [=interest group=] |ig| [=interest group/priority vector=]: 1. The [=string/length=] of |key|. 1. 8 bytes, which is the size of |value|. -1. If |ig|'s [=interest group/priority signals overrides=] is not null, [=map/for each=] |key| → - |value| of [=interest group/priority signals overrides=]: +1. If |ig|'s [=interest group/priority signals overrides=] is not null, [=map/for each=] |key| → |value| of + [=interest group/priority signals overrides=]: 1. The [=string/length=] of |key|. 1. 8 bytes, which is the size of |value|. 1. The size of [=interest group/execution mode=]. @@ -251,6 +325,8 @@ The estimated size of an [=interest group=] |ig| 1. The [=string/length=] of the [=URL serializer|serialization=] of |ad|'s [=interest group ad/render url=]. 1. The [=string/length=] of |ad|'s [=interest group ad/metadata=] if the field is not null. + 1. The [=string/length=] of |ad|'s [=interest group ad/buyer reporting ID=] if the field is not null. + 1. The [=string/length=] of |ad|'s [=interest group ad/buyer and seller reporting ID=] if the field is not null. 1. If |ig|'s [=interest group/ad components=] is not null, [=list/for each=] |ad| of it: 1. The [=string/length=] of the [=URL serializer|serialization=] of |ad|'s [=interest group ad/render url=]. @@ -261,27 +337,47 @@ The estimated size of an [=interest group=] |ig|
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. -1. Let |permissionsUrl| be the result of [=building an interest group permissions url=] - with |ownerOrigin| and |frameOrigin|. -1. Let |request| be the result of [=creating a request=] with |permissionsUrl|, - "`application/json`", and null. +|ownerOrigin|, an [=origin=] |frameOrigin|, and an enum |joinOrLeave| which is "`join`" or "`leave`": +1. If |ownerOrigin| is [=same origin=] with |frameOrigin|, then return true. +1. Let |permissionsUrl| be the result of [=building an interest group permissions url=] with + |ownerOrigin| and |frameOrigin|. +1. Let |request| be a new [=request=] with the following properties: + : [=request/URL=] + :: |permissionsUrl| + : [=request/header list=] + :: «`Accept`: `application/json`» + : [=request/client=] + :: `null` + : [=request/service-workers mode=] + :: "`none`" + : [=request/origin=] + :: |frameOrigin| + : [=request/mode=] + :: "`cors`" + : [=request/referrer=] + :: "`no-referrer`" + : [=request/credentials mode=] + :: "`omit`" + : [=request/redirect mode=] + :: "`error`" 1. Let |resource| be null. -1. [=Fetch=] |request| with [=fetch/processResponseConsumeBody=] set to the following steps given a - [=response=] |response| and |responseBody|: +1. [=Fetch=] |request| with [=fetch/useParallelQueue=] set to true, and + [=fetch/processResponseConsumeBody=] set to the following steps given a [=response=] |response| + and null, failure, or a [=byte sequence=] |responseBody|: 1. If |responseBody| is null or failure, set |resource| to failure and return. 1. Let |headers| be |response|'s [=response/header list=]. 1. Let |mimeType| be the result of [=header list/extracting a MIME type=] from |headers|. - 1. If |mimeType| is failure or is not a [=JSON MIME Type=], throw, set |resource| to failure and return. + 1. If |mimeType| is failure or is not a [=JSON MIME Type=], set |resource| to failure and return. 1. Set |resource| to |responseBody|. 1. Wait for |resource| to be set. 1. If |resource| is failure, then return false. -1. Let |permissions| be the result of [=parsing a JSON string to an Infra value=] with |resource|, returning false - on failure. +1. Let |permissions| be the result of [=parsing JSON bytes to an Infra value=] with |resource|, + returning false on failure. 1. If |permissions| is not an [=ordered map=], then return false. -1. If |isJoin| is true and |permissions|["`joinAdInterestGroup`"] [=map/exists=], then return |permissions|["`joinAdInterestGroup`"]. -1. If |isJoin| is false and |permissions|["`leaveAdInterestGroup`"] [=map/exists=], then return |permissions|["`leaveAdInterestGroup`"]. +1. If |joinOrLeave| is "`join`" and |permissions|["`joinAdInterestGroup`"] [=map/exists=], then + return |permissions|["`joinAdInterestGroup`"]. +1. If |joinOrLeave| is "`leave`" and |permissions|["`leaveAdInterestGroup`"] [=map/exists=], then + return |permissions|["`leaveAdInterestGroup`"]. 1. Return false. The browser may cache requests for |permissionsUrl| within a network partition. @@ -305,20 +401,14 @@ To build an interest group permissions url given a [=origin=] |ownerO

Leaving Interest Groups

-In order to remove a user from a particular interest group, - -{{Window/navigator}}.{{Navigator/leaveAdInterestGroup()}} can be called. +{{Window/navigator}}.{{Navigator/leaveAdInterestGroup()}} removes a user from a particular interest +group. -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); + Promise<undefined> leaveAdInterestGroup(optional AuctionAdInterestGroupKey group = {}); }; dictionary AuctionAdInterestGroupKey { @@ -332,24 +422,41 @@ dictionary AuctionAdInterestGroupKey { The <dfn for=Navigator method>leaveAdInterestGroup(group)</dfn> 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 [=exception/throw=] a - "{{NotAllowedError}}" {{DOMException}}. -1. Let |frameOrigin| be the [=relevant settings object=]'s [=environment settings object/origin=]. -1. [=Assert=] that |frameOrigin| is not an [=opaque origin=] and its [=origin/scheme=] is "https". -1. Let |owner| be the result of [=parsing an origin=] with - |group|["{{AuctionAdInterestGroupKey/owner}}"]. -1. If |owner| is failure, [=exception/throw=] a {{TypeError}}. -1. Let |name| be |group|["{{AuctionAdInterestGroupKey/name}}"]. +1. Let |frameOrigin| be [=this=]'s [=relevant settings object=]'s + [=environment settings object/origin=]. +1. [=Assert=] that |frameOrigin| is not an [=opaque origin=] and its [=origin/scheme=] is "`https`". 1. Let |p| be [=a new promise=]. -1. Run these steps [=in parallel=]: - 1. Let |permission| be the result of [=checking interest group permissions=] with - |owner|, |frameOrigin|, and false. - 1. If |permission| is false, then [=queue a task=] to [=reject=] |p| with a - "{{NotAllowedError}}" {{DOMException}} and do not run the remaining steps. - 1. [=Queue a task=] to [=resolve=] |p| with `undefined`. - 1. [=list/Remove=] [=interest groups=] from the user agent's [=interest group set=] whose - [=interest group/owner=] is |owner| and [=interest group/name=] is |name|. +1. If |group| [=map/is empty=]: + 1. Let |instance| be [=this=]'s [=relevant global object=]'s [=Window/browsing context=]'s + [=browsing context/fenced frame config instance=]. + 1. If |instance| is null, [=exception/throw=] a {{TypeError}}. + 1. Let |interestGroup| be |instance|'s [=fenced frame config instance/interest group descriptor=]. + 1. Run these steps [=in parallel=]: + 1. [=Queue a task=] to [=resolve=] |p| with `undefined`. + 1. If |interestGroup| is not null: + 1. Let |owner| be |interestGroup|'s [=interest group descriptor/owner=]. + 1. Let |name| be |interestGroup|'s [=interest group descriptor/name=]. + 1. If |owner| is [=same origin=] with |frameOrigin|: + 1. [=list/Remove=] [=interest groups=] from the user agent's [=interest group set=] whose + [=interest group/owner=] is |owner| and [=interest group/name=] is |name|. +1. Otherwise: + 1. If [=this=]'s [=relevant global object=]'s [=associated Document=] is not [=allowed to use=] the + "[=join-ad-interest-group=]" [=policy-controlled feature=], then [=exception/throw=] a + "{{NotAllowedError}}" {{DOMException}}. + + Note: both joining and leaving interest groups use the "join-ad-interest-group" feature. + 1. Let |owner| be the result of [=parsing an origin=] with + |group|["{{AuctionAdInterestGroupKey/owner}}"]. + 1. If |owner| is failure, [=exception/throw=] a {{TypeError}}. + 1. Let |name| be |group|["{{AuctionAdInterestGroupKey/name}}"]. + 1. Run these steps [=in parallel=]: + 1. Let |permission| be the result of [=checking interest group permissions=] with + |owner|, |frameOrigin|, and "`leave`". + 1. If |permission| is false, then [=queue a task=] to [=reject=] |p| with a + "{{NotAllowedError}}" {{DOMException}} and do not run the remaining steps. + 1. [=Queue a task=] to [=resolve=] |p| with `undefined`. + 1. [=list/Remove=] [=interest groups=] from the user agent's [=interest group set=] whose + [=interest group/owner=] is |owner| and [=interest group/name=] is |name|. 1. Return |p|. </div> @@ -367,7 +474,7 @@ bid in the auction for the chance to display their advertisement. <xmp class="idl"> [SecureContext] partial interface Navigator { - Promise<USVString?> runAdAuction(AuctionAdConfig config); + Promise<(USVString or FencedFrameConfig)?> runAdAuction(AuctionAdConfig config); }; dictionary AuctionAdConfig { @@ -375,18 +482,21 @@ dictionary AuctionAdConfig { required USVString decisionLogicURL; USVString trustedScoringSignalsURL; sequence<USVString> interestGroupBuyers; - any auctionSignals; - any sellerSignals; - USVString directFromSellerSignals; + Promise<any> auctionSignals; + Promise<any> sellerSignals; + Promise<USVString> directFromSellerSignals; unsigned long long sellerTimeout; unsigned short sellerExperimentGroupId; - record<USVString, any> perBuyerSignals; - record<USVString, unsigned long long> perBuyerTimeouts; + USVString sellerCurrency; + Promise<record<USVString, any>> perBuyerSignals; + Promise<record<USVString, unsigned long long>> perBuyerTimeouts; record<USVString, unsigned short> perBuyerGroupLimits; record<USVString, unsigned short> perBuyerExperimentGroupIds; record<USVString, record<USVString, double>> perBuyerPrioritySignals; + Promise<record<USVString, USVString>> perBuyerCurrencies; sequence<AuctionAdConfig> componentAuctions = []; AbortSignal? signal; + Promise<boolean> resolveToConfig; }; @@ -399,122 +509,435 @@ The runAdAuction(|config|) method steps are: 1. If |global|'s [=associated Document=] is not [=allowed to use=] the "[=run-ad-auction=]" [=policy-controlled feature=], then [=exception/throw=] a "{{NotAllowedError}}" {{DOMException}}. 1. Let |auctionConfig| be the result of running [=validate and convert auction ad config=] with - |config| and [=validate and convert auction ad config/isTopLevel=] set to true. + |config| and true. 1. If |auctionConfig| is failure, then [=exception/throw=] a {{TypeError}}. 1. Let |p| be [=a new promise=]. +1. Let |configMapping| be |global|'s [=associated Document=]'s [=node navigable=]'s + [=navigable/traversable navigable=]'s [=traversable navigable/fenced frame config mapping=]. +1. Let |pendingConfig| be the result of [=constructing a pending fenced frame config=] with + |auctionConfig|. +1. Let |urn| be the result of running [=fenced frame config mapping/store a pending config=] on + |configMapping| with |pendingConfig|. +1. If |urn| is failure, then resolve |p| with null and return |p|. +1. Let |bidIgs| be a new [=list=] of [=interest groups=]. 1. If |config|["{{AuctionAdConfig/signal}}"] [=map/exists=], then: 1. Let |signal| be |config|["{{AuctionAdConfig/signal}}"]. 1. If |signal| is [=AbortSignal/aborted=], then [=reject=] |p| with |signal|'s [=AbortSignal/abort reason=] and return |p|. 1. [=AbortSignal/Add|Add the following abort steps=] to |signal|: 1. [=Reject=] |p| with |signal|’s [=AbortSignal/abort reason=]. - 1. TODO: Update bidCount for interest groups that participated in the auction. + 1. Run [=update bid counts=] with |bidIgs|. 1. Run [=interest group update=] with |auctionConfig|'s [=auction config/interest group buyers=]. 1. Let |queue| be the result of [=starting a new parallel queue=]. 1. [=parallel queue/enqueue steps|Enqueue the following steps=] to |queue|: - 1. Let |winner| be the result of running [=generate and score bids=] with |auctionConfig|, null, - |global|, and |settings|. - 1. If |winner| is null: + 1. Let |winnerInfo| be the result of running [=generate and score bids=] with |auctionConfig|, + null, |global|, |settings|, and |bidIgs|. + 1. If |winnerInfo| is failure: + 1. [=Queue a global task=] on [=DOM manipulation task source=], given |global|, to [=reject=] + |p| with a "{{TypeError}}". + 1. If |winnerInfo| is null or |winnerInfo|'s [=leading bid info/leading bid=] is null: 1. [=Queue a global task=] on [=DOM manipulation task source=], given |global|, to resolve |p| with null. 1. Otherwise: - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |global|, to resolve - |p| with |winner|'s [=generated bid/ad descriptor=]. TODO: resolve |p| with urn-uuid, instead - of a URL. + 1. Let |winner| be |winnerInfo|'s [=leading bid info/leading bid=]. + 1. Let |fencedFrameConfig| be the result of [=filling in a pending fenced frame config=] with + |pendingConfig|, |auctionConfig|, and |winnerInfo|. + 1. [=fenced frame config mapping/Finalize a pending config=] on |configMapping| with |urn| and + |fencedFrameConfig|. + 1. Wait until |auctionConfig|'s [=auction config/resolve to config=] is a boolean. + 1. Let |result| be |fencedFrameConfig|. + 1. If |auctionConfig|'s [=auction config/resolve to config=] is false: + 1. Set |result| to |urn|. + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |global|, to + resolve |p| with |result|. + 1. [=Increment ad k-anonymity count=] given |winner|'s [=generated bid/interest group=] and + |winner|'s [=generated bid/ad descriptor=]'s [=ad descriptor/url=]. + 1. If |winner|'s [=generated bid/ad component descriptors=] is not null: + 1. [=set/For each=] |adComponentDescriptor| in |winner|'s + [=generated bid/ad component descriptors=]: + 1. [=Increment component ad k-anonymity count=] given |adComponentDescriptor|'s + [=ad descriptor/url=]. + 1. [=Increment reporting ID k-anonymity count=] given |winner|'s + [=generated bid/interest group=] and |winner|'s [=generated bid/ad descriptor=]'s + [=ad descriptor/url=]. 1. Run [=interest group update=] with |auctionConfig|'s [=auction config/interest group buyers=]. - 1. TODO: Update bidCount and prevWins for interest groups that participated in the auction. + 1. Run [=update bid counts=] with |bidIgs|. + 1. Run [=update previous wins=] with |winner|. 1. Return |p|.
+
+ +To construct a pending fenced frame config given an [=auction config=] +|config|: +1. Return a [=fenced frame config=] with the following [=struct/items=]: + : [=fenced frame config/mapped url=] + :: a [=struct=] with the following [=struct/items=]: + : [=mapped url/value=] + :: `"about:blank"` + + : [=mapped url/visibility=] + :: "`opaque`" + + : [=fenced frame config/container size=] + :: TODO: fill this in once container size is spec'd to be in |config| + + : [=fenced frame config/content size=] + :: null + + : [=fenced frame config/interest group descriptor=] + :: a [=struct=] with the following [=struct/items=]: + : [=interest group descriptor/value=] + :: a [=struct=] with the following [=struct/items=]: + : [=interest group descriptor/owner=] + :: "" + + : [=interest group descriptor/name=] + :: "" + + : [=interest group descriptor/visibility=] + :: "`opaque`" + + : [=fenced frame config/on navigate callback=] + :: null + + : [=fenced frame config/effective sandbox flags=] + :: a [=struct=] with the following [=struct/items=]: + : [=effective sandbox flags/value=] + :: TODO: fill this in once fenced frame sandbox flags are more fully specified + + : [=effective sandbox flags/visibility=] + :: "`opaque`" + + : [=fenced frame config/effective enabled permissions=] + :: a [=struct=] with the following [=struct/items=]: + : [=effective enabled permissions/value=] + :: «"{{PermissionPolicy/attribution-reporting}}", + "private-aggregation" (TODO:ref), "shared-storage" (TODO:ref), + "shared-storage-select-url" (TODO:ref)» + + : [=effective enabled permissions/visibility=] + :: "`opaque`" + + : [=fenced frame config/fenced frame reporting metadata=] + :: null + + : [=fenced frame config/exfiltration budget metadata=] + :: null + + : [=fenced frame config/nested configs=] + :: a [=struct=] with the following [=struct/items=]: + : [=nested configs/value=] + :: an empty [=list=] «» + + : [=nested configs/visibility=] + :: "`opaque`" + + : [=fenced frame config/embedder shared storage context=] + :: null + +
+ +
+ +To fill in a pending fenced frame config given a [=fenced frame config=] +|pendingConfig|, [=auction config=] |auctionConfig|, and [=leading bid info=] |winningBidInfo|: +1. Let |winningBid| be |winningBidInfo|'s [=leading bid info/leading bid=]. +1. Set |pendingConfig|'s [=fenced frame config/mapped url=]'s [=mapped url/value=] to + |winningBid|'s [=generated bid/ad descriptor=]'s [=ad descriptor/url=]. +1. Let |adSize| be |winningBid|'s [=generated bid/ad descriptor=]'s [=ad descriptor/size=]. +1. If |adSize| is not null: + 1. Set |pendingConfig|'s [=fenced frame config/content size=] to a [=struct=] with the following + [=struct/items=]: + : [=content size/value=] + :: |adSize| TODO: Resolve screen-relative sizes and macros and cast this properly. + + : [=content size/visibility=] + :: "`opaque`" +1. Set |pendingConfig|'s [=fenced frame config/interest group descriptor=]'s + [=interest group descriptor/value=] to a [=struct=] with the following [=struct/items=]: + : owner + :: |winningBid|'s [=generated bid/interest group=]'s [=interest group/owner=] + + : name + :: |winningBid|'s [=generated bid/interest group=]'s [=interest group/name=] +1. Set |pendingConfig|'s [=fenced frame config/fenced frame reporting metadata=] to a [=struct=] + with the following [=struct/items=]: + : [=fenced frame reporting metadata/value=] + :: If |auctionConfig|'s [=auction config/component auctions=] is [=list/empty=] (i.e., if + there was no component auction), then a [=struct=] with the following [=struct/items=]: + : [=fenced frame reporting metadata/fenced frame reporting map=] + :: a [=map=] «[ "buyer" → «», "seller" → «»]» + + : [=fenced frame reporting metadata/direct seller is seller=] + :: true + + Otherwise (i.e., if there was a component auction), a [=struct=] with the following + [=struct/items=]: + : [=fenced frame reporting metadata/fenced frame reporting map=] + :: a [=map=] «[ "buyer" → «», "seller" → «», + "component-seller" → «»]» + + : [=fenced frame reporting metadata/direct seller is seller=] + :: false + + : [=fenced frame reporting metadata/visibility=] + :: "`opaque`" +1. Set |pendingConfig|'s [=fenced frame config/on navigate callback=] to an algorithm with these + steps: + 1. [=Asynchronously finish reporting=] with |pendingConfig|'s + [=fenced frame config/fenced frame reporting metadata=]'s + [=fenced frame reporting metadata/value=]'s + [=fenced frame reporting metadata/fenced frame reporting map=] and |winningBidInfo|. +1. Set |pendingConfig|'s [=fenced frame config/nested configs=]'s [=nested configs/value=] to + the result of running [=create nested configs=] with |winningBid|'s + [=generated bid/ad component descriptors=]. +1. Return |pendingConfig|. + +
+ +
+ +To asynchronously finish reporting given a +[=fencedframetype/fenced frame reporting map=] |reportingMap| and [=leading bid info=] +|leadingBidInfo|: +1. Let |buyerDone|, |sellerDone|, and |componentSellerDone| be [=booleans=], initially false. +1. If |leadingBidInfo|'s [=leading bid info/component seller=] is null, set |componentSellerDone| + to true. +1. [=iteration/While=]: + 1. If |buyerDone|, |sellerDone|, and |componentSellerDone| are all true, then + [=iteration/break=]. + 1. Wait until |leadingBidInfo|'s [=leading bid info/buyer reporting result=] is not null, + or |leadingBidInfo|'s [=leading bid info/seller reporting result=] is not null, or + |leadingBidInfo|'s [=leading bid info/component seller reporting result=] is not null. + 1. If |buyerDone| is false and |leadingBidInfo|'s [=leading bid info/buyer reporting result=] + is not null: + 1. Let |buyerMap| be |leadingBidInfo|'s [=leading bid info/buyer reporting result=]'s + [=reporting result/reporting beacon map=]. + 1. If |buyerMap| is null, set |buyerMap| to an empty [=map=] «[]». + 1. [=Finalize a reporting destination=] with |reportingMap|, + {{FenceReportingDestination/buyer}}, and |buyerMap|. + 1. TODO: Fetch |leadingBidInfo|'s [=leading bid info/buyer reporting result=]'s + [=reporting result/report url=]. + 1. Set |buyerDone| to true. + 1. If |sellerDone| is false and |leadingBidInfo|'s [=leading bid info/seller reporting result=] + is not null: + 1. Let |sellerMap| be |leadingBidInfo|'s [=leading bid info/seller reporting result=]'s + [=reporting result/reporting beacon map=]. + 1. If |sellerMap| is null, set |sellerMap| to an empty [=map=] «[]». + 1. [=Finalize a reporting destination=] with |reportingMap|, + {{FenceReportingDestination/seller}}, and |sellerMap|. + 1. TODO: Fetch |leadingBidInfo|'s [=leading bid info/seller reporting result=]'s + [=reporting result/report url=]. + 1. Set |sellerDone| to true. + 1. If |componentSellerDone| is false and |leadingBidInfo|'s + [=leading bid info/component seller reporting result=] is not null: + 1. Let |componentSellerMap| be |leadingBidInfo|'s + [=leading bid info/component seller reporting result=]'s + [=reporting result/reporting beacon map=]. + 1. If |componentSellerMap| is null, set |componentSellerMap| to an empty [=map=] «[]». + 1. [=Finalize a reporting destination=] with |reportingMap|, + {{FenceReportingDestination/component-seller}}, and |componentSellerMap|. + 1. TODO: Fetch |leadingBidInfo|'s [=leading bid info/component seller reporting result=]'s + [=reporting result/report url=]. + 1. Set |componentSellerDone| to true. + +
+ +
+ +To create nested configs given [=generated bid/ad component descriptors=] +|adComponentDescriptors|: +1. Let |nestedConfigs| be an empty [=list=] «». +1. If |adComponentDescriptors| is null: + 1. Return |nestedConfigs|. +1. [=set/For each=] |adComponentDescriptor| of |adComponentDescriptors|: + 1. Let |fencedFrameConfig| be a [=fenced frame config=] with the following [=struct/items=]: + : [=fenced frame config/mapped url=] + :: a [=struct=] with the following [=struct/items=]: + : [=mapped url/value=] + :: |adComponentDescriptor|'s [=ad descriptor/url=] + + : [=mapped url/visibility=] + :: "`opaque`" + + : [=fenced frame config/container size=] + :: null + + : [=fenced frame config/content size=] + :: If |adComponentDescriptor|'s [=ad descriptor/size=] is null, then null. Otherwise, a + [=struct=] with the following [=struct/items=]: + : [=content size/value=] + :: |adComponentDescriptor|'s [=ad descriptor/size=] TODO: Resolve screen-relative sizes and + macros and cast this properly. + + : [=content size/visibility=] + :: "`opaque`" + + : [=fenced frame config/interest group descriptor=] + :: null + + : [=fenced frame config/on navigate callback=] + :: null + + : [=fenced frame config/effective sandbox flags=] + :: a [=struct=] with the following [=struct/items=]: + : [=effective sandbox flags/value=] + :: TODO: fill this in once fenced frame sandbox flags are more fully specified + + : [=effective sandbox flags/visibility=] + :: "`opaque`" + + : [=fenced frame config/effective enabled permissions=] + :: a [=struct=] with the following [=struct/items=]: + : [=effective enabled permissions/value=] + :: «"{{PermissionPolicy/attribution-reporting}}", + "private-aggregation" (TODO:ref), "shared-storage" (TODO:ref), + "shared-storage-select-url" (TODO:ref)» + + : [=effective enabled permissions/visibility=] + :: "`opaque`" + + : [=fenced frame config/fenced frame reporting metadata=] + :: null + + : [=fenced frame config/exfiltration budget metadata=] + :: null + + : [=fenced frame config/nested configs=] + :: a [=struct=] with the following [=struct/items=]: + : [=nested configs/value=] + :: an empty [=list=] «» + + : [=nested configs/visibility=] + :: "`opaque`" + + : [=fenced frame config/embedder shared storage context=] + :: null + + 1. [=list/Append=] |fencedFrameConfig| to |nestedConfigs|. +1. Return |nestedConfigs|. + +
+
To validate and convert auction ad config given an {{AuctionAdConfig}} |config| and a -[=boolean=] |isTopLevel|: +[=boolean=] |isTopLevel|: 1. Let |auctionConfig| be a new [=auction config=]. -1. Let |auctionConfig|'s [=auction config/seller=] be the result of [=parsing an origin=] with - |config|["{{AuctionAdConfig/seller}}"]. -1. [=exception/Throw=] a {{TypeError}} if |auctionConfig|'s [=auction config/seller=] is an error, - or its [=url/scheme=] is not "`https`". +1. Let |seller| be the result of [=parsing an origin=] with |config|["{{AuctionAdConfig/seller}}"]. +1. If |seller| is failure, or its [=url/scheme=] is not "`https`", then [=exception/throw=] a + {{TypeError}}. +1. Set |auctionConfig|'s [=auction config/seller=] to |seller|. 1. Let |decisionLogicURL| be the result of running the [=URL parser=] on |config|["{{AuctionAdConfig/decisionLogicURL}}"]. - 1. [=exception/Throw=] a {{TypeError}} if |decisionLogicURL| is an error, or it is not - [=same origin=] with |auctionConfig|'s [=auction config/seller=]. - 1. [=Assert=]: |decisionLogicURL|'s [=url/scheme=] is "`https`". - 1. Set |auctionConfig|'s [=auction config/decision logic url=] to |decisionLogicURL|. +1. If |decisionLogicURL| is failure, or it is not [=same origin=] with |auctionConfig|'s + [=auction config/seller=], then [=exception/throw=] a {{TypeError}}. +1. [=Assert=]: |decisionLogicURL|'s [=url/scheme=] is "`https`". +1. Set |auctionConfig|'s [=auction config/decision logic url=] to |decisionLogicURL|. 1. If |config|["{{AuctionAdConfig/trustedScoringSignalsURL}}"] [=map/exists=]: 1. Let |trustedScoringSignalsURL| be the result of running the [=URL parser=] on |config|["{{AuctionAdConfig/trustedScoringSignalsURL}}"]. - 1. [=exception/Throw=] a {{TypeError}} if |trustedScoringSignalsURL| is an error, - or it is not [=same origin=] with |auctionConfig|'s [=auction config/seller=]. + 1. If |trustedScoringSignalsURL| is failure, or it is not [=same origin=] with |auctionConfig|'s + [=auction config/seller=], then [=exception/throw=] a {{TypeError}}. 1. [=Assert=]: |trustedScoringSignalsURL|'s [=url/scheme=] is "`https`". 1. Set |auctionConfig|'s [=auction config/trusted scoring signals url=] to |trustedScoringSignalsURL|. -1. If |config|["{{AuctionAdConfig/interestGroupBuyers}}"] [=map/exists=], let |buyers| be a new - [=list/is empty|empty=] [=list=]. +1. If |config|["{{AuctionAdConfig/interestGroupBuyers}}"] [=map/exists=]: + 1. Let |buyers| be a new empty [=list=]. 1. [=list/For each=] |buyerString| in |config|["{{AuctionAdConfig/interestGroupBuyers}}"]: - 1. Let |buyer| be the result of [=parsing an origin=] with |buyerString|. If |buyer| is an - error, or |buyer|'s [=url/scheme=] is not "`https`", then [=exception/throw=] a {{TypeError}}. - Otherwise, [=list/append=] |buyer| to |buyers|. + 1. Let |buyer| be the result of [=parsing an origin=] with |buyerString|. + 1. If |buyer| is failure, or |buyer|'s [=url/scheme=] is not "`https`", then [=exception/throw=] + a {{TypeError}}. + 1. [=list/Append=] |buyer| to |buyers|. 1. Set |auctionConfig|'s [=auction config/interest group buyers=] to |buyers|. 1. If |config|["{{AuctionAdConfig/auctionSignals}}"] [=map/exists=]: - 1. If |config|["{{AuctionAdConfig/auctionSignals}}"] is a {{Promise}}, let |auctionConfig|'s - [=auction config/auction signals=] be |config|["{{AuctionAdConfig/auctionSignals}}"]. - 1. Otherwise, let |auctionConfig|'s [=auction config/auction signals=] be the result of - [=serializing a JavaScript value to a JSON string=], given + 1. Set |auctionConfig|'s [=auction config/auction signals=] to |config|["{{AuctionAdConfig/auctionSignals}}"]. -1. If |config|["{{AuctionAdConfig/sellerSignals}}"] [=map/exists=], let |auctionConfig|'s - [=auction config/seller signals=] of be the result of - [=serializing a JavaScript value to a JSON string=], given - |config|["{{AuctionAdConfig/sellerSignals}}"]. -1. If |config|["{{AuctionAdConfig/directFromSellerSignals}}"] [=map/exists=], let - |directFromSellerSignalsPrefix| be the result of running the [=URL parser=] on - |config|["{{AuctionAdConfig/directFromSellerSignals}}"]. - 1. [=exception/Throw=] a {{TypeError}} if any of the following conditions hold: - * |directFromSellerSignalsPrefix| is an error; - * |directFromSellerSignalsPrefix| is not [=same origin=] with |auctionConfig|'s - [=auction config/seller=]; - * |directFromSellerSignalsPrefix|'s [=url/query=] is not null. - 1. [=Assert=]: |directFromSellerSignalsPrefix|'s [=url/scheme=] is "`https`". + 1. [=Handle an input promise in configuration=] given |auctionConfig| and |auctionConfig|'s [=auction config/auction signals=]: + * To parse the value |result|: + 1. Let |auctionSignalsJSON| be the result of + [=serializing a JavaScript value to a JSON string=], given |result|. + 1. Set |auctionConfig|'s [=auction config/auction signals=] to |auctionSignalsJSON|. + 1. Set |auctionConfig|'s [=auction config/config idl=]["{{AuctionAdConfig/auctionSignals}}"] to |result|. + * To handle an error: + 1. Set |auctionConfig|'s [=auction config/auction signals=] to failure. + 1. Set |auctionConfig|'s [=auction config/config idl=]["{{AuctionAdConfig/auctionSignals}}"] to {{undefined}}. +1. If |config|["{{AuctionAdConfig/sellerSignals}}"] [=map/exists=]: + 1. Set |auctionConfig|'s [=auction config/seller signals=] to + |config|["{{AuctionAdConfig/sellerSignals}}"]. + 1. [=Handle an input promise in configuration=] given |auctionConfig| and |auctionConfig|'s [=auction config/seller signals=]: + * To parse the value |result|: + 1. Set |auctionConfig|'s [=auction config/seller signals=] to the result of + [=serializing a JavaScript value to a JSON string=], given |result|. + * To handle an error, set |auctionConfig|'s [=auction config/seller signals=] to failure. +1. If |config|["{{AuctionAdConfig/directFromSellerSignals}}"] [=map/exists=]: + 1. TODO: The receiving end of this isn't specified yet, so there is no place to put the computed value. + 1. [=Handle an input promise in configuration=] given |auctionConfig| and |config|["{{AuctionAdConfig/directFromSellerSignals}}"]: + * To parse the value |result|: + 1. Let |directFromSellerSignalsPrefix| be the result of running the [=URL parser=] on |result|. + 1. [=exception/Throw=] a {{TypeError}} if any of the following conditions hold: + * |directFromSellerSignalsPrefix| is failure; + * |directFromSellerSignalsPrefix| is not [=same origin=] with |auctionConfig|'s + [=auction config/seller=]; + * |directFromSellerSignalsPrefix|'s [=url/query=] is not null. + 1. [=Assert=]: |directFromSellerSignalsPrefix|'s [=url/scheme=] is "`https`". + * To handle an error: + 1. TODO: set the rep in |auctionConfig| to failure. 1. If |config|["{{AuctionAdConfig/sellerTimeout}}"] [=map/exists=], set |auctionConfig|'s - [=auction config/seller timeout=] to min(|config|["{{AuctionAdConfig/sellerTimeout}}"], 500) - milliseconds. + [=auction config/seller timeout=] to |config|["{{AuctionAdConfig/sellerTimeout}}"] in milliseconds + or 500 milliseconds, whichever is smaller. 1. If |config|["{{AuctionAdConfig/sellerExperimentGroupId}}"] [=map/exists=]: 1. Set |auctionConfig|'s [=auction config/seller experiment group id=] to |config|["{{AuctionAdConfig/sellerExperimentGroupId}}"]. -1. If |config|["{{AuctionAdConfig/perBuyerSignals}}"] [=map/exists=], [=map/for each=] |key| → - |value| of |config|["{{AuctionAdConfig/perBuyerSignals}}"]: - 1. Let |buyer| be the result of [=parsing an origin=] with |key|. If |buyer| is an error, - [=exception/throw=] a {{TypeError}}. - 1. Let |signalsString| be the result of [=serializing a JavaScript value to a JSON string=], given - |value|. - 1. [=map/Set=] |auctionConfig|'s [=auction config/per buyer signals=][|buyer|] to - |signalsString|. -1. If |config|["{{AuctionAdConfig/perBuyerTimeouts}}"] [=map/exists=], [=map/for each=] |key| → - |value| of |config|["{{AuctionAdConfig/perBuyerTimeouts}}"]: - 1. If |key| equals to "*", then set |auctionConfig|'s [=auction config/all buyers timeout=] - to min(|value|, 500) milliseconds, and [=iteration/continue=]. - 1. Let |buyer| the result of [=parsing an origin=] with |key|. If |buyer| is an error, - [=exception/throw=] a {{TypeError}}. - 1. [=map/Set=] |auctionConfig|'s [=auction config/per buyer timeouts=][|buyer|] to - min(|value|, 500) milliseconds. +1. If |config|["{{AuctionAdConfig/sellerCurrency}}"] [=map/exists=]: + 1. If the result of [=checking whether a string is a valid currency tag=] on + |config|["{{AuctionAdConfig/sellerCurrency}}"] is false, [=exception/throw=] a {{TypeError}}. + 1. Set |auctionConfig|'s [=auction config/seller currency=] to |config|["{{AuctionAdConfig/sellerCurrency}}"] +1. If |config|["{{AuctionAdConfig/perBuyerSignals}}"] [=map/exists=]: + 1. Set |auctionConfig|'s [=auction config/per buyer signals=] to + |config|["{{AuctionAdConfig/perBuyerSignals}}"]. + 1. [=Handle an input promise in configuration=] given |auctionConfig| and |auctionConfig|'s [=auction config/per buyer signals=]: + * To parse the value |result|: + 1. Set |auctionConfig|'s [=auction config/per buyer signals=] to a new [=ordered map=] whose + [=map/keys=] are [=origins=] and whose [=map/values=] are [=strings=]. + 1. [=map/For each=] |key| → |value| of |result|: + 1. Let |buyer| be the result of [=parsing an origin=] with |key|. If |buyer| is failure, throw a {{TypeError}}. + 1. Let |signalsString| be the result of [=serializing a JavaScript value to a JSON string=], given |value|. + 1. [=map/Set=] |auctionConfig|'s [=auction config/per buyer signals=][|buyer|] to |signalsString|. + * To handle an error, set |auctionConfig|'s [=auction config/per buyer signals=] to failure. +1. If |config|["{{AuctionAdConfig/perBuyerTimeouts}}"] [=map/exists=]: + 1. Set |auctionConfig|'s [=auction config/per buyer timeouts=] to + |config|["{{AuctionAdConfig/perBuyerTimeouts}}"]. + 1. [=Handle an input promise in configuration=] given |auctionConfig| and |auctionConfig|'s [=auction config/per buyer timeouts=]: + * To parse the value |result|: + 1. Set |auctionConfig|'s [=auction config/per buyer timeouts=] to a new [=ordered map=] whose + [=map/keys=] are [=origins=] and whose [=map/values=] are [=durations=] in milliseconds. + 1. [=map/For each=] |key| → |value| of |result|: + 1. If |key| is "*", then set |auctionConfig|'s [=auction config/all buyers timeout=] + to |value| in milliseconds or 500 milliseconds, whichever is smaller, and [=iteration/continue=]. + 1. Let |buyer| be the result of [=parsing an origin=] with |key|. If |buyer| is failure, + [=exception/throw=] a {{TypeError}}. + 1. [=map/Set=] |auctionConfig|'s [=auction config/per buyer timeouts=][|buyer|] to + |value| in milliseconds or 500 milliseconds, whichever is smaller. + * To handle an error, set |auctionConfig|'s [=auction config/per buyer timeouts=] to failure. 1. If |config|["{{AuctionAdConfig/perBuyerGroupLimits}}"] [=map/exists=], [=map/for each=] |key| → |value| of |config|["{{AuctionAdConfig/perBuyerGroupLimits}}"]: 1. If |value| is 0, [=exception/throw=] a {{TypeError}}. - 1. If |key| equals to "*", then set |auctionConfig|'s [=auction config/all buyers group limit=] + 1. If |key| is "*", then set |auctionConfig|'s [=auction config/all buyers group limit=] to |value|, and [=iteration/continue=]. - 1. Let |buyer| be the result of [=parsing an origin=] with |key|. If |buyer| is an error, + 1. Let |buyer| be the result of [=parsing an origin=] with |key|. If |buyer| is failure, [=exception/throw=] a {{TypeError}}. - 1. [=map/Set=] |auctionConfig|'s [=auction config/per buyer group limits=][|buyer|] to |value|. + 1. Set |auctionConfig|'s [=auction config/per buyer group limits=][|buyer|] to |value|. 1. If |config|["{{AuctionAdConfig/perBuyerExperimentGroupIds}}"] [=map/exists=], [=map/for each=] |key| → |value| of |config|["{{AuctionAdConfig/perBuyerExperimentGroupIds}}"]: - 1. If |key| equals to "*", then set |auctionConfig|'s + 1. If |key| is "*", then set |auctionConfig|'s [=auction config/all buyer experiment group id=] to |value|, and [=iteration/continue=]. - 1. Let |buyer| the result of [=parsing an origin=] with |key|. If |buyer| is an error, + 1. Let |buyer| the result of [=parsing an origin=] with |key|. If |buyer| is failure, [=exception/throw=] a {{TypeError}}. - 1. [=map/Set=] |auctionConfig|'s [=auction config/per buyer experiment group ids=][|buyer|] to - |value|. + 1. Set |auctionConfig|'s [=auction config/per buyer experiment group ids=][|buyer|] to |value|. 1. If |config|["{{AuctionAdConfig/perBuyerPrioritySignals}}"] [=map/exists=], [=map/for each=] |key| → |value| of |config|["{{AuctionAdConfig/perBuyerPrioritySignals}}"]: 1. Let |signals| be an [=ordered map=] whose [=map/keys=] are [=strings=] and whose [=map/values=] @@ -522,17 +945,39 @@ To validate and convert auction ad config given an {{AuctionAdConfig} 1. [=map/for each=] |k| → |v| of |value|: 1. If |k| [=string/starts with=] "browserSignals.", [=exception/throw=] a {{TypeError}}. 1. [=map/Set=] |signals|[|k|] to |v|. - 1. If |key| equals to "*", then set |auctionConfig|'s + 1. If |key| is "*", then set |auctionConfig|'s [=auction config/all buyers priority signals=] to |value|, and [=iteration/continue=]. 1. Let |buyer| be the result of [=parsing an origin=] with |key|. If it fails, [=exception/throw=] a {{TypeError}}. 1. [=map/Set=] |auctionConfig|'s [=auction config/per buyer priority signals=][|buyer|] to |signals|. +1. If |config|["{{AuctionAdConfig/perBuyerCurrencies}}"] [=map/exists=]: + 1. Set |auctionConfig|'s [=auction config/per buyer currencies=] to |config|["{{AuctionAdConfig/perBuyerCurrencies}}"] + 1. [=Handle an input promise in configuration=] given |auctionConfig| and |auctionConfig|'s [=auction config/per buyer currencies=]: + * To parse the value |result|: + 1. Set |auctionConfig|'s [=auction config/per buyer currencies=] to a new [=ordered map=] whose [=map/keys=] are [=origins=] and whose [=map/values=] are [=currency tags=]. + 1. [=map/for each=] |key| → |value| of |result|: + 1. If the result of [=checking whether a string is a valid currency tag=] given |value| is false, + [=exception/throw=] a {{TypeError}}. + 1. If |key| is "*", then set |auctionConfig|'s + [=auction config/all buyers currency=] to |value|, and [=iteration/continue=]. + 1. Let |buyer| be the result of [=parsing an origin=] with |key|. If |buyer| is failure, + [=exception/throw=] a {{TypeError}}. + 1. [=map/Set=] |auctionConfig|'s [=auction config/per buyer currencies=][|buyer|] to |value|. + * To handle an error, set |auctionConfig|'s [=auction config/per buyer currencies=] to failure. 1. [=list/For each=] |component| in |config|["{{AuctionAdConfig/componentAuctions}}"]: 1. If |isTopLevel| is false, [=exception/throw=] a {{TypeError}}. 1. Let |componentAuction| be the result of running [=validate and convert auction ad config=] with - |component| and [=validate and convert auction ad config/isTopLevel=] set to false. + |component| and false. 1. [=list/Append=] |componentAuction| to |auctionConfig|'s [=auction config/component auctions=]. +1. Set |auctionConfig|'s [=auction config/config idl=] to |config|. +1. If |config|["{{AuctionAdConfig/resolveToConfig}}"] [=map/exists=]: + 1. Let |auctionConfig|'s [=auction config/resolve to config=] be + |config|["{{AuctionAdConfig/resolveToConfig}}"]. + 1. TODO: What should happen if this rejects? + 1. [=Upon fulfillment=] of |auctionConfig|'s [=auction config/resolve to config=] with + |resolveToConfig|, set |auctionConfig|'s [=auction config/resolve to config=] to + |resolveToConfig|. 1. Return |auctionConfig|.
@@ -541,11 +986,46 @@ To validate and convert auction ad config given an {{AuctionAdConfig} To parse an origin given a [=string=] |input|: 1. Let |url| be the result of running the [=URL parser=] on |input|. -1. If |url| is an error, then return failure. +1. If |url| is failure, then return failure. 1. Return |url|'s [=url/origin=].
+
+ + To update bid count given a [=list=] of [=interest group=]s |igs|: + 1. [=list/For each=] |ig| in |igs|: + 1. Let |loadedIg| be the [=interest group=] from the user agent’s [=interest group set=] + whose [=interest group/owner=] is |ig|'s [=interest group/owner=] and whose + [=interest group/name=] is |ig|'s [=interest group/name=], [=iteration/continue=] if none found. + 1. If the most recent entry in |loadedIg|'s [=interest group/bid counts=] corresponds to + the current day in UTC, increment its count. If not, [=list/insert=] a new [=tuple=] of + the time set to the current UTC day and a count of 1. + 1. [=list/Replace=] the [=interest group=] that has |loadedIg|'s [=interest group/owner=] and + [=interest group/name=] in the browser’s [=interest group set=] with |loadedIg|. + +
+ +
+ + To update previous wins given a [=generated bid=] |bid|: + 1. Let |ig| be |bid|'s [=generated bid/interest group=]. + 1. Let |loadedIg| be the [=interest group=] from the user agent’s [=interest group set=] + whose [=interest group/owner=] is |ig|'s [=interest group/owner=] and whose + [=interest group/name=] is |ig|'s [=interest group/name=], return if none found. + 1. Let |win| be a new [=previous win=]. + 1. Set |win|'s [=previous win/time=] to the [=current wall time=]. + 1. Let |ad| be the [=ad descriptor=] from |ig|'s [=interest group/ads=] whose + [=ad descriptor/url=] is |bid|'s [=generated bid/ad descriptor=] + [=ad descriptor/url=], return if none found. + 1. Set |win|'s [=previous win/ad json=] to the result of + [=serializing an Infra value to a JSON string=] given |ad|. + 1. [=list/Append=] |win| to |loadedIg|'s [=interest group/previous wins=]. + 1. [=list/Replace=] the [=interest group=] that has |loadedIg|'s [=interest group/owner=] and + [=interest group/name=] in the browser’s [=interest group set=] with |loadedIg|. + +
+
To build bid generators map given an [=auction config=] |auctionConfig|: @@ -580,12 +1060,55 @@ To build bid generators map given an [=auction config=] |auctionConfi
+
+ +To generate a bid given an [=ordered map=] |allTrustedBiddingSignals|, a +[=string=] |auctionSignals|, a {{BiddingBrowserSignals}} |browserSignals|, a [=string=] +|perBuyerSignals|, a [=duration=] |perBuyerTimeout| in milliseconds, a [=currency tag=] |expectedCurrency|, +an [=interest group=] |ig|, and a [=moment=] |auctionStartTime|: + 1. Let |igGenerateBid| be the result of [=building an interest group passed to generateBid=] + with |ig|. + 1. Set |browserSignals|["{{BiddingBrowserSignals/joinCount}}"] to the sum of |ig|'s + [=interest group/join counts=] for all days within the last 30 days. + 1. [=map/Set=] |browserSignals|["{{BiddingBrowserSignals/bidCount}}"] to the sum of |ig|'s + [=interest group/bid counts=] for all days within the last 30 days. + 1. Let |prevWins| be a new [=sequence=]<{{PreviousWin}}>. + 1. [=list/For each=] |prevWin| of |ig|'s [=interest group/previous wins=] for all days within the + the last 30 days: + 1. Let |timeDelta| be |auctionStartTime| minus |prevWin|'s [=previous win/time=]. + 1. Set |timeDelta| to 0 if |timeDelta| is negative, |timeDelta|'s nearest second (rounding down) + otherwise. + 1. Let |prevWinIDL| be a new {{PreviousWin}}. + 1. [=map/Set=] |prevWinIDL|["{{PreviousWin/timeDelta}}"] to |timeDelta|. + 1. [=map/Set=] |prevWinIDL|["{{PreviousWin/adJSON}}"] to |prevWin|'s [=previous win/ad json=]. + 1. [=list/Append=] |prevWinIDL| to |prevWins|. + 1. [=map/Set=] |browserSignals|["{{BiddingBrowserSignals/prevWinsMs}}"] to |prevWins|. + 1. Let |biddingScript| be the result of [=fetching script=] with |ig|'s + [=interest group/bidding url=]. + 1. If |biddingScript| is failure, return failure. + 1. If |ig|'s [=interest group/bidding wasm helper url=] is not null: + 1. Let |wasmModuleObject| be the result of [=fetching WebAssembly=] with |ig|'s + [=interest group/bidding wasm helper url=]. + 1. If |wasmModuleObject| is not failure: + 1. [=map/Set=] |browserSignals|["{{BiddingBrowserSignals/wasmHelper}}"] to |wasmModuleObject|. + 1. Let |trustedBiddingSignals| be an [=ordered map=] whose [=map/keys=] are [=strings=] and + whose [=map/values=] are {{any}}. + 1. [=list/For each=] |key| of |ig|'s [=interest group/trusted bidding signals keys=]: + 1. If |allTrustedBiddingSignals| is an [=ordered map=] and |allTrustedBiddingSignals|[|key|] + [=map/exists=], then [=map/set=] |trustedBiddingSignals|[|key|] to + |allTrustedBiddingSignals|[|key|]. + 1. Return the result of [=evaluating a bidding script=] with |biddingScript|, |ig|, |expectedCurrency|, + |igGenerateBid|, |auctionSignals|, |perBuyerSignals|, |trustedBiddingSignals|, |browserSignals|, + and |perBuyerTimeout|. +
+
To generate and score bids given an [=auction config=] |auctionConfig|, an -[=auction config=]-or-null |topLevelAuctionConfig|, a [=global object=] |global|, and an -[=environment settings object=] |settings|: +[=auction config=]-or-null |topLevelAuctionConfig|, a [=global object=] |global|, an +[=environment settings object=] |settings|, and a [=list=] of [=interest groups=] |bidIgs|: 1. [=Assert=] that these steps are running [=in parallel=]. +1. Let |auctionStartTime| be the [=current wall time=]. 1. Let |decisionLogicScript| be the result of [=fetching script=] with |auctionConfig|'s [=auction config/decision logic url=]. 1. If |decisionLogicScript| is failure, return null. @@ -599,10 +1122,12 @@ To generate and score bids given an [=auction config=] |auctionConfig 1. [=list/For each=] |component| in |auctionConfig|'s [=auction config/component auctions=], [=parallel queue/enqueue steps|enqueue the following steps=] to |queue|: 1. Let |compWinner| be the result of running [=generate and score bids=] with |component|, - |auctionConfig|, and |global|. + |auctionConfig|, |global|, and |settings|. + 1. If |compWinner| is failure, return failure. + 1. If [=recursively wait until configuration input promises resolve=] given |auctionConfig| returns failure, return failure. 1. If |compWinner| is not null: 1. Run [=score and rank a bid=] with |auctionConfig|, |compWinner|, |leadingBidInfo|, - |decisionLogicScript|, null, true and |settings|'s [=environment/top-level origin=]. + |decisionLogicScript|, null, "top-level-auction", null, and |settings|'s [=environment/top-level origin=]. 1. Decrement |pendingComponentAuctions| by 1. 1. Wait until |pendingComponentAuctions| is 0. @@ -612,7 +1137,7 @@ To generate and score bids given an [=auction config=] |auctionConfig 1. Set |leadingBidInfo|'s [=leading bid info/component seller=] to |winningComponentConfig|'s [=auction config/seller=]. 1. Let « |topLevelSellerSignals|, unusedTopLevelReportResultBrowserSignals » be the result of - running [=report result=] with |leadingBidInfo|. + running [=report result=] with |leadingBidInfo| and |winningComponentConfig|. 1. Set |leadingBidInfo|'s [=leading bid info/auction config=] to |winningComponentConfig|. 1. Set |leadingBidInfo|'s [=leading bid info/component seller=] to null. 1. Set |leadingBidInfo|'s [=leading bid info/top level seller=] to |auctionConfig|'s @@ -620,30 +1145,31 @@ To generate and score bids given an [=auction config=] |auctionConfig 1. Set |leadingBidInfo|'s [=leading bid info/top level seller signals=] to |topLevelSellerSignals|. 1. Let « |sellerSignals|, |reportResultBrowserSignals| » be the result of running - [=report result=] with |leadingBidInfo|. + [=report result=] with |leadingBidInfo| and null. 1. Run [=report win=] with |leadingBidInfo|, |sellerSignals|, and |reportResultBrowserSignals|. 1. Return |leadingBidInfo|'s [=leading bid info/leading bid=]. +1. If [=waiting until configuration input promises resolve=] given |auctionConfig| returns failure, return failure. 1. Let |allBuyersExperimentGroupId| be |auctionConfig|'s [=auction config/all buyer experiment group id=]. 1. Let |allBuyersGroupLimit| be |auctionConfig|'s - [=auction config/all buyers group limit=]. + [=auction config/all buyers group limit=]. 1. Let |auctionSignals| be |auctionConfig|'s [=auction config/auction signals=]. -1. If |auctionSignals| is a {{Promise}}: - 1. [=Upon fulfillment=] of |auctionSignals| with |result| set |auctionSignals| to the result - - of [=serializing a JavaScript value to a JSON string=], given |result|. -1. Let |browserSignals| be an [=ordered map=] whose [=map/keys=] are [=strings=] and whose - [=map/values=] are {{any}}. -1. [=map/Set=] |browserSignals|["`topWindowHostname`"] to [=this=]'s [=relevant settings object=]'s - [=environment/top-level origin=]'s [=origin/host=]. -1. [=map/Set=] |browserSignals|["seller"] to |auctionConfig|'s [=auction config/seller=]. -1. Let |isComponentAuction| be false. +1. Let |browserSignals| be a {{BiddingBrowserSignals}}. +1. Let |topLevelHost| be the result of running the host serializer on [=this=]'s + [=relevant settings object=]'s [=environment/top-level origin=]'s [=origin/host=]. +1. [=map/Set=] |browserSignals|["{{BiddingBrowserSignals/topWindowHostname}}"] to |topLevelHost|. +1. [=map/Set=] |browserSignals|["{{BiddingBrowserSignals/seller}}"] to the [=serialization of an + origin|serialization=] of |auctionConfig|'s [=auction config/seller=]. +1. Let |auctionLevel| be "single-level-auction". +1. Let |componentAuctionExpectedCurrency| be null. 1. If |topLevelAuctionConfig| is not null: - 1. [=map/Set=] |browserSignals|["topLevelSeller"] to the + 1. [=map/Set=] |browserSignals|["{{BiddingBrowserSignals/topLevelSeller}}"]] to the [=serialization of an origin|serialization=] of |topLevelAuctionConfig|'s [=auction config/seller=]. - 1. Set |isComponentAuction| to true. + 1. Set |auctionLevel| to "component-auction". + 1. Set |componentAuctionExpectedCurrency| to the result of [=looking up per-buyer currency=] with + |topLevelAuctionConfig| and |auctionConfig|'s [=auction config/seller=]. 1. Let |pendingBuyers| be the [=map/size=] of |bidGenerators|. 1. [=map/For each=] |buyer| → |perBuyerGenerator| of |bidGenerators|, [=parallel queue/enqueue steps|enqueue the following steps=] to |queue|: @@ -673,6 +1199,11 @@ To generate and score bids given an [=auction config=] |auctionConfig 1. If |auctionConfig|'s [=auction config/per buyer signals=] is not null and [=auction config/per buyer signals=][|buyer|] [=map/exists=]: 1. Set |perBuyerSignals| to |auctionConfig|'s [=auction config/per buyer signals=][|buyer|]. + 1. Let |perBuyerTimeout| be |auctionConfig|'s [=auction config/all buyers timeout=]. + 1. If |auctionConfig|'s [=auction config/per buyer timeouts=] is not null and + [=auction config/per buyer timeouts=][|buyer|] [=map/exists=]: + 1. Set |perBuyerTimeout| to |auctionConfig|'s [=auction config/per buyer timeouts=][|buyer|]. + 1. Let |expectedCurrency| be the result of [=looking up per-buyer currency=] with |auctionConfig| and |buyer|. 1. [=map/For each=] |signalsUrl| → |perSignalsUrlGenerator| of |perBuyerGenerator|: 1. Let |keys| be a new [=ordered set=]. 1. Let |igNames| be a new [=ordered set=]. @@ -684,54 +1215,51 @@ To generate and score bids given an [=auction config=] |auctionConfig |signalsUrl|, |keys|, |igNames|, |buyerExperimentGroupId|. 1. Let « |allTrustedBiddingSignals|, |dataVersion| » be the result of [=fetching trusted signals=] with |biddingSignalsUrl| and true. + 1. If |dataVersion| is not null: + 1. [=map/Set=] |browserSignals|["{{BiddingBrowserSignals/dataVersion}}"] to |dataVersion|. 1. [=map/For each=] joiningOrigin → |groups| of |perSignalsUrlGenerator|: 1. [=list/For each=] |ig| of |groups|: - 1. TODO: If rerunning `generateBid()` with only k-anonymous ads, remember to swap out |ig|'s ads - to just k-anonymous ones so that [=validating an ad url=] excludes the non-k-anonymous ones. - 1. TODO: Let |interestGroup| be ... from |ig| ... minus priority and prioritySignalsOverrides and any browser-defined pieces - 1. [=map/Set=] |browserSignals|["`joinCount`"] to the sum of |ig|'s - [=interest group/join counts=] for all days within the last 30 days. - 1. [=map/Set=] |browserSignals|["`bidCount`"] to the sum of |ig|'s - [=interest group/bid counts=] for all days within the last 30 days. - 1. [=map/Set=] |browserSignals|["`prevWins`"] to a [=list=] containing [=tuples=] of the - time and the corresponding winning [=interest group ad=] from |ig|'s - [=interest group/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. - 1. If |dataVersion| is not null: - 1. [=map/Set=] |browserSignals|["`dataVersion`"] to |dataVersion|. - 1. Let |biddingScript| be the result of [=fetching script=] with |ig|'s - [=interest group/bidding url=]. - 1. If |biddingScript| is failure, [=iteration/continue=]. - 1. If |ig|'s [=interest group/bidding wasm helper url=] is not null: - 1. Let |wasmModuleObject| be the result of [=fetching WebAssembly=] with |ig|'s - [=interest group/bidding wasm helper url=]. - 1. If |wasmModuleObject| is not failure: - 1. [=map/Set=] |browserSignals|["`wasmHelper`"] to |wasmModuleObject|. - 1. Let |trustedBiddingSignals| be an [=ordered map=] whose [=map/keys=] are [=strings=] and - whose [=map/values=] are {{any}}. - 1. [=list/For each=] |key| of |ig|'s [=interest group/trusted bidding signals keys=]: - 1. If |allTrustedBiddingSignals| is an [=ordered map=] and |allTrustedBiddingSignals|[|key|] - [=map/exists=], then [=map/set=] |trustedBiddingSignals|[|key|] to - |allTrustedBiddingSignals|[|key|]. - 1. Let |startTime| be the current time. - 1. Let |generatedBidResult| be the result of [=evaluating a bidding script=] with - |biddingScript|, |ig|, and « |interestGroup|, |auctionSignals|, |perBuyerSignals|, - |trustedBiddingSignals|, |browserSignals| ». - 1. Let |duration| be the current time minus |startTime| in milliseconds. - 1. If |generatedBidResult| is an [=abrupt completion=], [=iteration/continue=]. - 1. Let |generatedBidIDL| be the result of [=converted to an IDL value|converting=] - |generatedBidResult| to a {{GenerateBidOutput}}. - 1. If an exception was caught in the previous step, [=iteration/continue=]. - 1. Let |groupHasAdComponents| be true. - 1. If |interestGroup|'s [=interest group/ad components=] is null: - 1. Set |groupHasAdComponents| be false. - 1. Let |generatedBid| be the result of [=converting GenerateBidOutput to generated bid=] - with |generatedBidIDL|, |ig|, |isComponentAuction|, and |groupHasAdComponents|. - 1. Set |generatedBid|'s [=generated bid/bid duration=] to |duration|. + 1. If |ig|'s [=interest group/bidding url=] is null, [=iteration/continue=]. + 1. Let |generatedBid| be the result of [=generate a bid=] given + |allTrustedBiddingSignals|, |auctionSignals|, a [=map/clone=] of |browserSignals|, + |perBuyerSignals|, |perBuyerTimeout|, |expectedCurrency|, |ig|, and |auctionStartTime|. 1. If |generatedBid| is failure, [=iteration/continue=]. - 1. Set |generatedBid|'s [=generated bid/interest group=] to |ig|. + 1. If [=query generated bid k-anonymity count=] given |generatedBid| returns false: + + Note: [=Generate a bid=] is now rerun with only k-anonymous [=interest group/ads=] to give + the buyer a chance to [=generate a bid=] for k-anonymous [=interest group/ads=]. Allowing + the buyer to first [=generate a bid=] for non-k-anonymous [=interest group/ads=] provides a + mechanism to bootstrap the k-anonymity count, otherwise no [=interest group/ads=] would + ever trigger [=increment k-anonymity count=] and all ads would fail + [=query k-anonymity count=]. + 1. TODO: Run [=score and rank a bid=] on |generatedBid| to find the highest scoring bid + that isn't k-anonymous. After the auction, if the highest scoring bid that isn't + k-anonymous has a higher score than the highest scoring k-anonymous bid, then call + [=increment ad k-anonymity count=] on it. + 1. Let |originalAds| be |ig|'s [=interest group/ads=]. + 1. If |originalAds| is not null: + 1. Set |ig|'s [=interest group/ads=] to a new [=list=] of [=interest group ad=]. + 1. [=list/For each=] |ad| in |originalAds|: + 1. If [=query ad k-anonymity count=] given |ig| and |ad|'s + [=interest group ad/render url=] returns true, [=list/append=] |ad| to |ig|'s + [=interest group/ads=]. + 1. Let |originalAdComponents| be |ig|'s [=interest group/ad components=]. + 1. If |originalAdComponents| is not null: + 1. Set |ig|'s [=interest group/ad components=] to a new [=list=] of [=interest group ad=]. + 1. [=list/For each=] |adComponent| in |originalAdComponents|: + 1. If [=query component ad k-anonymity count=] given |adComponent|'s + [=interest group ad/render url=] returns true, [=list/append=] |adComponent| to |ig|'s + [=interest group/ad components=]. + 1. Set |generatedBid| to the result of [=generate a bid=] given + |allTrustedBiddingSignals|, |auctionSignals|, a [=map/clone=] of |browserSignals|, + |perBuyerSignals|, |perBuyerTimeout|, |expectedCurrency|, and |ig|. + + 1. Set |ig|'s [=interest group/ads=] to |originalAds|. + 1. Set |ig|'s [=interest group/ad components=] to |originalAdComponents|. + 1. If |generatedBid| is failure, [=iteration/continue=]. + 1. [=list/Insert=] |generatedBid|'s [=generated bid/interest group=] in |bidIgs|. 1. [=Score and rank a bid=] with |auctionConfig|, |generatedBid|, |leadingBidInfo|, - |decisionLogicScript|, |dataVersion|, |isComponentAuction|, and |settings|'s + |decisionLogicScript|, |dataVersion|, |auctionLevel|, |componentAuctionExpectedCurrency|, and |settings|'s [=environment/top-level origin=]. 1. Decrement |pendingBuyers| by 1. @@ -739,81 +1267,173 @@ To generate and score bids given an [=auction config=] |auctionConfig 1. If |leadingBidInfo|'s [=leading bid info/leading bid=] is null, return null. 1. If |topLevelAuctionConfig| is null: 1. Let « |sellerSignals|, |reportResultBrowserSignals| » be the result of running - [=report result=] with |leadingBidInfo|. + [=report result=] with |leadingBidInfo| and null. 1. Run [=report win=] with |leadingBidInfo|, |sellerSignals|, and |reportResultBrowserSignals|. 1. Return |leadingBidInfo|'s [=leading bid info/leading bid=].
+
+To build an interest group passed to generateBid given an [=interest group=] |ig|: + + 1. Let |igGenerateBid| be a new {{GenerateBidInterestGroup}} with the following fields: +
+
{{GenerateBidInterestGroup/owner}} +
The [=serialization of an origin|serialization=] of |ig|'s [=interest group/owner=] +
{{GenerateBidInterestGroup/name}} +
|ig|'s [=interest group/name=] +
{{GenerateBidInterestGroup/enableBiddingSignalsPrioritization}} +
|ig|'s [=interest group/enable bidding signals prioritization=] +
{{GenerateBidInterestGroup/priorityVector}} +
|ig|'s [=interest group/priority vector=] if not null, otherwise {{undefined}} +
{{GenerateBidInterestGroup/executionMode}} +
|ig|'s [=interest group/execution mode=] +
{{GenerateBidInterestGroup/biddingLogicURL}} +
The [=serialize a URL|serialization-or-undefined=] of |ig|'s [=interest group/bidding url=] +
{{GenerateBidInterestGroup/biddingWasmHelperURL}} +
The [=serialize a URL|serialization=] of |ig|'s [=interest group/bidding wasm helper url=] +
{{GenerateBidInterestGroup/updateURL}} +
The [=serialize a URL|serialization=] of |ig|'s [=interest group/update url=] +
{{GenerateBidInterestGroup/trustedBiddingSignalsURL}} +
The [=serialize a URL|serialization=] of |ig|'s [=interest group/trusted bidding signals url=] +
{{GenerateBidInterestGroup/trustedBiddingSignalsKeys}} +
|ig|'s [=interest group/trusted bidding signals keys=] +
{{GenerateBidInterestGroup/userBiddingSignals}} +
[=Parse a JSON string to a JavaScript value=] given |ig|'s [=interest group/user bidding signals=] + +
{{GenerateBidInterestGroup/ads}} +
|ig|'s [=interest group/ads=] [=converted to an AuctionAd sequence=] +
{{GenerateBidInterestGroup/adComponents}} +
|ig|'s [=interest group/ad components=] [=converted to an AuctionAd sequence=] +
+ 1. Return |igGenerateBid|. +
+ +
+To serialize a URL given a [=URL=]-or-null |url|: + + 1. If |url| is null, then return {{undefined}}. + 1. Return the [=URL serializer|serialization=] of |url|. +
+ +
+To convert to an AuctionAd sequence given a [=list=]-or-null |ads|: + + 1. If |ads| is null, then return {{undefined}}. + 1. Let |adsIDL| be a new [=sequence=]<{{AuctionAd}}>. + 1. [=list/For each=] |ad| of |ads|: + 1. Let |adIDL| be a new {{AuctionAd}}. + 1. [=map/Set=] |adIDL|["{{AuctionAd/renderURL}}"] to the [=URL serializer|serialization=] of + |ad|'s [=interest group ad/render url=]. + 1. If |ad|'s [=interest group ad/metadata=] is not null, then: + 1. [=map/Set=] |adIDL|["{{AuctionAd/metadata}}"] to the result of + [=parsing a JSON string to a JavaScript value=] given |ad|'s [=interest group ad/metadata=]. + 1. [=list/Append=] |adIDL| to |adsIDL|. + 1. Return |adsIDL|. +
+
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|, a [=boolean=] |isComponentAuction|, and an [=origin=] -|topWindowOrigin|: -1. Let |renderURLs| be a new [=set=]. -1. Let |adComponentRenderUrls| be a new [=set=]. -1. [=set/Append=] |generatedBid|'s [=generated bid/ad descriptor=]'s [=ad descriptor/url=] to |renderURLs|. +{{unsigned long}}-or-null |biddingDataVersion|, an enum |auctionLevel|, which is +"single-level-auction", "top-level-auction", or "component-auction", a [=currency tag=] +|componentAuctionExpectedCurrency|, and an [=origin=] |topWindowOrigin|: + +1. Let |renderURL| be [=URL serializer|serialized=] |generatedBid|'s + [=generated bid/ad descriptor=]'s [=ad descriptor/url=]. +1. Let |adComponentRenderURLs| be a new empty [=list=]. 1. If |generatedBid|'s [=generated bid/ad component descriptors=] is not null: - 1. [=set/For each=] |adComponentDescriptor| in |generatedBid|'s + 1. [=list/For each=] |adComponentDescriptor| in |generatedBid|'s [=generated bid/ad component descriptors=]: - 1. [=set/Append=] |adComponentDescriptor|'s [=ad descriptor/url=] to |adComponentRenderUrls|. + 1. [=list/Append=] [=URL serializer|serialized=] |adComponentDescriptor|'s [=ad descriptor/url=] + to |adComponentRenderURLs|. 1. Let |fullSignalsUrl| be the result of [=building trusted scoring signals url=] with |auctionConfig|'s - [=auction config/trusted scoring signals url=], |renderURLs|, |adComponentRenderUrls|, + [=auction config/trusted scoring signals url=], «|renderURL|», |adComponentRenderURLs|, |auctionConfig|'s [=auction config/seller experiment group id=], and |topWindowOrigin|. Implementations may batch requests by collecting render URLs and ad component render URLs from multiple invocations of [=score and rank a bid=] and passing them all to a single invocation - of [=building trusted scoring signals url=] -- the network response has to be parsed to pull out the pieces - relevant to each [=evaluating a scoring script|evaluation of a scoring script=]. + of [=building trusted scoring signals url=] -- the network response has to be parsed to pull out + the pieces relevant to each [=evaluating a scoring script|evaluation of a scoring script=]. 1. Let |trustedScoringSignals| be null. -1. Let «|allTrustedScoringSignals|, |scoringDataVersion|» be the result of [=fetching trusted signals=] with - |fullSignalsUrl| and false. +1. Let «|allTrustedScoringSignals|, |scoringDataVersion|» be the result of [=fetching trusted signals=] + with |fullSignalsUrl| and false. 1. If |allTrustedScoringSignals| is an [=ordered map=]: - 1. Let |trustedScoringSignals| be a new [=map=]. - 1. [=Assert=]: |renderURLs|'s [=set/size=] is 1. - 1. [=set/For each=] |renderURL| in |renderURLs|: - 1. If |allTrustedScoringSignals|["`renderURLs`"] [=map/exists=] and - |allTrustedScoringSignals|["`renderURLs`"][|renderURL|] [=map/exists=]: - 1. Let |renderURLValue| be a new [=map=]. - 1. [=map/Set=] |renderURLValue|[|renderURL|] to |allTrustedScoringSignals|["`renderURLs`"][|renderURL|]. - 1. [=map/Set=] |trustedScoringSignals|["`renderURL`"] to |renderURLValue|. - 1. Let |adComponentRenderUrlsValue| be a new [=map=]. - 1. [=set/For each=] |adComponentRenderUrl| in |adComponentRenderUrls|: - 1. If |allTrustedScoringSignals|["`adComponentRenderUrls`"] [=map/exists=] and - |allTrustedScoringSignals|["`adComponentRenderUrls`"][|adComponentRenderUrl|] [=map/exists=]: - 1. [=map/Set=] |adComponentRenderUrlsValue|[|adComponentRenderUrl|] to - |allTrustedScoringSignals|["`adComponentRenderUrls`"][|adComponentRenderUrl|]. - 1. If |adComponentRenderUrlsValue| is not [=map/is empty|empty=]: - 1. [=map/Set=] |trustedScoringSignals|["`adComponentRenderUrls`"] to |adComponentRenderUrlsValue|. + 1. Set |trustedScoringSignals| to a new empty [=map=]. + 1. [=map/Set=] |trustedScoringSignals|["`renderURL`"] to a new empty [=map=]. + 1. If |allTrustedScoringSignals|["`renderURLs`"] [=map/exists=] and + |allTrustedScoringSignals|["`renderURLs`"][|renderURL|] [=map/exists=]: + 1. [=map/Set=] |trustedScoringSignals|["`renderURL`"][|renderURL|] to + |allTrustedScoringSignals|["`renderURLs`"][|renderURL|]. + 1. If |adComponentRenderURLs| is not [=list/empty=]: + 1. Let |adComponentRenderURLsValue| be a new empty [=map=]. + 1. If |allTrustedScoringSignals|["`adComponentRenderURLs`"] [=map/exists=], [=set/for each=] + |adComponentRenderURL| in |adComponentRenderURLs|: + 1. If |allTrustedScoringSignals|["`adComponentRenderURLs`"][|adComponentRenderURL|] [=map/exists=]: + 1. [=map/Set=] |adComponentRenderURLsValue|[|adComponentRenderURL|] to + |allTrustedScoringSignals|["`adComponentRenderURLs`"][|adComponentRenderURL|]. + 1. [=map/Set=] |trustedScoringSignals|["`adComponentRenderURLs`"] to |adComponentRenderURLsValue|. 1. Let |adMetadata| be |generatedBid|'s [=generated bid/ad=]. 1. Let |bidValue| be |generatedBid|'s [=generated bid/bid=]. 1. If |generatedBid|'s [=generated bid/modified bid=] is not null: 1. Set |bidValue| to |generatedBid|'s [=generated bid/modified bid=]. 1. Let |owner| be |generatedBid|'s [=generated bid/interest group=]'s [=interest group/owner=]. -1. Let |browserSignals| be an [=ordered map=] whose [=map/keys=] are [=strings=] and whose - [=map/values=] are {{any}}. -1. [=map/Set=] |browserSignals|["`topWindowHostname`"] to |topWindowOrigin|'s [=origin/host=]. -1. [=map/Set=] |browserSignals|["interestGroupOwner"] to |owner|. -1. [=map/Set=] |browserSignals|["`renderURL`"] to |generatedBid|'s [=generated bid/ad descriptor=]'s - [=ad descriptor/url=]. -1. [=map/Set=] |browserSignals|["`adComponents`"] to |generatedBid|'s - [=generated bid/ad component descriptors=]. -1. [=map/Set=] |browserSignals|["`biddingDurationMsec`"] to |generatedBid|'s - [=generated bid/bid duration=]. -1. If |scoringDataVersion| is not null: - 1. [=map/Set=] |browserSignals|["`dataVersion`"] to |scoringDataVersion|. -1. TODO: Remove fields of |auctionConfig| that don't pass through. -1. Let |scoreAdOutput| be the result of [=evaluating a scoring script=] with - |decisionLogicScript| and « |adMetadata|, |bidValue|, |auctionConfig|, |trustedScoringSignals|, - |browserSignals| ». -1. If |isComponentAuction| is true, and |scoreAdOutput|'s - [=score ad output/allow component auction=] is false, return. -1. Let |score| be |scoreAdOutput|'s [=score ad output/desirability=]. +1. Let |browserSignals| be a {{ScoringBrowserSignals}} with the following fields: +
+
{{ScoringBrowserSignals/topWindowHostname}} +
The result of running the host serializer on |topWindowOrigin|'s [=origin/host=] +
{{ScoringBrowserSignals/interestGroupOwner}} +
[=serialization of an origin|Serialized=] |owner| +
{{ScoringBrowserSignals/renderURL}} +
The result of running the [=URL serializer=] on |generatedBid|'s [=generated bid/ad descriptor=]'s [=ad descriptor/url=] +
{{ScoringBrowserSignals/biddingDurationMsec}} +
|generatedBid|'s [=generated bid/bid duration=] +
{{ScoringBrowserSignals/bidCurrency}} +
The result of [=serializing a currency tag=] with |generatedBid|'s [=generated bid/bid=]'s + [=bid with currency/currency=] +
{{ScoringBrowserSignals/dataVersion}} +
|scoringDataVersion| if it is not null, {{undefined}} otherwise +
{{ScoringBrowserSignals/adComponents}} +
|generatedBid|'s [=generated bid/ad component descriptors=] [=converted to a string sequence=] +
+1. Let |scoreAdResult| be the result of [=evaluating a scoring script=] with + |decisionLogicScript|, |adMetadata|, |bidValue|'s [=bid with currency/value=], |auctionConfig|'s + [=auction config/config idl=], |trustedScoringSignals|, |browserSignals|, and |auctionConfig|'s + [=auction config/seller timeout=]. +1. Let |scoreAdOutput| be result of [=processing scoreAd output=] with |scoreAdResult|. +1. If |scoreAdOutput| is failure, return. +1. If |auctionLevel| is not "single-level-auction", and |scoreAdOutput| + ["{{ScoreAdOutput/allowComponentAuction}}"] is false, return. +1. Let |score| be |scoreAdOutput|["{{ScoreAdOutput/desirability}}"]. 1. If |score| is negative or 0, return. -1. If |isComponentAuction| is true and |scoreAdOutput|'s [=score ad output/bid=] is not null: - 1. Set |generatedBid|'s [=generated bid/modified bid=] to |scoreAdOutput|'s - [=score ad output/bid=]. +1. If |auctionLevel| is "component-auction": + 1. Let |bidToCheck| be |generatedBid|'s [=generated bid/bid=]. + 1. If |scoreAdOutput|["{{ScoreAdOutput/bid}}"] [=map/exists=]: + 1. Let |modifiedBidValue| be |scoreAdOutput|["{{ScoreAdOutput/bid}}"]. + 1. If |modifiedBidValue| is negative or 0, return. + 1. Let |modifiedBidCurrency| be null. + 1. If |scoreAdOutput|["{{ScoreAdOutput/bidCurrency}}] [=map/exists=]: + 1. Set |modifiedBidCurrency| to |scoreAdOutput|["{{ScoreAdOutput/bidCurrency}}]. + 1. Set |generatedBid|'s [=generated bid/modified bid=] to a [=bid with currency=] with + [=bid with currency/value=] |modifiedBidValue| and [=bid with currency/currency=] + |modifiedBidCurrency|. + 1. Set |bidToCheck| to |generatedBid|'s [=generated bid/modified bid=]. + 1. If the result of [=checking a currency tag=] with |componentAuctionExpectedCurrency| and + |bidToCheck|'s [=bid with currency/currency=] is false, return. + 1. If the result of [=checking a currency tag=] with |auctionConfig|'s + [=auction config/seller currency=] and |bidToCheck|'s [=bid with currency/currency=] is false, + return. +1. If |auctionConfig|'s [=auction config/seller currency=] is not null: + 1. If |generatedBid|'s [=generated bid/bid=]'s [=bid with currency/currency=] is equal to + |auctionConfig|'s [=auction config/seller currency=]: + 1. Set |generatedBid|'s [=generated bid/bid in seller currency=] to |generatedBid|'s + [=generated bid/bid=]'s [=bid with currency/value=]. + 1. If |scoreAdOutput|["{{ScoreAdOutput/incomingBidInSellerCurrency}}"] [=map/ exists=] and does + not equal |generatedBid|'s [=generated bid/bid in seller currency=], return. + 1. Otherwise if |scoreAdOutput|["{{ScoreAdOutput/incomingBidInSellerCurrency}}"] [=map/ exists=]: + 1. Set |generatedBid|'s [=generated bid/bid in seller currency=] to + |scoreAdOutput|["{{ScoreAdOutput/incomingBidInSellerCurrency}}"] 1. Let |updateLeadingBid| be false. 1. If |leadingBidInfo|'s [=leading bid info/leading bid=] is null, or |score| is greater than |leadingBidInfo|'s [=leading bid info/top score=]: @@ -845,6 +1465,16 @@ To score and rank a bid given an [=auction config=] |auctionConfig|,
+
+To convert to a string sequence given a [=list=]-or-null |adComponents|: + + 1. If |adComponents| is null, return {{undefined}}. + 1. Let |result| be a new [=sequence=]<{{USVString}}>. + 1. [=list/For each=] |component| of |adComponents|: + 1. [=list/Append=] [=URL serializer|serialized=] |component|'s [=ad descriptor/url=] to |result|. + 1. Return |result|. +
+
To update highest scoring other bid given a {{double}} |score|, a [=generated bid=]-or-null |bid|, and a [=leading bid info=] |leadingBidInfo|: @@ -870,39 +1500,12 @@ To update highest scoring other bid given a {{double}} |score|, a
-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: - : [=request/URL=] - :: |url| - : [=request/header list=] - :: A new [=header list=] containing a [=header=] named "`Accept`" whose value is |accept| - : [=request/client=] - :: `null` - : [=request/window=] - :: "`no-window`" TODO: verify - : [=request/service-workers mode=] - :: "`none`" - : [=request/origin=] - :: If |origin| is null, then [=opaque origin=], otherwise |origin|. - : [=request/referrer=] - :: "`no-referrer`" - : [=request/credentials mode=] - :: "`omit`" - : [=request/cache mode=] - :: "`no-store`" - : [=request/redirect mode=] - :: "`error`" - 1. Return |request|. -
- -
-To validate fetching response given a [=response=] |response| and |responseBody|, and a -[=string=] |mimeType|: +To validate fetching response given a [=response=] |response|, null, failure, or a +[=byte sequence=]|responseBody|, and a [=string=] |mimeType|: 1. If |responseBody| is null or failure, return false. - 1. If [=header list/getting a structured field value|getting=] "X-Allow-Protected-Audience" from - |response|'s [=response/header list=] does not return true, return false. + 1. If [=header list/getting a structured field value|getting=] "X-Allow-Protected-Audience" and + "`item`" from |response|'s [=response/header list=] does not return true, return false. 1. Let |headerMimeType| be the result of [=header list/extracting a MIME type=] from |response|'s [=response/header list=]. 1. Return false if any of the following conditions hold: @@ -913,18 +1516,33 @@ To validate fetching response given a [=response=] |response| and |re 1. Return false if any of the following conditions hold: * |mimeTypeCharset| does not [=map/exist=], or |mimeTypeCharset| is "utf-8", and |responseBody| is not [=UTF-8=] encoded; - * |mimeTypeCharset| is "us-ascii", and |responseBody| is not [=ascii string=]. + * |mimeTypeCharset| is "us-ascii", and not all bytes in |responseBody| are [=ASCII bytes=]. 1. 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. + 1. Let |request| be a new [=request=] with the following properties: + : [=request/URL=] + :: |url| + : [=request/header list=] + :: «`Accept`: `text/javascript`» + : [=request/client=] + :: `null` + : [=request/service-workers mode=] + :: "`none`" + : [=request/mode=] + :: "`no-cors`" + : [=request/referrer=] + :: "`no-referrer`" + : [=request/credentials mode=] + :: "`omit`" + : [=request/redirect mode=] + :: "`error`" 1. Let |script| be null. - 1. [=Fetch=] |request| with [=fetch/processResponseConsumeBody=] set to the following steps given - a [=response=] |response| and |responseBody|: + 1. [=Fetch=] |request| with [=fetch/useParallelQueue=] set to true, and + [=fetch/processResponseConsumeBody=] set to the following steps given a [=response=] |response| + and null, failure, or a [=byte sequence=] |responseBody|: 1. If [=validate fetching response=] with |response|, |responseBody| and "`text/javascript`" returns false, set |script| to failure and return. 1. Set |script| to |responseBody|. @@ -935,20 +1553,33 @@ To fetch script given a [=URL=] |url|:
To fetch WebAssembly given a [=URL=] |url|: - 1. Let |request| be the result of [=creating a request=] with |url|, "`application/wasm`", and - null. + 1. Let |request| be a new [=request=] with the following properties: + : [=request/URL=] + :: |url| + : [=request/header list=] + :: «`Accept`: `application/wasm`» + : [=request/client=] + :: `null` + : [=request/service-workers mode=] + :: "`none`" + : [=request/mode=] + :: "`no-cors`" + : [=request/referrer=] + :: "`no-referrer`" + : [=request/credentials mode=] + :: "`omit`" + : [=request/redirect mode=] + :: "`error`" 1. Let |moduleObject| be null. 1. [=Fetch=] |request| with [=fetch/processResponseConsumeBody=] set to the following steps given - a [=response=] |response| and |responseBody|: + a [=response=] |response| and null, failure, or a [=byte sequence=] |responseBody|: 1. Set |moduleObject| to failure and return, if any of the following conditions hold: * |responseBody| is null or failure; - * [=header list/getting a structured field value|Getting=] "X-Allow-Protected-Audience" from - |response|'s [=response/header list=] does not return true. - 1. Let |moduleObjectPromise| be the result of [=compiling a WebAssembly module=] |response|. - 1. [=Upon fulfillment=] of |moduleObjectPromise| with value |module|: - 1. Set |moduleObject| to |module|. - 1. [=Upon rejection=] of |moduleObjectPromise|: - 1. Set |moduleObject| to failure. + * [=header list/getting a structured field value|Getting=] "X-Allow-Protected-Audience" and + "`item`" from |response|'s [=response/header list=] does not return true. + 1. Let |module| be the result of [=compiling a WebAssembly module=] |response|. + 1. If |module| is [=error=], set |moduleObject| to failure. + 1. Otherwise, set |moduleObject| to |module|. 1. Wait for |moduleObject| to be set. 1. Return |moduleObject|.
@@ -961,22 +1592,37 @@ HTTP response header is a [=structured header=] whose value must be an [=structu
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. + 1. Let |request| be a new [=request=] with the following properties: + : [=request/URL=] + :: |url| + : [=request/header list=] + :: «`Accept`: `application/json`» + : [=request/client=] + :: `null` + : [=request/service-workers mode=] + :: "`none`" + : [=request/mode=] + :: "`no-cors`" + : [=request/referrer=] + :: "`no-referrer`" + : [=request/credentials mode=] + :: "`omit`" + : [=request/redirect mode=] + :: "`error`" 1. Let |signals| be null. 1. Let |dataVersion| be null. 1. Let |formatVersion| be null. - 1. [=Fetch=] |request| with [=fetch/processResponseConsumeBody=] set to the following steps given - a [=response=] |response| and |responseBody|: + 1. [=Fetch=] |request| with [=fetch/useParallelQueue=] set to true, and + [=fetch/processResponseConsumeBody=] set to the following steps given a [=response=] |response| + and null, failure, or a [=byte sequence=] |responseBody|: 1. If [=validate fetching response=] with |response|, |responseBody| and "`application/json`" returns false, set |signals| to failure and return. 1. Let |headers| be |response|'s [=response/header list=]. 1. Set |dataVersion| to the result of [=header list/getting a structured field value=] given [:Data-Version:] and "`item`" from |headers|. 1. 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. - 1. TODO: Check whether version is consistent for all keys requested by this interest group. + 1. If |dataVersion| is not an integer, or is less than 0 or more than 232−1, + set |signals| to failure and return. 1. If |isBiddingSignal| is true: 1. Set |formatVersion| to the result of [=header list/getting a structured field value=] given [:X-protected-audience-bidding-signals-format-version:] and "`item`" from |headers|. @@ -984,18 +1630,21 @@ To fetch trusted signals given a [=URL=] |url|, and a [=boolean=] |is 1. Wait for |signals| to be set. 1. If |signals| is a parsing exception, or if |signals| is not an [=ordered map=], return « null, null ». + 1. [=map/For each=] |key| → |value| of |signals|: + 1. [=map/Set=] |signals|[|key|] to the result of [=serializing an Infra value to a JSON string=] + given |value|. 1. If |formatVersion| is 2: - 1. If |signals|["`keys`"] [=map/exists=], then set |signals| to |signals|["`keys`"]. - 1. TODO: handle priority vector. - 1. Otherwise, return « null, null ». + 1. If |signals|["`keys`"] does not [=map/exist=], return « null, null ». + 1. Set |signals| to |signals|["`keys`"]. 1. If |signals| is not an [=ordered map=], return « null, null ». + 1. TODO: handle priority vector. 1. Return « |signals|, |dataVersion| ».
To encode trusted signals keys given an [=ordered set=] of [=strings=] |keys|: -1. Let |list| be a new [=list/is empty|empty=] [=list=]. +1. Let |list| be a new empty [=list=]. 1. Let |keysStr| be the result of [=string/concatenating=] |keys| with separator set to ",". 1. [=list/Append=] the result of [=string/UTF-8 percent-encoding=] |keysStr| using [=component percent-encode set=] to |list|. @@ -1008,7 +1657,7 @@ To encode trusted signals keys given an [=ordered set=] of [=strings= To build trusted bidding signals url given a [=URL=] |signalsUrl|, an [=ordered set=] of [=strings=] |keys|, an [=ordered set=] of [=strings=] |igNames|, and an {{unsigned short}}-or-null |experimentGroupId|: -1. Let |queryParamsList| be a new [=list/is empty|empty=] [=list=]. +1. Let |queryParamsList| be a new empty [=list=]. 1. [=list/Append=] "hostname=" to |queryParamsList|. 1. [=list/Append=] the result of [=string/UTF-8 percent-encoding=] [=this=]'s [=relevant settings object=]'s [=environment/top-level origin=] using @@ -1032,29 +1681,24 @@ To build trusted bidding signals url given a [=URL=] |signalsUrl|, an
-To build trusted scoring signals url given a [=URL=] |signalsUrl|, an [=ordered set=] of -[=URLs=] |renderURLs|, an [=ordered set=] of [=URLs=] |adComponentRenderUrls|, an {{unsigned short}} -|experimentGroupId|, and an [=origin=] |topWindowOrigin|: -1. Let |queryParamsList| be a new [=list/is empty|empty=] [=list=]. +To build trusted scoring signals url given a [=URL=] |signalsUrl|, a [=list=] of +[=strings=] |renderURLs|, an [=ordered set=] of [=strings=] |adComponentRenderURLs|, an +{{unsigned short}} |experimentGroupId|, and an [=origin=] |topWindowOrigin|: + +Note: When trusted scoring signals fetches are not batched, |renderURLs|'s [=list/size=] is 1. + +1. Let |queryParamsList| be a new empty [=list=]. 1. [=list/Append=] "hostname=" to |queryParamsList|. 1. [=list/Append=] the result of [=string/UTF-8 percent-encoding=] |topWindowOrigin| using [=component percent-encode set=] to |queryParamsList|. 1. If |renderURLs| is not [=set/is empty|empty=]: 1. [=list/Append=] "&renderURLs=" to |queryParamsList|. - 1. Let |renderURLsStrings| be a new [=list/is empty|empty=] [=list=]. - 1. [=list/For each=] |renderURL| of |renderURLs|: - 1. [=list/Append=] the result of [=URL serializer|serialization=] of |renderURL| to - |renderURLsStrings|. 1. [=list/Extend=] |queryParamsList| with the result of [=encode trusted signals keys=] with - |renderURLsStrings|. -1. If |adComponentRenderUrls| is not [=set/is empty|empty=]: - 1. [=list/Append=] "&adComponentRenderUrls=" to |queryParamsList|. - 1. Let |adComponentRenderUrlsStrings| be a new [=list/is empty|empty=] [=list=]. - 1. [=list/For each=] |adComponentRenderUrl| of |adComponentRenderUrls|: - 1. [=list/Append=] the result of [=URL serializer|serialization=] of |adComponentRenderUrl| to - |adComponentRenderUrlsStrings|. + |renderURLs|. +1. If |adComponentRenderURLs| is not [=set/is empty|empty=]: + 1. [=list/Append=] "&adComponentRenderURLs=" to |queryParamsList|. 1. [=list/Extend=] |queryParamsList| with the result of [=encode trusted signals keys=] with - |adComponentRenderUrlsStrings|. + |adComponentRenderURLs|. 1. If |experimentGroupId| is not null: 1. [=list/Append=] "&experimentGroupId=" to |queryParamsList|. 1. [=list/Append=] [=serialize an integer|serialized=] |experimentGroupId| to |queryParamsList|. @@ -1074,53 +1718,125 @@ Issue: This would ideally be replaced by a more descriptive algorithm in Infra.
-To report result given a [=leading bid info=] |leadingBidInfo|: +To round a value given a {{double}} |value|: + 1. If |value| is not a [=valid floating-point number=], return |value|. + 1. Let |valueExp| be |value|'s IEEE 754 biased exponent field minus 1023. + 1. Let |normValue| be |value| multiplied by 2(−1 × |valueExp|). + 1. If |valueExp| is less than −128: + 1. If |value| is less than 0, return −0. + 1. Otherwise, return 0. + 1. If |valueExp| is greater than 127: + 1. If |value| is less than 0, return −∞. + 1. Otherwise, return ∞. + 1. Let |precisionScaledValue| be |normValue| multiplied by 256. + 1. Let |noisyScaledValue| be |precisionScaledValue| plus a random {{double}} value greater than or equal to 0 but less than 1. + 1. Let |truncatedScaledValue| be the largest integer not greater than |noisyScaledValue|. + 1. Return |truncatedScaledValue| multiplied by 2(|valueExp| − 8). - 1. Let |browserSignals| be an [=ordered map=] whose [=map/keys=] are [=strings=] and whose - [=map/values=] are {{any}}. - 1. [=map/Set=] |browserSignals|["`topWindowHostname`"] to [=this=]'s - [=relevant settings object=]'s [=environment/top-level origin=]'s [=origin/host=]. +
+ +
+To report result given a [=leading bid info=] |leadingBidInfo| and [=auction config=] or +null |winningComponentConfig|: + 1. Let |config| be |leadingBidInfo|'s [=leading bid info/auction config=]. + 1. Let |bidCurrency| be null. + 1. If |winningComponentConfig| is not null: + 1. [=Assert=] that |leadingBidInfo|'s [=leading bid info/component seller=] is not null. + 1. Set |bidCurrency| to |winningComponentConfig|'s [=auction config/seller currency=]. + 1. If |bidCurrency| is null: + 1. Set |bidCurrency| to the result of [=looking up per-buyer currency=] with |config| and + |leadingBidInfo|'s [=leading bid info/component seller=]. + 1. Otherwise: + 1. Set |bidCurrency| to the result of [=looking up per-buyer currency=] with |config| and + |leadingBidInfo|'s [=leading bid info/leading bid=]'s [=generated bid/interest group=]'s + [=interest group/owner=]. 1. Let |winner| be |leadingBidInfo|'s [=leading bid info/leading bid=]. - 1. [=map/Set=] |browserSignals|["`interestGroupOwner`"] to |winner|'s - [=generated bid/interest group=]'s [=interest group/owner=]. - 1. [=map/Set=] |browserSignals|["`renderURL`"] to |winner|'s [=generated bid/ad descriptor=]'s - [=ad descriptor/url=]. - 1. [=map/Set=] |browserSignals|["`bid`"] to |winner|'s [=generated bid/bid=]. - 1. [=map/Set=] |browserSignals|["`desirability`"] to |leadingBidInfo|'s - [=leading bid info/top score=]. - 1. [=map/Set=] |browserSignals|["`highestScoringOtherBid`"] to |leadingBidInfo|'s - [=leading bid info/highest scoring other bid=]'s [=generated bid/bid=]. - 1. TODO: if trusted scoring signals response data version is not null, set - |browserSignals|["`dataVersion`"]. - 1. If |leadingBidInfo|'s [=leading bid info/top level seller=] is not null, [=map/set=] - |browserSignals|["`topLevelSeller`"] to it. - 1. If |leadingBidInfo|'s [=leading bid info/top level seller signals=] is not null, [=map/set=] - |browserSignals|["`topLevelSellerSignals`"] to it. - 1. If |leadingBidInfo|'s [=leading bid info/component seller=] is not null: - 1. [=map/Set=] |browserSignals|["`componentSeller`"] to |leadingBidInfo|'s - [=leading bid info/component seller=]. - 1. If |winner|'s [=generated bid/modified bid=] is not null, [=map/set=] - |browserSignals|["`bid`"] to it. - 1. Otherwise, if |winner|'s [=generated bid/modified bid=] is not null, [=map/set=] - |browserSignals|["`modifiedBid`"] to it. - 1. Set |config| to |leadingBidInfo|'s [=leading bid info/auction config=]. + 1. Let |sellerCurrency| be |leadingBidInfo|'s [=leading bid info/auction config=]'s + [=auction config/seller currency=]. + 1. Let |highestScoringOtherBid| be |leadingBidInfo|'s + [=leading bid info/highest scoring other bid=]'s [=generated bid/bid in seller currency=] (or + 0 if encountered a null). + 1. If |sellerCurrency| is null: + 1. Set |highestScoringOtherBid| to |leadingBidInfo|'s + [=leading bid info/highest scoring other bid=]'s [=generated bid/bid=]'s + [=bid with currency/value=] (or 0 if encountered a null). + 1. Let |bid| be |winner|'s [=generated bid/bid=]'s [=bid with currency/value=]. + 1. Let |modifiedBid| be null. + 1. If |winner|'s [=generated bid/modified bid=] is not null: + 1. If |leadingBidInfo|'s [=leading bid info/component seller=] is not null: + 1. Set |bid| to |winner|'s [=generated bid/modified bid=]. + 1. Otherwise: + 1. Set |modifiedBid| to |winner|'s [=generated bid/modified bid=]. + 1. Let |browserSignals| be a {{ReportResultBrowserSignals}} with the following fields: +
+
{{topWindowHostname}} +
The result of running the host serializer on [=this=]'s + [=relevant settings object=]'s [=environment/top-level origin=]'s [=origin/host=]. +
{{interestGroupOwner}} +
[=serialization of an origin|Serialized=] |winner|'s [=generated bid/interest group=]'s + [=interest group/owner=]. +
{{renderURL}} +
[=URL serializer|Serialized=] |winner|'s [=generated bid/ad descriptor=]'s [=ad descriptor/url=] +
{{bid}} +
[=round a value|Stochastically rounded=] |bid| +
{{bidCurrency}} +
The result of [=serializing a currency tag=] with |bidCurrency| +
{{highestScoringOtherBid}} +
|highestScoringOtherBid| +
{{highestScoringOtherBidCurrency}} +
|sellerCurrency| if it is not null, "`???`" otherwise +
{{topLevelSeller}} +
|leadingBidInfo|'s [=leading bid info/top level seller=] if it is not null, {{undefined}} + otherwise +
{{componentSeller}} +
|leadingBidInfo|'s [=leading bid info/component seller=] if it is not null, {{undefined}} + otherwise +
{{ReportResultBrowserSignals/desirability}} +
[=round a value|Stochastically rounded=] |leadingBidInfo|'s [=leading bid info/top score=] +
{{ReportResultBrowserSignals/topLevelSellerSignals}} +
|leadingBidInfo|'s [=leading bid info/top level seller signals=] if it is not null, + {{undefined}} otherwise +
{{ReportResultBrowserSignals/modifiedBid}} +
[=round a value|Stochastically rounded=] |modifiedBid| if it is not null, {{undefined}} otherwise +
{{ReportResultBrowserSignals/dataVersion}} +
|leadingBidInfo|'s [=leading bid info/scoring data version=] if it is not null, + {{undefined}} otherwise +
+ 1. Let |igAd| be the [=interest group ad=] from |winner|'s [=generated bid/interest group=]'s [=interest group/ads=] whose [=interest group ad/render url=] is |winner|'s [=generated bid/ad descriptor=]'s [=ad descriptor/url=]. + 1. If |igAd|'s [=interest group ad/buyer and seller reporting ID=] + [=map/exists=] and the result of [=query reporting ID k-anonymity count=] + given |winner|'s [=generated bid/interest group=] and |igAd| is true: + 1. [=map/Set=] |browserSignals|["{{ReportingBrowserSignals/buyerAndSellerReportingId}}"] to |igAd|'s [=interest group ad/buyer and seller reporting ID=]. 1. Let |sellerReportingScript| be the result of [=fetching script=] with |config|'s [=auction config/decision logic url=]. - 1. Let « |sellerSignals|, |reportUrl| » be the result of [=evaluating a reporting script=] with - |sellerReportingScript|, "`reportResult`", and - « |config|'s [=auction config/auction signals=], |browserSignals| ». - 1. If |reportUrl| is not null: - 1. TODO: Set |reportUrl| into the {{FencedFrameConfig}} so they can be fetched when ad renders. - 1. [=map/Remove=] |browserSignals|["`dataVersion`"]. + 1. Let « |sellerSignals|, |reportUrl|, |reportingBeaconMap| » be the result of + [=evaluating a reporting script=] with |sellerReportingScript|, "`reportResult`", and + « |config|'s [=auction config/config idl=], |browserSignals| ». + 1. Let |reportingResult| be a [=reporting result=] with the following [=struct/items=]: + : [=reporting result/report url=] + :: |reportUrl| + + : [=reporting result/reporting beacon map=] + :: |reportingBeaconMap| + 1. If |leadingBidInfo|'s [=leading bid info/top level seller=] is null (i.e., if we are reporting + for a component seller), set |leadingBidInfo|'s + [=leading bid info/component seller reporting result=] to |reportingResult|. + 1. Otherwise, set |leadingBidInfo|'s [=leading bid info/seller reporting result=] to + |reportingResult|. + 1. [=map/Remove=] |browserSignals|["`desirability`"]. 1. [=map/Remove=] |browserSignals|["`modifiedBid`"]. - 1. [=map/Remove=] |browserSignals|["`topLevelSellerSignals`"]. + 1. [=map/Remove=] |browserSignals|["`topLevelSellerSignals`"]. + 1. [=map/Remove=] |browserSignals|["`dataVersion`"]. + + Note: Remove fields specific to {{ReportResultBrowserSignals}} which only sellers can learn about, + so that they are not passed to "`reportWin()`". + 1. Return « |sellerSignals|, |browserSignals| ».
-To report win given a [=leading bid info=] |leadingBidInfo|, a [=string=] -|sellerSignals| and an [=ordered map=] |browserSignals| whose [=map/keys=] are [=strings=] and whose -[=map/values=] are {{any}}: +To report win given a [=leading bid info=] |leadingBidInfo|, a [=string=] |sellerSignals| +and a {{ReportingBrowserSignals}} |browserSignals|: 1. Let |config| be |leadingBidInfo|'s [=leading bid info/auction config=]. 1. Let |winner| be |leadingBidInfo|'s [=leading bid info/leading bid=]. @@ -1128,35 +1844,182 @@ To report win given a [=leading bid info=] |leadingBidInfo|, a [=stri 1. Let |buyer| be |winner|'s [=generated bid/interest group=]'s [=interest group/owner=]. 1. Let |perBuyerSignalsForBuyer| be |perBuyerSignals|[|buyer|] if that member [=map/exists=], and null otherwise. - 1. [=map/Remove=] |browserSignals|["`desirability`"]. - 1. TODO: [=map/Set=] |browserSignals|["`interestGroupName`"] to |winner|'s [=interest group/name=] - if the tuple of interest group owner, name, bidding script URL and ad creative URL were jointly - k-anonymous. - 1. [=map/Set=] |browserSignals|["`madeHighestScoringOtherBid`"] to false. - 1. Let |highestScoringOtherBidOwner| be |leadingBidInfo|'s - [=leading bid info/highest scoring other bid owner=]. - 1. If |highestScoringOtherBidOwner| is not null, and |buyer| is [=same origin=] with - |highestScoringOtherBidOwner|: - 1. [=map/Set=] |browserSignals|["`madeHighestScoringOtherBid`"] to true. - 1. If |leadingBidInfo|'s [=leading bid info/bidding data version=] is not null: - 1. [=map/Set=] |browserSignals|["dataVersion"] to |leadingBidInfo|'s - [=leading bid info/bidding data version=]. - 1. [=map/Set=] |browserSignals|["adCost"] to |winner|'s [=generated bid/ad cost=]. - 1. [=map/Set=] |browserSignals|["seller"] to |config|'s [=auction config/seller=]. - 1. If |leadingBidInfo|'s [=leading bid info/top level seller=] is not null: - 1. [=map/Set=] |browserSignals|["`topLevelSeller`"] to |leadingBidInfo|'s - [=leading bid info/top level seller=]. - 1. If |winner|'s [=generated bid/modeling signals=] is not null: - 1. [=map/Set=] |browserSignals|["`modelingSignals`"] to |winner|'s - [=generated bid/modeling signals=]. + 1. Let |reportWinBrowserSignals| be a {{ReportWinBrowserSignals}} with the members that + are declared on {{ReportingBrowserSignals}} initialized to their values in |browserSignals|. + 1. Add the following fields to |reportWinBrowserSignals|: +
+
{{ReportWinBrowserSignals/dataVersion}} +
|leadingBidInfo|'s [=leading bid info/bidding data version=] if it is not null, + {{undefined}} otherwise. +
{{ReportWinBrowserSignals/adCost}} +
[=Round a value|Rounded=] |winner|’s [=generated bid/ad cost=] +
{{ReportWinBrowserSignals/seller}} +
[=serialization of an origin|Serialized=] |config|'s [=auction config/seller=] +
{{ReportWinBrowserSignals/madeHighestScoringOtherBid}} +
Set to true if |leadingBidInfo|'s [=leading bid info/highest scoring other bid owner=] is + not null, and |buyer| is [=same origin=] with |leadingBidInfo|'s + [=leading bid info/highest scoring other bid owner=], false otherwise +
{{ReportWinBrowserSignals/modelingSignals}} +
|winner|'s [=generated bid/modeling signals=] if it is not null, {{undefined}} otherwise + (TODO: noise and bucket this signal) +
+ 1. Let |igAd| be the [=interest group ad=] from |winner|'s + [=generated bid/interest group=]'s [=interest group/ads=] whose + [=interest group ad/render url=] is |winner|'s + [=generated bid/ad descriptor=]'s [=ad descriptor/url=]. + 1. If |igAd|'s [=interest group ad/buyer and seller reporting ID=] + does not [=map/exist=] and the result of [=query reporting ID k-anonymity count=] given |winner|'s [=generated bid/interest group=] and |igAd| is true: + 1. If |igAd|'s [=interest group ad/buyer reporting ID=] [=map/exists=], + [=map/set=] |reportWinBrowserSignals|["{{ReportWinBrowserSignals/buyerReportingId}}"] to |igAd|'s [=interest group ad/buyer reporting ID=]. + 1. Otherwise, [=map/Set=] |reportWinBrowserSignals|["{{ReportWinBrowserSignals/interestGroupName}}"] to |winner|'s [=generated bid/interest group=] [=interest group/name=]. 1. Let |buyerReportingScript| be the result of [=fetching script=] with |winner|'s [=generated bid/interest group=]'s [=interest group/bidding url=]. - 1. Let « nullReturn, |resultUrl| » be the result of [=evaluating a reporting script=] with - |buyerReportingScript|, "`reportWin`", and - « |leadingBidInfo|'s [=leading bid info/auction config=]'s [=auction config/auction signals=], - |perBuyerSignalsForBuyer|, |sellerSignals|, |browserSignals| ». - 1. If |resultUrl| is not null: - 1. TODO: Set |resultUrl| into the {{FencedFrameConfig}} so they can be fetched when ad renders. + 1. Let « ignored, |resultUrl|, |reportingBeaconMap| » be the result of [=evaluating a + reporting script=] with |buyerReportingScript|, "`reportWin`", and + « |leadingBidInfo|'s [=leading bid info/auction config=]'s [=auction config/config idl=]'s + {{AuctionAdConfig/auctionSignals}}, |perBuyerSignalsForBuyer|, |sellerSignals|, + |reportWinBrowserSignals| ». + 1. Set |leadingBidInfo|'s [=leading bid info/buyer reporting result=] to a [=reporting result=] + with the following [=struct/items=]: + : [=reporting result/report url=] + :: |resultUrl| + + : [=reporting result/reporting beacon map=] + :: |reportingBeaconMap| +
+ +# K-anonymity # {#k-anonymity} + +Two goals of this specification rely on applying k-anonymity thresholds: + + * To prevent cross-site leaks: Inputs to event-level reporting functions, `reportWin()` and + `reportResult()`, only contain limited cross-site information. As described in + [[#privacy-considerations]], part of this limiting is done by + applying k-anonymity requirements to the ad URLs. + * To prevent microtargeting: The browser applies k-anonymity requirements on the ad URLs to + ensure that the same ad or ad component is being shown to at least some minimum number of + people. + +The browser enforces these k-anonymity requirements by maintaining counts of how many times each +ad and ad component has been shown to users. These counts are maintained across users, so the counting must +be done on a central k-anonymity server. This specification relies on two operations to query and +increment the counts: [=query k-anonymity count=] and [=increment k-anonymity count=]. + +The details of how the [=k-anonymity server=] is operated and accessed are [=implementation-defined=] +but it should be done in a way that prevents the server operator from joining the identity of two +query or increment requests. One way to help prevent this is by making accesses to the server go +through an HTTP proxy that prevents the server from seeing the browsers' IP addresses. + +The browser should choose a k-anonymity threshold, otherwise known as the value for "k", +and a k-anonymity duration depending +on the projected sizes of interest groups and the browser's privacy goals. For example an implementation +might choose to require a k-anonymity threshold of fifty users over a seven day period. The server +will maintain the count over the chosen duration and compare the count to the chosen k-anonymity +threshold when responding to [=query k-anonymity count=]. + +
+ To query k-anonymity count given a |hashCode|: + 1. If the [=k-anonymity server=] has recorded at least [=k-anonymity threshold=] users + seeing |hashCode| over the last [=k-anonymity duration=], return true. + Otherwise return false. + 1. Return true if it is above the threshold, otherwise return false. +
+ +
+ To query ad k-anonymity count given an [=interest group=] |ig| and a [=URL=] |ad|: + 1. Let |keyString| be the [=string/concatenation=] of the following strings separated with U+000A LF: + 1. "AdBid" + 1. the [=serialization of an origin|serialization=] of |ig|'s [=interest group/owner=] + 1. the [=URL serializer|serialization=] of |ig|'s [=interest group/bidding url=] + 1. the [=URL serializer|serialization=] of |ad|. + 1. Let |keyHash| be the [=SHA-256=] hash of the [=ASCII encoding=] of |keyString|. + + 1. Return the result of [=query k-anonymity count|querying the k-anonymity count=] given |keyHash|. +
+ +
+ To compute the key hash of reporting ID given an [=interest group=] |ig| and an [=interest group ad=] |igAd|: + 1. Let |keyString| be the [=string/concatenation=] of the following strings separated with U+000A (LF): + + 1. "NameReport" + 1. the [=serialization of an origin|serialization=] of |ig|'s [=interest group/owner=] + 1. the [=URL serializer|serialization=] of |ig|'s [=interest group/bidding url=] + 1. the [=URL serializer|serialization=] of |igAd|'s [=interest group ad/render url=] + 1. If |igAd|'s [=interest group ad/buyer and seller reporting ID=] + [=map/exists=]: + 1. "BuyerAndSellerReportingId" + 1. |igAd|'s [=interest group ad/buyer and seller reporting ID=] + 1. Otherwise, if |igAd|'s [=interest group ad/buyer reporting ID=] + [=map/exists=]: + 1. "BuyerReportingId" + 1. |igAd|'s [=interest group ad/buyer reporting ID=] + 1. Otherwise: + 1. "IgName" + 1. |ig|'s [=interest group/name=]. + 1. Return the [=SHA-256=] hash of the [=ASCII encoding=] of |keyString|. +
+ +
+ To query component ad k-anonymity count given a [=URL=] |ad|: + 1. Let |keyString| be the [=string/concatenation=] of the following strings separated with U+000A LF: + 1. "ComponentBid" + 1. the [=URL serializer|serialization=] of |ad|. + 1. Let |keyHash| be the [=SHA-256=] hash of the [=ASCII encoding=] of |keyString|. + + 1. Return the result of [=query k-anonymity count|querying the k-anonymity count=] given |keyHash|. +
+ +
+ To query generated bid k-anonymity count given a [=generated bid=] |bid|: + 1. If [=query ad k-anonymity count=] given |bid|'s [=generated bid/ad descriptor=]'s + [=ad descriptor/url=] returns false, return false. + 1. If |bid|'s [=generated bid/ad component descriptors=] is not null: + 1. [=set/For each=] |adComponentDescriptor| in |bid|'s + [=generated bid/ad component descriptors=]: + 1. If [=query component ad k-anonymity count=] given |adComponentDescriptor|'s + [=ad descriptor/url=] returns false, return false. + 1. Return true. + +
+ +
+ To query reporting ID k-anonymity count given an [=interest group=] + |ig| and [=interest group ad=] |igAd|: + 1. Let |keyHash| be the result of [=computing the key hash of reporting ID=] given |ig| and |igAd|. + 1. Return the result of [=query k-anonymity count|querying the k-anonymity count=] given |keyHash|. +
+ +
+ To increment k-anonymity count given a |hashCode|: + 1. Ask the [=k-anonymity server=] to record that this user agent has seen |hashCode|. +
+ +
+ To increment ad k-anonymity count given an [=interest group=] |ig| and a [=URL=] |ad|: + 1. Let |keyString| be the [=string/concatenation=] of the following strings separated with U+000A LF: + 1. "AdBid" + 1. the [=serialization of an origin|serialization=] of |ig|'s [=interest group/owner=] + 1. the [=URL serializer|serialization=] of |ig|'s [=interest group/bidding url=] + 1. the [=URL serializer|serialization=] of |ad|. + 1. Let |keyHash| be the [=SHA-256=] hash of the [=ASCII encoding=] of |keyString|. + 1. [=Increment k-anonymity count=] given |keyHash|. +
+ +
+ To increment component ad k-anonymity count given a [=URL=] |ad|: + 1. Let |keyString| be the [=string/concatenation=] of the following strings separated with U+000A LF: + 1. "ComponentBid" + 1. the [=URL serializer|serialization=] of |ad|. + 1. Let |keyHash| be the [=SHA-256=] hash of the [=ASCII encoding=] of |keyString|. + 1. [=Increment k-anonymity count=] given |keyHash|. +
+ +
+ To increment reporting ID k-anonymity count given an [=interest group=] |ig| and a [=URL=] + |ad|: + 1. Let |igAd| be the [=interest group ad=] from |ig|'s [=interest group/ads=] whose [=interest group ad/render url=] is |ad|. + 1. Let |keyHash| be the result of [=computing the key hash of reporting ID=] given |ig| and |igAd|. + 1. [=Increment k-anonymity count=] given |keyHash|.
# Script Runners # {#script-runners} @@ -1187,73 +2050,7 @@ execution environment. In particular, they: inside them are not run with the familiar [[WebIDL]] [=invoke|invocation=] mechanism. * They do not [=perform a microtask checkpoints=]. -## Script evaluation ## {#script-evaluation} - -Concretely, a script runner is a JavaScript execution environment instantiated with one -of the following global objects: - - * {{InterestGroupBiddingScriptRunnerGlobalScope}} - * {{InterestGroupScoringScriptRunnerGlobalScope}} - * {{InterestGroupReportingScriptRunnerGlobalScope}} - -Each {{InterestGroupBiddingScriptRunnerGlobalScope}} has a -bid, which is a [=generated bid=], a -priority, which is a {{double}} or null, -a priority signals, which is an -[=ordered map=] whose [=map/keys=] are [=strings=] and whose [=map/values=] are {{double}}, an -interest group, which is an -[=interest group=], a -is component auction, which is a -[=boolean=], and a -group has ad components, which is a -[=boolean=]. - -Each {{InterestGroupReportingScriptRunnerGlobalScope}} has a -report url, which is a [=URL=] or null. - -
- To evaluate a bidding script given a [=string=] |script|, an [=interest group=] |ig|, - and a [=list=] of arguments |arguments|: - - 1. Let |groupHasAdComponents| be true. - 1. If |ig|'s [=interest group/ad components=] is null: - 1. Set |groupHasAdComponents| be false. - 1. Let |global| be a new {{InterestGroupBiddingScriptRunnerGlobalScope}}. - 1. Set |global|'s - [=InterestGroupBiddingScriptRunnerGlobalScope/group has ad components=] to - |groupHasAdComponents|. - 1. Let |numArguments| be |arguments|'s [=list/size=]. - 1. Let |browserSignals| be |arguments|[|numArguments| - 1]. - 1. Let |isComponentAuction| be false. - 1. If |browserSignals|["`topLevelSeller`"] is not null: - 1. Set |isComponentAuction| to true. - 1. Set |global|'s - [=InterestGroupBiddingScriptRunnerGlobalScope/is component auction=] to |isComponentAuction|. - 1. Set |global|'s [=InterestGroupBiddingScriptRunnerGlobalScope/interest group=] to |ig|. - 1. Return the result of [=evaluating a script=] with |global|, |script|, "`generateBid`", and - |arguments|. -
- -
- To evaluate a scoring script given a [=string=] |script| and a [=list=] of arguments - |arguments|: - - 1. Let |global| be a new {{InterestGroupScoringScriptRunnerGlobalScope}}. - 1. Return the result of [=evaluating a script=] with |global|, |script|, "`scoreAd`", and - |arguments|. -
- -
- To evaluate a reporting script given a [=string=] |script|, a [=string=] - |functionName|, and a [=list=] of arguments |arguments|: - - 1. Let |global| be a new {{InterestGroupReportingScriptRunnerGlobalScope}}. - 1. Let |result| be the result of [=evaluating a script=] with |global|, |script|, - |functionName|, and |arguments|. - 1. Return « |result|, |global|'s [=InterestGroupReportingScriptRunnerGlobalScope/report url=] ». -
- -
+## Realm and agent ## {#realm-and-agent}
To create a new script runner agent, run these steps: @@ -1267,15 +2064,14 @@ Each {{InterestGroupReportingScriptRunnerGlobalScope}} has a \[[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 + 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 [=ECMAScript/agent cluster=]. + 1. Let |agentCluster| be a new [=ECMAScript/agent cluster=]. 1. Let |agent| be the result of [=creating a new script runner agent=]. @@ -1285,24 +2081,20 @@ Each {{InterestGroupReportingScriptRunnerGlobalScope}} has a
- To evaluate a script with a [=realm/global object=] |global|, [=string=] |script|, [=string=] - |functionName|, and a [=list=] |arguments|, run these steps. They return a [=ECMAScript/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/ECMAScript language value=] result - of invoking |functionName|. + To create a new script runner realm with a global type |globalType|, run these steps: 1. [=Assert=] that these steps are running [=in parallel=]. - 1. Let |agent| be the result of [=obtaining a script runner agent=] given null, true, and - false. Run the rest of these steps in |agent|. + 1. Let |agent| be the result of [=obtaining a script runner agent=] given null, true, and false. + Run the rest of these steps in |agent|. - Issue: This exclusively creates a new [=ECMAScript/agent cluster=] for the given |script| to - run in, but we should make this work with [=interest group/execution mode=] somehow. + Issue: This exclusively creates a new [=ECMAScript/agent cluster=] for a given script to run + in, but we should make this work with [=interest group/execution mode=] somehow. 1. Let |realmExecutionContext| be the result of [=creating a new realm=] given |agent| and the following customizations: - * For the global object, use |global|. + * For the global object, create a new object of type |globalType|. 1. Let |realm| be |realmExecutionContext|'s Realm component. @@ -1313,15 +2105,136 @@ Each {{InterestGroupReportingScriptRunnerGlobalScope}} has a 1. If !|global|.\[[HasProperty]]("`Temporal`") is true, then perform !|global|.\[[Delete]]("`Temporal`"). - Advisement: This is not the best way to perform such API neutering (see tc39/ecma262#1357), + Advisement: 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 + Note: Removing time-referencing APIs from the |global| object is imperative for privacy, as a + script might otherwise be able to more easily exfiltrate data by using more accurate time measurements. - 1. Let |result| be [=ParseScript=](|script|, |realm|, `empty`). + 1. Return |realm|. +
+ +## Script evaluation ## {#script-evaluation} + +Concretely, a script runner is a JavaScript execution environment instantiated with one +of the following global objects: + + * {{InterestGroupBiddingScriptRunnerGlobalScope}} + * {{InterestGroupScoringScriptRunnerGlobalScope}} + * {{InterestGroupReportingScriptRunnerGlobalScope}} + +
+ To evaluate a bidding script given a [=string=] |script|, an [=interest group=] |ig|, a + [=currency tag=] |expectedCurrency|, a {{GenerateBidInterestGroup}} |igGenerateBid|, a [=string=]-or-null + |auctionSignals|, a [=string=]-or-null |perBuyerSignals|, an [=ordered map=] |trustedBiddingSignals|, a + {{BiddingBrowserSignals}} |browserSignals|, and an integer millisecond [=duration=] |timeout|: + + 1. Let |realm| be the result of [=creating a new script runner realm=] given + {{InterestGroupBiddingScriptRunnerGlobalScope}}. + 1. Let |global| be |realm|'s [=realm/global object=]. + 1. Set |global|'s + [=InterestGroupBiddingScriptRunnerGlobalScope/group has ad components=] to true if |ig|'s + [=interest group/ad components=] is not null, or false otherwise. + 1. Set |global|'s [=InterestGroupBiddingScriptRunnerGlobalScope/expected currency=] to |expectedCurrency|. + 1. Let |isComponentAuction| be true if |browserSignals|["{{BiddingBrowserSignals/topLevelSeller}}"] is not null, or + false otherwise. + 1. Set |global|'s [=InterestGroupBiddingScriptRunnerGlobalScope/is component auction=] to + |isComponentAuction|. + 1. Set |global|'s [=InterestGroupBiddingScriptRunnerGlobalScope/interest group=] to |ig|. + 1. Let |igJS| be the result of [=converted to ECMAScript values|converting to ECMAScript values=] + given |igGenerateBid|. + 1. Let |auctionSignalsJS| be the result of [=parsing a JSON string to a JavaScript value=] given + |auctionSignals| if |auctionSignals| is not null, otherwise {{undefined}}. + 1. Let |perBuyerSignalsJS| be the result of [=parsing a JSON string to a JavaScript value=] + given |perBuyerSignals| if |perBuyerSignals| is not null, otherwise {{undefined}}. + 1. Let |trustedBiddingSignalsJS| be |trustedBiddingSignals| [=converted to ECMAScript values=]. + 1. Let |browserSignalsJS| be |browserSignals| [=converted to ECMAScript values=]. + 1. Let |startTime| be the [=current wall time=]. + 1. Let |result| be the result of [=evaluating a script=] with |realm|, |script|, "`generateBid`", + « |igJS|, |auctionSignalsJS|, |perBuyerSignalsJS|, |trustedBiddingSignalsJS|, |browserSignalsJS| », + and |timeout|. + 1. Let |duration| be the [=current wall time=] minus |startTime| in milliseconds. + 1. If |global|'s [=InterestGroupBiddingScriptRunnerGlobalScope/priority=] is not null: + 1. Set |ig|'s [=interest group/priority=] to |global|'s + [=InterestGroupBiddingScriptRunnerGlobalScope/priority=]. + 1. [=list/Replace=] the [=interest group=] that has |ig|'s [=interest group/owner=] and + [=interest group/name=] in the browser’s [=interest group set=] with |ig|. + 1. If |global|'s [=InterestGroupBiddingScriptRunnerGlobalScope/priority signals=] + [=map/is not empty=]: + 1. [=map/For each=] |k| → |v| of |global|'s + [=InterestGroupBiddingScriptRunnerGlobalScope/priority signals=]: + 1. If |v| is null, [=map/remove=] |ig|'s [=interest group/priority signals overrides=][|k|]. + 1. Otherwise, [=map/set=] |ig|'s [=interest group/priority signals overrides=][|k|] to |v|. + 1. [=list/Replace=] the [=interest group=] that has |ig|'s [=interest group/owner=] and + [=interest group/name=] in the browser’s [=interest group set=] with |ig|. + 1. Let |generatedBid| be |global|'s [=InterestGroupBiddingScriptRunnerGlobalScope/bid=]. + 1. If |result| is a [=ECMAScript/normal completion=]: + 1. Let |generatedBidIDL| be the result of [=converted to an IDL value|converting=] + |result|'s \[[Value]] to a {{GenerateBidOutput}}. + 1. If no exception was [=exception/thrown=] in the previous step: + 1. Set |generatedBid| to the result of [=converting GenerateBidOutput to generated bid=] with |generatedBidIDL|, |ig|, |expectedCurrency|, |isComponentAuction|, and |global|'s [=InterestGroupBiddingScriptRunnerGlobalScope/group has ad components=]. + 1. Otherwise, set |generatedBid| to failure. + 1. If |generatedBid| is a [=generated bid=] and |generatedBid|'s [=generated bid/bid=]'s [=bid with currency/value=] ≤ 0, set |generatedBid| to failure. + 1. If |generatedBid| is null, set it to failure. + 1. If |generatedBid| is not failure: + 1. Set |generatedBid|'s [=generated bid/bid duration=] to |duration|. + 1. Set |generatedBid|'s [=generated bid/interest group=] to |ig|. + 1. Return |generatedBid|. +
+ +
+ To evaluate a scoring script given a [=string=] |script|, a [=string=] |adMetadata|, + a {{double}} |bidValue|, an {{AuctionAdConfig}} |auctionConfigIDL|, an [=ordered map=] + |trustedScoringSignals|, a {{ScoringBrowserSignals}} |browserSignals|, and an integer millisecond + [=duration=] |timeout|: + + 1. Let |realm| be the result of [=creating a new script runner realm=] given + {{InterestGroupScoringScriptRunnerGlobalScope}}. + 1. Let |browserSignalsJS| be |browserSignals| [=converted to ECMAScript values=]. + 1. Let |auctionConfigJS| be |auctionConfigIDL| [=converted to ECMAScript values=]. + 1. Let |trustedScoringSignalsJS| be |trustedScoringSignals| [=converted to ECMAScript values=]. + 1. Return the result of [=evaluating a script=] with |realm|, |script|, "`scoreAd`", + «|adMetadata|, |bidValue|, |auctionConfigJS|, |trustedScoringSignalsJS|, |browserSignalsJS|», + and |timeout|. +
+ +
+ To evaluate a reporting script given a [=string=] |script|, a [=string=] + |functionName|, and a [=list=] of arguments |arguments|: + + 1. Let |realm| be the result of [=creating a new script runner realm=] given + {{InterestGroupReportingScriptRunnerGlobalScope}}. + 1. Let |global| be |realm|'s [=realm/global object=]. + 1. Let |argumentsJS| be the result of [=converting a Web IDL arguments list to an ECMAScript + arguments list|converting=] |arguments| to an ECMAScript arguments list. If this + [=exception/throws=] an exception, return « "null", null, null ». + 1. Let |result| be the result of [=evaluating a script=] with |realm|, |script|, + |functionName|, |argumentsJS|, and 50 milliseconds. + 1. If |result| is an [=ECMAScript/abrupt completion=], return « "null", null, null ». + 1. Let |resultJSON| be "null". + 1. If |functionName| is "`reportResult`", then set |resultJSON| to the result of + [=serializing a JavaScript value to a JSON string=] given |result|. + + Note: Consider a return value that can't be converted to JSON a valid result, so if an + exception was [=exception/thrown=] in the previous step, keep |resultJSON| as "null". + 1. Return « |resultJSON|, |global|'s [=InterestGroupReportingScriptRunnerGlobalScope/report url=], + |global|'s [=InterestGroupReportingScriptRunnerGlobalScope/reporting beacon map=] ». +
+ +
+ To evaluate a script with a [=ECMAScript/realm=] |realm|, [=string=] |script|, [=string=] + |functionName|, a [=list=] |arguments|, and an integer millisecond duration |timeout|, run these steps. + They return a [=ECMAScript/Completion Record=], which is either an [=ECMAScript/abrupt completion=] (in + the case of a parse failure or execution error), or a [=ECMAScript/normal completion=] populated with the + [=ECMAScript/ECMAScript language value=] result of invoking |functionName|. + + 1. [=Assert=] that these steps are running [=in parallel=]. + + 1. Let |global| be |realm|'s [=realm/global object=], and run these steps in |realm|'s [=realm/agent=]: + + 1. Let |result| be [$ParseScript$](|script|, |realm|, `empty`). Note: The resulting [=ECMAScript/Script Record=] will have no \[[HostDefined]] component, unlike traditional [=scripts=] on the web platform. @@ -1335,18 +2248,22 @@ Each {{InterestGroupReportingScriptRunnerGlobalScope}} has a stack|JavaScript execution context stack=]; it is now the [=ECMAScript/running execution context|running JavaScript execution context=]. - 1. Let |evaluationStatus| be the result of [=ScriptEvaluation=](result). + 1. Let |evaluationStatus| be the result of [$ScriptEvaluation$](result). - 1. If |evaluationStatus| is an [=abrupt completion=], jump to the step labeled return. + 1. If |evaluationStatus| is an [=ECMAScript/abrupt completion=], jump to the step labeled + return. - 1. Let |F| be [$Get$](|global|, |functionName|). If that returns a [=throw completion=], set - |finalCompletion| to |F| and jump to the step labeled return. + 1. Let |F| be [$Get$](|global|, |functionName|). If that returns a [=ECMAScript/throw completion=], + set |finalCompletion| to |F| and jump to the step labeled + return. 1. Set |finalCompletion| be [=ECMAScript/Completion Record|Completion=]([$Call$](F, `undefined`, |arguments|)). + In |timeout| milliseconds, if the invocation of [$Call$] has not completed, + [=immediately=] interrupt the execution and set |finalCompletion| to a new + [=ECMAScript/throw completion=] given null. + 1. Return: at this point |finalCompletion| will be set to a [=ECMAScript/Completion Record=]. @@ -1369,97 +2286,111 @@ specification are those that explicitly list the global names provided here. interface InterestGroupScriptRunnerGlobalScope { }; + +### InterestGroupBiddingScriptRunnerGlobalScope ### {#bidding-global-scope} + +
 [Exposed=InterestGroupBiddingScriptRunnerGlobalScope,
  Global=(InterestGroupScriptRunnerGlobalScope,
          InterestGroupBiddingScriptRunnerGlobalScope)]
 interface InterestGroupBiddingScriptRunnerGlobalScope
         : InterestGroupScriptRunnerGlobalScope {
-  boolean setBid();
-  boolean setBid(GenerateBidOutput generateBidOutput);
+  boolean setBid(optional 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);
+  undefined setPrioritySignalsOverride(DOMString key, optional double? priority);
 };
 
 dictionary AdRender {
   required DOMString url;
-  required DOMString width;
-  required DOMString height;
+  DOMString width;
+  DOMString height;
 };
 
 dictionary GenerateBidOutput {
-  required double bid;
-  required (DOMString or AdRender) adRender;
+  double bid = -1;
+  DOMString bidCurrency;
+  (DOMString or AdRender) render;
   any ad;
   sequence<(DOMString or AdRender)> adComponents;
   double adCost;
-  double modelingSignals;
+  unrestricted double modelingSignals;
   boolean allowComponentAuction = false;
 };
+
 
-
- The setBid() method steps are: +Each {{InterestGroupBiddingScriptRunnerGlobalScope}} has a +
+: bid +:: A [=generated bid=] +: priority +:: Null or a {{double}} +: priority signals +:: An [=ordered map=] whose [=map/keys=] are [=strings=] and whose [=map/values=] are {{double}} or null. +: interest group +:: An [=interest group=] +: expected currency +:: A [=currency tag=] +: is component auction +:: A [=boolean=] +: group has ad components +:: A [=boolean=] + +
- 1. Set [=this=]'s [=relevant global object=]'s [=InterestGroupBiddingScriptRunnerGlobalScope/bid=] - to null. - 1. Return true. -
-To convert GenerateBidOutput to generated bid given a {{GenerateBidOutput}} |generateBidOutput|, -an [=interest group=] |ig|, a [=boolean=] |isComponentAuction| and a [=boolean=] |groupHasAdComponents|: - 1. If |generateBidOutput|["{{GenerateBidOutput/bid}}"] is less than or equal to 0, return failure. +To convert GenerateBidOutput to generated bid given a {{GenerateBidOutput}} +|generateBidOutput|, an [=interest group=] |ig|, a [=currency tag=] |expectedCurrency|, a [=boolean=] +|isComponentAuction| and a [=boolean=] |groupHasAdComponents|: + 1. Let |bid| be a new [=generated bid=]. + 1. If |generateBidOutput|["{{GenerateBidOutput/bid}}"] ≤ 0: + 1. Set |bid|'s [=generated bid/bid=] to a [=bid with currency=] with [=bid with currency/value=] |generateBidOutput|["{{GenerateBidOutput/bid}}"] and [=bid with currency/currency=] null. + 1. Return |bid|. + 1. If |generateBidOutput|["{{GenerateBidOutput/render}}"] does not [=map/exist=], return failure. 1. If |isComponentAuction| is true, and |generateBidOutput|["{{GenerateBidOutput/allowComponentAuction}}"] is false: 1. Return failure. - 1. Let |bid| be a new [=generated bid=]. - 1. Set |bid|'s [=generated bid/bid=] to |generateBidOutput|["{{GenerateBidOutput/bid}}"]. + 1. Let |bidCurrency| be null. + 1. If |generateBidOutput|["{{GenerateBidOutput/bidCurrency}}"] is specified: + 1. If the result of [=checking whether a string is a valid currency tag=] on + |generateBidOutput|["{{GenerateBidOutput/bidCurrency}}"] is true: + 1. Set |bidCurrency| to |generateBidOutput|["{{GenerateBidOutput/bidCurrency}}"] + 1. Otherwise return failure. + 1. If the result of [=checking a currency tag=] with |expectedCurrency| and |bidCurrency| is + false, return failure. + 1. Set |bid|'s [=generated bid/bid=] to a [=bid with currency=] with [=bid with currency/value=] + |generateBidOutput|["{{GenerateBidOutput/bid}}"] and [=bid with currency/currency=] |bidCurrency|. 1. If |generateBidOutput|["{{GenerateBidOutput/ad}}"] [=map/exists=]: 1. Let |adJSON| be the result of [=serializing a JavaScript value to a JSON string=], given |generateBidOutput|["{{GenerateBidOutput/ad}}"]. 1. If |adJSON| is failure, return failure. 1. Set |bid|'s [=generated bid/ad=] to |adJSON|. 1. Let |adDescriptor| be a new [=ad descriptor=]. - 1. If |generateBidOutput|["{{GenerateBidOutput/adRender}}"] is a {{DOMString}}: + 1. If |generateBidOutput|["{{GenerateBidOutput/render}}"] is a {{DOMString}}: 1. Let |adUrl| be the result of running the [=URL parser=] on - |generateBidOutput|["{{GenerateBidOutput/adRender}}"]. - 1. If |adUrl| is an error, return failure. + |generateBidOutput|["{{GenerateBidOutput/render}}"]. + 1. If |adUrl| is failure, return failure. 1. If [=validating an ad url=] given |adUrl|, |ig|, and false returns false, return failure. 1. Set |adDescriptor|'s [=ad descriptor/url=] to |adUrl|. 1. Otherwise: 1. Set |adDescriptor| to the result of [=converting an ad render=] given - |generateBidOutput|["{{GenerateBidOutput/adRender}}"], |ig| and false. + |generateBidOutput|["{{GenerateBidOutput/render}}"], |ig| and false. 1. If |adDescriptor| is failure, return failure. 1. Set |bid|'s [=generated bid/ad descriptor=] to |adDescriptor|. 1. If |generateBidOutput|["{{GenerateBidOutput/adComponents}}"] [=map/exists=]: 1. Let |adComponents| be |generateBidOutput|["{{GenerateBidOutput/adComponents}}"]. 1. Return failure if any of the following conditions hold: * |groupHasAdComponents| is false; - * |adComponents| is not an array; * |adComponents|'s size is greater than 20. 1. Let |adComponentDescriptors| be a new [=list=] of [=ad descriptors=]. 1. For |component| in |adComponents|: 1. Let |componentDescriptor| be a new [=ad descriptor=]. 1. If |component| is {{DOMString}}: 1. Let |componentUrl| be the result of running the [=URL parser=] on |component|. - 1. If |componentUrl| is an error, return failure. + 1. If |componentUrl| is failure, return failure. 1. If [=validating an ad url=] given |componentUrl|, |ig|, and true returns false, return failure. 1. Set |componentDescriptor|'s [=ad descriptor/url=] to |componentUrl|. 1. Otherwise: @@ -1473,15 +2404,15 @@ an [=interest group=] |ig|, a [=boolean=] |isComponentAuction| and a [=boolean=] |generateBidOutput|["{{GenerateBidOutput/adCost}}"]. 1. If |generateBidOutput|["{{GenerateBidOutput/modelingSignals}}"] [=map/exists=]: 1. Let |modelingSignals| be |generateBidOutput|["{{GenerateBidOutput/modelingSignals}}"]. - 1. If |modelingSignals| is greater than or equal to 0 and less than 4096: - 1. Set |bid|'s [=generated bid/modeling signals=] to |modelingSignals|. + 1. If |modelingSignals| ≥ 0 and |modelingSignals| < 4096: + 1. Set |bid|'s [=generated bid/modeling signals=] to the result of [=converted to an IDL value|converting=] the ECMAScript value represented by |modelingSignals| to an {{unsigned short}}. 1. Return |bid|.
To parse an AdRender dimension value given a [=string=] |input|: - + 1. Let |position| be a [=string/position variable=], initially pointing at the start of |input|. 1. [=Strip leading and trailing ASCII whitespace=] from |input|. 1. If |input| [=string/starts with=] "`0`" but [=string/is=] not "`0`" and does not @@ -1502,9 +2433,8 @@ an [=interest group=] |ig|, a [=boolean=] |isComponentAuction| and a [=boolean=] To convert an ad render given an {{AdRender}} |adRender|, an [=interest group=] |ig|, and a [=boolean=] |isComponent|: - 1. If |adRender|["{{AdRender/url}}"] does not [=map/exist=], return false. 1. Let |adUrl| be the result of running the [=URL parser=] on |adRender|["{{AdRender/url}}"]. - 1. If |adUrl| is an error, return failure. + 1. If |adUrl| is failure, return failure. 1. If [=validating an ad url=] given |adUrl|, |ig|, and |isComponent| returns false, return failure. 1. Let |adDescriptor| be a new [=ad descriptor=]. 1. Set |adDescriptor|'s [=ad descriptor/url=] to |adUrl|. @@ -1545,15 +2475,16 @@ an [=interest group=] |ig|, a [=boolean=] |isComponentAuction| and a [=boolean=] to null. 1. Let |ig| be [=this=]'s [=relevant global object=]'s [=InterestGroupBiddingScriptRunnerGlobalScope/interest group=]. + 1. Let |expectedCurrency| be [=this=]'s [=relevant global object=]'s + [=InterestGroupBiddingScriptRunnerGlobalScope/expected currency=]. 1. Let |bidToSet| be the result of [=converting GenerateBidOutput to generated bid=] with - |generateBidOutput|, |ig|, [=this=]'s [=relevant global object=]'s + |generateBidOutput|, |ig|, |expectedCurrency|, [=this=]'s [=relevant global object=]'s [=InterestGroupBiddingScriptRunnerGlobalScope/is component auction=], and [=this=]'s [=relevant global object=]'s [=InterestGroupBiddingScriptRunnerGlobalScope/group has ad components=]. - 1. If |bidToSet| is failure, return false. + 1. If |bidToSet| is failure, [=exception/throw=] a {{TypeError}}. 1. Set [=this=]'s [=relevant global object=]'s [=InterestGroupBiddingScriptRunnerGlobalScope/bid=] to |bidToSet|. - 1. Return true.
@@ -1577,6 +2508,42 @@ an [=interest group=] |ig|, a [=boolean=] |isComponentAuction| and a [=boolean=] [=InterestGroupBiddingScriptRunnerGlobalScope/priority signals=][|key|] to |priority|.
+### InterestGroupScoringScriptRunnerGlobalScope ### {#scoring-global-scope} + +
+[Exposed=InterestGroupScoringScriptRunnerGlobalScope,
+ Global=(InterestGroupScriptRunnerGlobalScope,
+         InterestGroupScoringScriptRunnerGlobalScope)]
+interface InterestGroupScoringScriptRunnerGlobalScope
+        : InterestGroupScriptRunnerGlobalScope {
+};
+
+
+ +### InterestGroupReportingScriptRunnerGlobalScope ### {#reporting-global-scope} + +
+[Exposed=InterestGroupReportingScriptRunnerGlobalScope,
+ Global=(InterestGroupScriptRunnerGlobalScope,
+         InterestGroupReportingScriptRunnerGlobalScope)]
+interface InterestGroupReportingScriptRunnerGlobalScope
+        : InterestGroupScriptRunnerGlobalScope {
+  undefined sendReportTo(DOMString url);
+  undefined registerAdBeacon(record<DOMString, USVString> map);
+};
+
+
+ +Each {{InterestGroupReportingScriptRunnerGlobalScope}} has a +
+: report url +:: Null or a [=URL=]. Defaulting to null. +: reporting beacon map +:: Null or an [=ordered map=] whose [=map/keys=] are [=strings=] and whose [=map/values=] are + [=URLs=]. Defaulting to null. + +
+
The sendReportTo(|url|) method steps are: @@ -1593,6 +2560,25 @@ an [=interest group=] |ig|, a [=boolean=] |isComponentAuction| and a [=boolean=] [=InterestGroupReportingScriptRunnerGlobalScope/report url=] to |parsedUrl|.
+
+ The registerAdBeacon(|map|) + method steps are: + + 1. If [=this=]'s [=relevant global object=]'s [=InterestGroupReportingScriptRunnerGlobalScope/ + reporting beacon map=] is not null, then [=exception/Throw=] a {{TypeError}}. + + 1. [=map/For each=] |url| of |map|'s [=map/values=]: + + 1. Let |parsedURL| be the result of running [=URL parser=] on |url|. + + 1. [=exception/Throw=] a {{TypeError}} if any of the following conditions hold: + * |parsedURL| is failure; + * |parsedURL|'s [=url/scheme=] is not "`https`". + + 1. Set [=this=]'s [=relevant global object=]'s [=InterestGroupReportingScriptRunnerGlobalScope/ + reporting beacon map=] to |map|. +
+ # Interest Group Updates # {#interest-group-updates} @@ -1612,7 +2598,7 @@ partial interface Navigator { The updateAdInterestGroups() method steps are: -1. [=In parallel=], run [=interest group update=] with +1. [=In parallel=], run [=interest group update=] with « [=relevant settings object=]'s [=environment/top-level origin=] »
@@ -1622,16 +2608,33 @@ The updateAdInterestGroups() method steps are: 1. [=list/For each=] |owner| of |owners|: 1. [=list/For each=] |originalInterestGroup| of the user agent's [=interest group set=] whose - [=interest group/owner=] is |owner| and [=interest group/next update after=] is before now: + [=interest group/owner=] is |owner| and [=interest group/next update after=] is before + the [=current wall time=]: 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|. - 1. Let |request| be the result of [=creating a request=] with |ig|'s - [=interest group/update url=], "`application/json`", and |owner|. + 1. Let |request| be a new [=request=] with the following properties: + : [=request/URL=] + :: |ig|'s [=interest group/update url=] + : [=request/header list=] + :: «`Accept`: `application/json`» + : [=request/client=] + :: `null` + : [=request/service-workers mode=] + :: "`none`" + : [=request/mode=] + :: "`no-cors`" + : [=request/referrer=] + :: "`no-referrer`" + : [=request/credentials mode=] + :: "`omit`" + : [=request/redirect mode=] + :: "`error`" 1. Let |update| be null. - 1. [=Fetch=] |request| with [=fetch/processResponseConsumeBody=] set to the following steps - given a [=response=] |response| and |responseBody|: + 1. [=Fetch=] |request| with [=fetch/useParallelQueue=] set to true, and + [=fetch/processResponseConsumeBody=] set to the following steps given a [=response=] |response| + and null, failure, or a [=byte sequence=] |responseBody|: 1. If [=validate fetching response=] with |response|, |responseBody| and "`application/json`" returns false, set |update| to failure and return. 1. Set |update| to |responseBody|. @@ -1640,7 +2643,7 @@ The updateAdInterestGroups() method steps are: 1. Let |parsedUpdate| be the result of [=parsing JSON bytes to an Infra value=], given |update|. 1. If |parsedUpdate| is failure, [=iteration/continue=]. 1. If |parsedUpdate| is not an [=ordered map=], [=iteration/continue=]. - 1. If |parsedUpdate|["`name`"] exists and doesn't match |ig|'s [=interest group/name=], + 1. If |parsedUpdate|["`name`"] exists and doesn't match |ig|'s [=interest group/name=], [=iteration/continue=]. 1. If |parsedUpdate|["`owner`"] exists and doesn't match |ig|'s [=interest group/owner=], [=iteration/continue=]. @@ -1661,9 +2664,9 @@ The updateAdInterestGroups() method steps are:
"`priorityVector`"
- 1. If |value| is null or an [=ordered map=] whose [=map/keys=] are - [=strings=] and whose [=map/values=] are {{double}}, set |ig|'s - [=interest group/priority vector=] to |value|. + 1. If |value| is null or an [=ordered map=] whose [=map/keys=] are [=strings=] and + whose [=map/values=] are {{double}}, set |ig|'s [=interest group/priority vector=] to + |value|. 1. Otherwise, jump to the step labeled Abort update.
"`prioritySignalsOverrides`" @@ -1716,7 +2719,7 @@ The updateAdInterestGroups() method steps are: * |parsedURL| [=includes credentials=]; * |parsedURL| [=url/fragment=] is not null. 1. Set |ig|'s |interestGroupField| to |parsedURL|. - +
"`trustedBiddingSignalsKeys`"
1. If |value| is a [=list=] of [=strings=], @@ -1756,13 +2759,19 @@ The updateAdInterestGroups() method steps are: [=serializing a JavaScript value to a JSON string=], given |ad|["{{AuctionAd/metadata}}"]. If this [=exception/throws=], jump to the step labeled Abort update. + 1. If |groupMember| is "`ads`": + 1. If |ad|["{{AuctionAd/buyerReportingId}}"] [=map/exists=] then set + |igAd|'s [=interest group ad/buyer reporting ID=] to it. + 1. If |ad|["{{AuctionAd/buyerAndSellerReportingId}}"] [=map/exists=] + then set |igAd|'s [=interest group ad/buyer and seller + reporting ID=] to it. 1. [=list/Append=] |igAd| to |ig|'s |interestGroupField|. - + - - 1. Set |ig|'s [=interest group/next update after=] to the current time plus 24 hours. - 1. [=list/Replace=] |originalInterestGroup| with |ig| in the browser's - [=interest group set=]. + + 1. Set |ig|'s [=interest group/next update after=] to the [=current wall time=] plus 24 hours. + 1. [=list/Replace=] the [=interest group=] that has |ig|'s [=interest group/owner=] and + [=interest group/name=] in the browser’s [=interest group set=] with |ig|. 1. Abort update: We jump here if some part of the [=interest group=] update failed. [=iteration/Continue=] to the next [=interest group=] update. @@ -1781,13 +2790,156 @@ Issue(WICG/turtledove#522): Move from "`*`" to "`self`". # Structures # {#structures} + + +dictionary PreviousWin { + required long long timeDelta; + required DOMString adJSON; +}; + +dictionary BiddingBrowserSignals { + required DOMString topWindowHostname; + required USVString seller; + required long joinCount; + required long bidCount; + + USVString topLevelSeller; + sequence<PreviousWin> prevWinsMs; + object wasmHelper; + unsigned long dataVersion; +}; + +dictionary ScoringBrowserSignals { + required DOMString topWindowHostname; + required USVString interestGroupOwner; + required USVString renderURL; + required unsigned long biddingDurationMsec; + required DOMString bidCurrency; + + unsigned long dataVersion; + sequence<USVString> adComponents; +}; + + +Note: {{ScoringBrowserSignals}}'s {{ScoringBrowserSignals/adComponents}} is {{undefined}} when +[=generated bid/ad component descriptors=] is null or [=list/is empty|an empty list=]. It cannot be +an [=list/is empty|empty list=]. + + + +dictionary ReportingBrowserSignals { + required DOMString topWindowHostname; + required USVString interestGroupOwner; + required USVString renderURL; + required double bid; + required double highestScoringOtherBid; + + DOMString bidCurrency; + DOMString highestScoringOtherBidCurrency; + USVString topLevelSeller; + USVString componentSeller; + + USVString buyerAndSellerReportingId; +}; + + +{{ReportingBrowserSignals}} includes browser signals both `reportResult()` and `reportWin()` get. +
+
{{ReportingBrowserSignals/topWindowHostname}} +
[=environment/Top-level origin=]'s [=origin/host=] +
{{ReportingBrowserSignals/interestGroupOwner}} +
The winning [=interest group=]'s [=interest group/owner=]. +
{{ReportingBrowserSignals/renderURL}} +
The render URL returned by "`generateBid()`". It is + [=query reporting ID k-anonymity count|k-anonymous=] +
{{ReportingBrowserSignals/bid}} +
[=round a value|Stochastically rounded=] winning bid. This is always in the bidder's own + currency +
{{ReportingBrowserSignals/highestScoringOtherBid}} +
The [=round a value|stochastically rounded value=] of the bid that got the second highest score, or 0 if it's + + not available. 0 for top-level auctions with components +
{{ReportingBrowserSignals/bidCurrency}} +
The currency the {{ReportingBrowserSignals/bid}} is in +
{{ReportingBrowserSignals/highestScoringOtherBidCurrency}} +
The currency the {{ReportingBrowserSignals/highestScoringOtherBid}} is in +
{{ReportingBrowserSignals/topLevelSeller}} +
Copied from [=leading bid info/top level seller=] +
{{ReportingBrowserSignals/componentSeller}} +
Copied from [=leading bid info/component seller=] +
{{ReportingBrowserSignals/buyerAndSellerReportingId}} +
Set if the winning ad had a [=interest group ad/buyer and seller reporting ID=] set in its listing in the interest group, and that value was [=query reporting ID k-anonymity count|jointly k-anonymous=] combined with interest group owner, bidding script URL, and ad creative URL. +
+ + + +dictionary ReportResultBrowserSignals : ReportingBrowserSignals { + required double desirability; + + DOMString topLevelSellerSignals; + double modifiedBid; + unsigned long dataVersion; +}; + + +
+
{{ReportResultBrowserSignals/desirability}} +
The [=round a value|stochastically rounded value=] of the score returned by "`scoreAd()`" for + the winning bid +
{{ReportResultBrowserSignals/topLevelSellerSignals}} +
Metadata returned by the top-level seller's "`reportResult()`", as JSON +
{{ReportResultBrowserSignals/modifiedBid}} +
The [=round a value|stochastically rounded value=] of the bid value returned by the component + seller's "`scoreAd()`" method +
{{ReportResultBrowserSignals/dataVersion}} +
Set to the value of the [:Data-Version:] header from the trusted + scoring signals server, if any. +
+ + + +dictionary ReportWinBrowserSignals : ReportingBrowserSignals { + double adCost; + USVString seller; + boolean madeHighestScoringOtherBid; + DOMString interestGroupName; + DOMString buyerReportingId; + unsigned short modelingSignals; + unsigned long dataVersion; +}; + + +
+
{{ReportWinBrowserSignals/adCost}} +
[=round a value|Stochastically rounded=] winner's [=generated bid/ad cost=]. +
{{ReportWinBrowserSignals/seller}} +
The origin of the seller running the ad auction +
{{ReportWinBrowserSignals/madeHighestScoringOtherBid}} +
True if the interest group owner was the only bidder that made bids with the second highest + score +
{{ReportWinBrowserSignals/buyerReportingId}} +
Set if the winning ad had a [=interest group ad/buyer reporting ID=] but not a [=interest group ad/buyer and seller reporting ID=] set in its listing in the interest group, and that value was [=query reporting ID k-anonymity count|jointly k-anonymous=] combined with interest group owner, bidding script URL, and ad creative URL. + +
{{ReportWinBrowserSignals/interestGroupName}} +
Only set if the tuple of interest group owner, name, bidding script URL and ad creative URL + + were [=query reporting ID k-anonymity count|jointly k-anonymous=], and the + winning ad had neither [=interest group ad/buyer and seller reporting ID=] + nor [=interest group ad/buyer reporting ID=] set in its listing in the interest group. +
{{ReportWinBrowserSignals/modelingSignals}} +
A 0-4095 integer (12-bits) passed to `reportWin()`, with noising +
{{ReportWinBrowserSignals/dataVersion}} +
Only set if the Data-Version header was provided in the response headers from the trusted + bidding signals server +
+

Interest group

-An interest group is a [=struct=] with the following items: +An interest group is a [=struct=] with the following [=struct/items=]:
: expiry -:: A point in time at which the browser will forget about this interest group. +:: A [=moment=] at which the browser will forget about this interest group. : owner :: An [=origin=]. Frames that join interest groups owned by [=interest group/owner=] must either be served from [=interest group/owner=], or another origin delegated by [=interest group/owner=] (See @@ -1810,8 +2962,7 @@ An interest group is a [=struct=] with the following items: :: Null or an [=ordered map=] whose [=map/keys=] are [=strings=] and whose [=map/values=] are {{double}}. Overrides the {{AuctionAdConfig}}'s corresponding priority signals. : execution mode -:: A [=string=], initially "compatibility". Acceptable values are - "compatibility" and "group-by-origin". +:: "`compatibility`" or "`group-by-origin`". TODO: Define spec for these execution modes, link to it from here and explain these modes. : bidding url :: Null or a [=URL=]. The URL to fetch the buyer's JavaScript from. @@ -1856,26 +3007,24 @@ An interest group is a [=struct=] with the following items: :: An [=origin=]. The top level page origin from where the interest group was joined. : join counts :: A [=list=] containing [=tuples=] 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 + is calculated based on UTC time. The join count is a count of the number of times {{Navigator/joinAdInterestGroup()}} was called for this interest group on the corresponding day. : bid counts :: A [=list=] containing [=tuples=] 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 + is calculated based on UTC time. The bid count is a count of the number of times the bid calculated during {{Navigator/runAdAuction()}} was greater than 0. : previous wins -:: A [=list=] containing [=tuples=] of the time and the corresponding - [=interest group ad=] for each instance that this interest group won an - auction. +:: A [=list=] of [=previous wins=]. : next update after -:: A point in time at which the browser will permit updating this interest group. See +:: A [=moment=] at which the browser will permit updating this interest group. See [interest group updates](#interest-group-updates).

Interest group ad

-An interest group ad is a [=struct=] with the following items: +An interest group ad is a [=struct=] with the following [=struct/items=]:
: render url @@ -1884,9 +3033,41 @@ An interest group ad is a [=struct=] with the following items: <{iframe}> (or a <{fencedframe}>). : metadata :: Null or a [=string=]. Extra arbitary information about this ad, passed to `generateBid()`. +: buyer reporting ID +:: Null or a [=string=]. Will be passed in place of interest group name to `reportWin()`, subject to k-anonymity checks. +: buyer and seller reporting ID +:: Null or a [=string=]. Will be passed in place of interest group name or [=interest group ad/buyer reporting ID=] to `reportWin()` and `reportResult()`, subject to k-anonymity checks.
+

Currency tag

+A currency tag is a [=string=] containing exactly 3 upper-case ASCII letters, or null. The null +value is used to denote that the currency is unspecified. + +
+ To serialize a currency tag given a [=currency tag=] |currency|: + 1. If |currency| is null, return "???". + 1. Return |currency|. +
+ +
+ To check whether a string is a valid currency tag given [=string=] |currencyString|: + 1. If [=string/length=] of |currencyString| is not 3, return false. + 1. If |currencyString|[0] is not a [=ASCII upper alpha=] code point, return false. + 1. If |currencyString|[1] is not a [=ASCII upper alpha=] code point, return false. + 1. If |currencyString|[2] is not a [=ASCII upper alpha=] code point, return false. + 1. Return true. +
+ +
+ To check a currency tag given the [=currency tags=] |expected| and |actual|: + + 1. If |expected| is null, return true. + 1. If |actual| is null, return true. + 1. If |actual| is equal to |expected|, return true. + 1. Return false. +
+

Auction config

An auction config is a [=struct=] with the following items: @@ -1911,27 +3092,27 @@ An auction config is a [=struct=] with the following items: [=same origin=] with [=auction config/seller=].

: interest group buyers -:: Null or a [=list=] of [=origin=]. +:: Null or a [=list=] of [=origins=]. Owners of interest groups allowed to participate in the auction. Each [=origin's=] [=origin/scheme=] must be "https". : auction signals -:: Null or a [=string=] or a {{Promise}}. - Opaque JSON data passed to both sellers' and buyers' script runners. +:: Null or a [=string=] or a {{Promise}} or failure. + 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. +:: Null or a [=string=] or a {{Promise}} or failure. + Opaque JSON data passed to the seller's [=script runner=]. : seller timeout :: A [=duration=] in milliseconds, initially 50 milliseconds. Restricts the runtime of the seller's `scoreAd()` script. If scoring does not complete before the timeout, the bid being scored is not considered further. : per buyer signals -:: Null or an [=ordered map=] whose [=map/keys=] are [=origins=] and whose [=map/values=] are - [=strings=]. +:: Null or a {{Promise}} or failure or an [=ordered map=] whose [=map/keys=] are [=origins=] and + whose [=map/values=] are [=strings=]. [=map/Keys=] are buyers and must be valid HTTPS origins. [=map/Values=] are opaque JSON data - passed to corresponding buyer's script runner. + passed to corresponding buyer's [=script runner=]. : per buyer timeouts -:: Null or an [=ordered map=] whose [=map/keys=] are [=origins=] and whose [=map/values=] are - [=durations=] in milliseconds. +:: Null or a {{Promise}} or failure or an [=ordered map=] whose [=map/keys=] are [=origins=] and + whose [=map/values=] are [=durations=] in milliseconds. [=map/Keys=] are buyers and must be valid HTTPS origins. [=map/Values=] restrict the runtime of corresponding buyer's `generateBid()` script. If the timeout expires, only the bid submitted via `setBid()` is considered. @@ -1979,9 +3160,78 @@ An auction config is a [=struct=] with the following items: :: Null or an {{unsigned short}}, initially null. Optional identifier for an experiment group to support coordinated experiments with buyers' trusted servers for buyers without a specified experiment group. +: pending promise count +:: An integer, initially 0. The number of [=auction config/auction signals=], + [=auction config/per buyer signals=], [=auction config/per buyer currencies=], + [=auction config/per buyer timeouts=], directFromSellerSignals, or [=auction config/seller signals=] + whose {{Promise}}s are not yet resolved. +: config idl +:: {{AuctionAdConfig}}. +: resolve to config +:: A [=boolean=] or a {{Promise}}, initially false. + Whether the ad should be returned as a {{FencedFrameConfig}}, or otherwise as a [=urn uuid=]. +: seller currency +:: A [=currency tag=]. Specifies the currency bids returned by `scoreAd()` are expected to use, and + which reporting for this auction will agree on. + +: per buyer currencies +:: A {{Promise}} or failure or an [=ordered map=] whose [=map/keys=] are [=origins=] and whose + [=map/values=] are [=currency tags=]. Specifies the currency bids returned by `generateBid()` or + `scoreAd()` in component auctions are expected to use. The initial value is an empty map. + +: all buyers currency +:: A [=currency tag=]. Specifies the currency bids returned by `generateBid()` or `scoreAd()` in + component auctions are expected to use if [=auction config/per buyer currencies=] does not specify + a particular value. + +
+To wait until configuration input promises resolve given an [=auction config=] |auctionConfig|: +1. Wait until |auctionConfig|'s [=auction config/pending promise count=] is 0. +1. [=Assert=] |auctionConfig|'s [=auction config/auction signals=], [=auction config/seller signals=], + [=auction config/per buyer signals=], [=auction config/per buyer currencies=], and + [=auction config/per buyer timeouts=] are not {{Promise}}s. +1. If |auctionConfig|'s [=auction config/auction signals=], [=auction config/seller signals=], + [=auction config/per buyer signals=], [=auction config/per buyer currencies=] or + [=auction config/per buyer timeouts=] is failure, return failure. +1. TODO: the above two steps should also check directFromSellerSignals once something handles it. + +
+ +
+To recursively wait until configuration input promises resolve given an [=auction config=] |auctionConfig|: +1. [=list/For each=] |componentAuctionConfig| in |auctionConfig|'s [=auction config/component auctions=]: + 1. If the result of [=waiting until configuration input promises resolve=] given |componentAuctionConfig| is failure, return failure. +1. Return the result of [=waiting until configuration input promises resolve=] given |auctionConfig|. + +
+ +
+To handle an input promise in configuration given an [=auction config=] |auctionConfig|, +a {{Promise}} |p|, and two sequences of steps, covering the parsing of the value and error-handling: +1. Increment |auctionConfig|'s [=auction config/pending promise count=]. +1. Let |resolvedAndTypeChecked| be the promise representing performing the following steps [=upon fulfillment=] of |p| with |result|: + 1. Execute the steps to be run for parsing of the value given |result|. + 1. If no exception was [=exception/thrown=] in the previous step: + 1. Decrement |auctionConfig|'s [=auction config/pending promise count=]. +1. [=Upon rejection=] of |resolvedAndTypeChecked|: + 1. Execute the steps for error-handling. + 1. Decrement |auctionConfig|'s [=auction config/pending promise count=]. + +
+ +
+ To look up per-buyer currency given an [=auction config=] |auctionConfig|, and an [=origin=] |buyer|: + + 1. Let |perBuyerCurrency| be |auctionConfig|'s [=auction config/all buyers currency=] + 1. Assert: |auctionConfig|'s [=auction config/per buyer currencies=] is an [=ordered map=]. + 1. If |auctionConfig|'s [=auction config/per buyer currencies=][|buyer|] [=map/exists=]: + 1. Set |perBuyerCurrency| to |auctionConfig|'s [=auction config/per buyer currencies=][|buyer|]. + 1. Return |perBuyerCurrency| +
+

Per buyer bid generator

A per buyer bid generator is an [=ordered map=] whose [=map/keys=] are [=URLs=] representing @@ -1994,6 +3244,31 @@ A per signals url bid generator is an [=ordered map=] whose [=map/keys=] are [=o representing [=interest group/joining origins=], and whose [=map/values=] are [=lists=] of [=interest groups=]. + +

Previous win

+ +The [=interest group=]'s auction win history, to allow on-device frequency capping. + + +
+: time +:: A [=moment=]. Approximate time the [=interest group=] won an auction. +: ad json +:: A [=string=]. A JSON serialized object corresponding to the ad that won the auction. + +
+ +

Bid with currency

+Numeric value of a bid and the currency it is in. + +
+: value +:: A {{double}}. The value of the bid. +: currency +:: A [=currency tag=]. The currency the bid is in. + +
+

Generated bid

The output of running a Protected Audience `generateBid()` script, which needs to be scored by @@ -2001,8 +3276,11 @@ the seller.
: bid -:: A {{double}}. If the bid is zero or negative, then this interest group will not participate in - the auction. +:: A [=bid with currency=]. If the [=bid with currency/value=] is zero or negative, then this + [=interest group=] will not participate in the auction. +: bid in seller currency +:: A {{double}} or null. An equivalent of the original bid in seller's currency. This is either the + original bid if the currency already matched, or a conversion provided by `scoreAd()`. : ad :: A [=string=]. JSON string to be passed to the scoring function. : ad descriptor @@ -2013,14 +3291,14 @@ the seller. [=interest group/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. + `reportWin()`. Negative values will be ignored and not passed. Will be + [=round a value|stochastically rounded=] when passed. : modeling signals :: Null or an {{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. +:: Null or a [=bid with currency=]. 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 `generateBid()`. @@ -2057,27 +3335,63 @@ Width and height of an ad.

Score ad output

-The output of running a Protected Audience `scoreAd()` script. +The output of running a Protected Audience `scoreAd()` script, is represented using the following type: +
+dictionary ScoreAdOutput {
+  required double desirability;
+  double bid;
+  DOMString bidCurrency;
+  double incomingBidInSellerCurrency;
+  boolean allowComponentAuction = false;
+};
+
+Either a dictionary of this type, or a {{double}}, are handled as the return values. -
-: desirability -:: A {{double}}. - Numeric score of the bid. Must be positive or the ad will be rejected. The winner of the auction +The meanings of the fields are as follows: +
+
{{ScoreAdOutput/desirability}} +
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. - +
{{ScoreAdOutput/bid}} +
Only relevant if this is a component auction. 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. +
{{ScoreAdOutput/bidCurrency}} +
Only relevant if this is a component auction and {{ScoreAdOutput/bid}} is set. Specifies which + currency the {{ScoreAdOutput/bid}} field is in. +
{{ScoreAdOutput/incomingBidInSellerCurrency}} +
Provides a conversion of the incoming bid to auction's seller currency. This is different from + {{ScoreAdOutput/bid}} which is the bid the component auction itself produces. +
{{ScoreAdOutput/allowComponentAuction}} +
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.
+TODO: This also has an ad field, which should behave similar to the way {{ScoreAdOutput/bid}} +affects [=generated bid/modified bid=], and then affecting the adMetadata parameter to scoreAd. + +
+ +To process scoreAd output given an [=ECMAScript/Completion Record=] |result|: + 1. If |result| is an an [=ECMAScript/abrupt completion=], return failure. + 1. If |result|.\[[Value]] is a [=Number=]: + 1. Let |checkedScore| be the result of [=converted to an IDL value|converting=] + |result|.\[[Value]] to a {{double}}. + 1. If an exception was [=exception/thrown=] in the previous step, return failure. + 1. Let |resultIDL| be a new {{ScoreAdOutput}}. + 1. Set |resultIDL|'s {{ScoreAdOutput/desirability}} to |checkedScore|. + 1. Return |resultIDL|. + 1. Let |resultIDL| be the result of [=converted to an IDL value|converting=] + |result|.\[[Value]] to a {{ScoreAdOutput}}. + 1. If an exception was [=exception/thrown=] in the previous step, return failure. + 1. If |resultIDL|["{{ScoreAdOutput/bidCurrency}}"] [=map/exists=] and result of + [=checking whether a string is a valid currency tag=] applied to + |resultIDL|["{{ScoreAdOutput/bidCurrency}}"] is false: + 1. Return failure. + 1. Return |resultIDL|. +
+

Leading bid info

Information of the auction's leading bid so far when ranking scored bids. @@ -2105,7 +3419,7 @@ Information of the auction's leading bid so far when ranking scored bids. : highest scoring other bid owner :: Null or an [=origin=], initially null. The interest group owner that made bids with the `second highest score`. Set to null if there are more than one owners made bids with the - `second highest score`. + `second highest score`. : top level seller :: Null or a [=string=]. The seller in the top level auction. Only set for component auctions, null otherwise. @@ -2127,7 +3441,26 @@ Information of the auction's leading bid so far when ranking scored bids. 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. +: buyer reporting result +:: Null or a [=reporting result=], initially null. +: seller reporting result +:: Null or a [=reporting result=], initially null. +: component seller reporting result +:: Null or a [=reporting result=], initially null. + +
+ +A reporting result is a [=struct=] with the following [=struct/items=]: + +
+ : report url + :: Null or a [=URL=], initially null. Set by + {{InterestGroupReportingScriptRunnerGlobalScope/sendReportTo(url)}}. + : reporting beacon map + :: Null or an [=ordered map=] whose [=map/keys=] are [=strings=] and whose [=map/values=] are + [=URLs=], initially null. Set by + {{InterestGroupReportingScriptRunnerGlobalScope/registerAdBeacon(map)}}.
# Privacy Considerations # {#privacy-considerations} @@ -2163,7 +3496,7 @@ JavaScript is controlled and limited as follows: {{Navigator/joinAdInterestGroup()}}. - URL schemes are required to be HTTPS. - Redirects are disallowed. -- Responses are required to contain the `X-Allow-Protected-Audience: true` header. +- Responses are required to contain the `X-Allow-Protected-Audience: ?1` header. - Fetches are uncredentialed. Protected Audience has the browser pass in several “browserSignals” to the bidding script that give the script