@@ -1527,6 +1527,14 @@ The <dfn method for=Attribution>measureConversion(|options|)</dfn> method steps
15271527
152815281. Let |implicitInputs| be the result of [=obtaining the implicit API inputs=] from [=this=] .
152915291. [=Assert=] : |implicitInputs| is not null.
1530+ 1. Return the result of running [=measure a conversion=] with |options| and |implicitInputs|.
1531+
1532+ </div>
1533+
1534+ <div algorithm>
1535+ To <dfn>measure a conversion</dfn> given {{AttributionConversionOptions}} |options|
1536+ and [=implicit API inputs=] |implicitInputs|:
1537+
153015381. Let |document| be |implicitInputs|'s [=implicit API inputs/associated document=] .
153115391. Let |realm| be |document|'s [=relevant realm=] .
153215401. If |document| is not [=allowed to use=] the [=policy-controlled feature=] named
@@ -1917,6 +1925,8 @@ if the user has opted out of collection of diagnostic data.
19171925
19181926# HTTP API # {#http-api}
19191927
1928+ ## Saving impressions ## {#http-api-impressions}
1929+
19201930\`<dfn http-header><code>Save-Impression</code></dfn> \` is a
19211931[=structured header/dictionary|Dictionary Structured Header=]
19221932set on a response requesting that the user agent invoke the
@@ -2016,6 +2026,201 @@ To <dfn noexport>parse a `Save-Impression` header</dfn> given a [=header value=]
20162026
20172027</div>
20182028
2029+ ## Measuring Conversions ## {#http-api-conversions}
2030+
2031+ \`<dfn http-header><code>Measure-Conversion</code></dfn> \` is a
2032+ [=structured header/dictionary|Dictionary Structured Header=]
2033+ set on a response requesting that the user agent invoke the
2034+ <a method for=Attribution>measureConversion()</a> API.
2035+
2036+ <div class=example id=ex-measure-conversion-header>
2037+ This is the HTTP equivalent of <a href=#ex-measure-conversion>the JavaScript `measureConversion` example</a> ,
2038+ with the addition of a `report-url` to which the resulting report will be `POST`ed:
2039+ <xmp highlight=http>
2040+ Measure-Conversion: aggregation-service="https://aggregator.example/tee", histogram-size=20, epsilon=1.0, lookback-days=14, impression-sites=("publisher.example" "other.example"), impression-callers=("ad-tech.example"), match-values=(2), credit=(0.25 0.25 0.5), value=3, max-value=7, report-url="https://report-handler.example/foo"
2041+ </xmp>
2042+ </div>
2043+
2044+ The following keys are defined, corresponding to the members of
2045+ the {{AttributionConversionOptions}} dictionary passed to
2046+ <a method for=Attribution>measureConversion()</a> . Default values for omitted
2047+ optional keys are populated the same way as the corresponding
2048+ {{AttributionConversionOptions}} field.
2049+
2050+ <dl dfn-for=measure-conversion>
2051+ <dt> <dfn noexport><code>aggregation-service</code></dfn> </dt>
2052+ <dd>
2053+ Value of <a dict-member for=AttributionConversionOptions>aggregationService</a> ,
2054+ a [=structured header/string=] . This key is required.
2055+ </dd>
2056+ <dt> <dfn noexport><code>epsilon</code></dfn> </dt>
2057+ <dd>
2058+ Value of <a dict-member for=AttributionConversionOptions>epsilon</a> ,
2059+ a positive [=structured header/decimal=] . This key is optional.
2060+ </dd>
2061+ <dt> <dfn noexport><code>histogram-size</code></dfn> </dt>
2062+ <dd>
2063+ Value of <a dict-member for=AttributionConversionOptions>histogramSize</a> ,
2064+ a positive [=structured header/integer=] . This key is required.
2065+ </dd>
2066+ <dt> <dfn noexport><code>lookback-days</code></dfn> </dt>
2067+ <dd>
2068+ Value of <a dict-member for=AttributionConversionOptions>lookbackDays</a> ,
2069+ a positive [=structured header/integer=] . This key is optional.
2070+ </dd>
2071+ <dt> <dfn noexport><code>match-values</code></dfn> </dt>
2072+ <dd>
2073+ Value of <a dict-member for=AttributionConversionOptions>matchValues</a> ,
2074+ an [=structured header/inner list=] containing non-negative [=structured header/integer|integers=] .
2075+ This key is optional.
2076+ </dd>
2077+ <dt> <dfn noexport><code>impression-sites</code></dfn> </dt>
2078+ <dd>
2079+ Value of <a dict-member for=AttributionConversionOptions>impressionSites</a> ,
2080+ an [=structured header/inner list=] containing [=structured header/string|strings=] .
2081+ Each string value includes a domain name using A-labels only;
2082+ [[RFC5890|Internationalized Domain Names]] therefore need to use [[RFC3492|punycode]] .
2083+ This key is optional.
2084+ </dd>
2085+ <dt> <dfn noexport><code>impression-callers</code></dfn> </dt>
2086+ <dd>
2087+ Value of <a dict-member for=AttributionConversionOptions>impressionCallers</a> ,
2088+ an [=structured header/inner list=] containing [=structured header/string|strings=] .
2089+ Each string value includes a domain name using A-labels only;
2090+ [[RFC5890|Internationalized Domain Names]] therefore need to use [[RFC3492|punycode]] .
2091+ This key is optional.
2092+ </dd>
2093+ <dt> <dfn noexport><code>credit</code></dfn> </dt>
2094+ <dd>
2095+ Value of <a dict-member for=AttributionConversionOptions>credit</a> ,
2096+ an [=structured header/inner list=] containing positive [=structured header/decimal|decimals=]
2097+ or positive [=structured header/integer|integers=] . This key is optional.
2098+ </dd>
2099+ <dt> <dfn noexport><code>value</code></dfn> </dt>
2100+ <dd>
2101+ Value of <a dict-member for=AttributionConversionOptions>value</a> ,
2102+ a positive [=structured header/integer=] . This key is optional.
2103+ </dd>
2104+ <dt> <dfn noexport><code>max-value</code></dfn> </dt>
2105+ <dd>
2106+ Value of <a dict-member for=AttributionConversionOptions>maxValue</a> ,
2107+ a positive [=structured header/integer=] . This key is optional.
2108+ </dd>
2109+ <dt> <dfn noexport><code>report-url</code></dfn> </dt>
2110+ <dd>
2111+ A [=structured header/string=] containing the [=potentially trustworthy URL=]
2112+ to which the resulting report, if any, will be `POST`ed. The URL may be
2113+ relative to the response URL. Its [=url/scheme=] must be "`https`".
2114+ This key is required.
2115+ </dd>
2116+ </dl>
2117+
2118+ <div algorithm>
2119+ To <dfn noexport>parse a `Measure-Conversion` header</dfn> given a [=header value=]
2120+ |input| and [=URL=] |baseUrl|, run these steps:
2121+
2122+ 1. Let |dict| be the result of [=structured header/parsing structured fields=]
2123+ with <var ignore> input_bytes</var> set to |input| and
2124+ <var ignore> field_type</var> set to "`dictionary`".
2125+ 1. If parsing failed, return an error.
2126+ 1. Let |aggregationService| be |dict|["<code>[=measure-conversion/aggregation-service=] </code> "] [=map/with default=] `undefined`.
2127+ 1. If |aggregationService| is not a [=structured header/string=] , return an error.
2128+ 1. Let |histogramSize| be |dict|["<code>[=measure-conversion/histogram-size=] </code> "] [=map/with default=] `undefined`.
2129+ 1. If |histogramSize| is not a positive [=structured header/integer=] in the [=32-bit unsigned integer=] range, return an error.
2130+ 1. Let |reportUrlString| be |dict|["<code>[=measure-conversion/report-url=] </code> "] [=map/with default=] `undefined`.
2131+ 1. If |reportUrlString| is not a [=structured header/string=] , return an error.
2132+ 1. Let |reportUrl| be the result of applying the [=URL parser=] to |reportUrlString|, with |baseUrl|.
2133+ 1. If |reportUrl| is failure, return an error.
2134+ 1. If |reportUrl| is not a [=potentially trustworthy URL=] , return an error.
2135+ 1. If |reportUrl|'s [=url/scheme=] is not "`https`", return an error.
2136+ 1. Let |opts| be a new {{AttributionConversionOptions}} with the following items:
2137+ : {{AttributionConversionOptions/aggregationService}}
2138+ :: |aggregationService|
2139+ : {{AttributionConversionOptions/histogramSize}}
2140+ :: |histogramSize|
2141+ 1. If |dict|["<code>[=measure-conversion/epsilon=] </code> "] [=map/exists=] :
2142+ 1. Let |epsilon| be its [=map/value=] .
2143+ 1. If |epsilon| is not a [=structured header/decimal=] or [=structured header/integer=] , return an error.
2144+ 1. Set |opts|.{{AttributionConversionOptions/epsilon}} to |epsilon|.
2145+ 1. If |dict|["<code>[=measure-conversion/lookback-days=] </code> "] [=map/exists=] :
2146+ 1. Let |lookbackDays| be its [=map/value=] .
2147+ 1. If |lookbackDays| is not a positive [=structured header/integer=] , return an error.
2148+ 1. Set |opts|.{{AttributionConversionOptions/lookbackDays}} to |lookbackDays|.
2149+ 1. If |dict|["<code>[=measure-conversion/match-values=] </code> "] [=map/exists=] :
2150+ 1. Let |matchValues| be its [=map/value=] .
2151+ 1. If |matchValues| is not an [=structured header/inner list=] , or if any of
2152+ |matchValues|' [=list/items=] is not a [=structured header/integer=] in
2153+ the [=32-bit unsigned integer=] range, return an error.
2154+ 1. Set |opts|.{{AttributionConversionOptions/matchValues}} to |matchValues|.
2155+ 1. If |dict|["<code>[=measure-conversion/impression-sites=] </code> "] [=map/exists=] :
2156+ 1. Let |impressionSites| be its [=map/value=] .
2157+ 1. If |impressionSites| is not an [=structured header/inner list=] , or if any of
2158+ |impressionSites|' [=list/items=] is not a [=structured header/string=] ,
2159+ return an error.
2160+ 1. Set |opts|.{{AttributionConversionOptions/impressionSites}} to |impressionSites|.
2161+ 1. If |dict|["<code>[=measure-conversion/impression-callers=] </code> "] [=map/exists=] :
2162+ 1. Let |impressionCallers| be its [=map/value=] .
2163+ 1. If |impressionCallers| is not an [=structured header/inner list=] , or if any of
2164+ |impressionCallers|' [=list/items=] is not a [=structured header/string=] ,
2165+ return an error.
2166+ 1. Set |opts|.{{AttributionConversionOptions/impressionCallers}} to |impressionCallers|.
2167+ 1. If |dict|["<code>[=measure-conversion/credit=] </code> "] [=map/exists=] :
2168+ 1. Let |credit| be its [=map/value=] .
2169+ 1. If |credit| is not an [=structured header/inner list=] , or if any of
2170+ |credit|'s [=list/items=] is not a [=structured header/decimal=] or
2171+ [=structured header/integer=] , return an error.
2172+ 1. Set |opts|.{{AttributionConversionOptions/credit}} to |credit|.
2173+ 1. If |dict|["<code>[=measure-conversion/value=] </code> "] [=map/exists=] :
2174+ 1. Let |value| be its [=map/value=] .
2175+ 1. If |value| is not a positive [=structured header/integer=] in the
2176+ [=32-bit unsigned integer=] range, return an error.
2177+ 1. Set |opts|.{{AttributionConversionOptions/value}} to |value|.
2178+ 1. If |dict|["<code>[=measure-conversion/max-value=] </code> "] [=map/exists=] :
2179+ 1. Let |maxValue| be its [=map/value=] .
2180+ 1. If |maxValue| is not a positive [=structured header/integer=] in the
2181+ [=32-bit unsigned integer=] range, return an error.
2182+ 1. Set |opts|.{{AttributionConversionOptions/maxValue}} to |maxValue|.
2183+ 1. Return (|opts|, |reportUrl|).
2184+
2185+ Issue: Should we allow `http` for |reportUrl|'s [=url/scheme=] ?
2186+ Related to https://github.com/w3c/attribution/issues/146.
2187+
2188+ </div>
2189+
2190+ <div algorithm>
2191+ To <dfn noexport>send a report</dfn> given a [=byte sequence=] |report|,
2192+ a [=URL=] |url|, and an [=environment settings object=] |settings|:
2193+
2194+ 1. [=Assert=] : |url| is a [=potentially trustworthy URL=] .
2195+ 1. [=Assert=] : |url|'s [=url/scheme=] is "`https`".
2196+ 1. Let |headers| be a new [=header list=] containing a [=header=] named `"Content-Type"` whose value is `"application/octet-stream"`.
2197+ 1. Let |request| be a new [=request=] with the following properties:
2198+ : [=request/method=]
2199+ :: "`POST`"
2200+ : [=request/URL=]
2201+ :: |url|
2202+ : [=request/header list=]
2203+ :: |headers|
2204+ : [=request/body=]
2205+ :: |report|
2206+ : [=request/client=]
2207+ :: |settings|
2208+ : [=request/mode=]
2209+ :: "`cors`"
2210+ : [=request/cache mode=]
2211+ :: "`no-store`"
2212+ : [=request/keepalive=]
2213+ :: true
2214+ 1. [=Fetch=] |request|, optionally retrying in the event of an error.
2215+
2216+ Issue: Should the request use a more specific content-type?
2217+
2218+ Issue: What is the correct [=request/credentials mode=] ?
2219+
2220+ </div>
2221+
2222+ ## Fetch monkey patches ## {#fetch-monkey-patches}
2223+
20192224<div algorithm>
20202225To <dfn noexport>handle Attribution headers</dfn> given a [=request=] |request|
20212226and [=response=] |response|, run these steps:
@@ -2028,7 +2233,7 @@ and [=response=] |response|, run these steps:
20282233
202922341. If |response|'s [=response/URL=] is not a [=potentially trustworthy URL=] , return.
20302235
2031- 1. If |response|'s [=response/URL=]' s [=url/scheme=] is not not "`http`" or "`https`", return.
2236+ 1. If |response|'s [=response/URL=]' s [=url/scheme=] is not "`https`", return.
20322237
203322381. Let |implicitInputs| be the result of [=obtaining the implicit API inputs=] from |request|'s [=request/client=]
20342239 with |response|'s [=response/URL=]' s [=url/origin=] .
@@ -2037,17 +2242,33 @@ and [=response=] |response|, run these steps:
20372242
203822431. Let |saveImpressionHeader| be the result of [=header list/get|getting=] <code> [:Save-Impression:] </code> from |response|'s [=response/header list=] .
20392244
2040- 1. If |saveImpressionHeader| is null, return.
2245+ 1. If |saveImpressionHeader| is not null:
20412246
2042- 1. Let |impressionOptions| be the result of [=parse a Save-Impression header|parsing=] |saveImpressionHeader|.
2247+ 1. Let |impressionOptions| be the result of [=parse a Save-Impression header|parsing=] |saveImpressionHeader|.
20432248
2044- 1. If |impressionOptions| is an error, return .
2249+ 1. If |impressionOptions| is not an error, [=save an impression|Save=] |impressionOptions| with |implicitInputs| .
20452250
2046- 1. [=save an impression|Save =] |impressionOptions| with |implicitInputs| .
2251+ 1. Let |measureConversionHeader| be the result of [=header list/get|getting =] <code> [:Measure-Conversion:] </code> from |response|'s [=response/header list=] .
20472252
2048- </div>
2253+ 1. If |measureConversionHeader| is not null:
20492254
2050- ## Fetch monkey patches ## {#fetch-monkey-patches}
2255+ 1. Let |parseConversionResult| be the result of [=parse a Measure-Conversion header|parsing=] |measureConversionHeader|.
2256+
2257+ 1. If |parseConversionResult| is not an error:
2258+
2259+ 1. Let (|conversionOptions|, |reportUrl|) be |parseConversionResult|.
2260+
2261+ 1. Let |reportPromise| be the result of running [=measure a conversion=] with |conversionOptions| and |implicitInputs|.
2262+
2263+ 1. [=In parallel=] , [=upon fulfillment=] of |reportPromise|, [=send a report=] with the fulfilled value, |reportUrl|, and
2264+ |request|'s [=request/client=] .
2265+
2266+ Issue: Confirm the desired semantics of [:Save-Impression:] and [:Measure-Conversion:] in the same response.
2267+
2268+ Issue: Should we allow `http` for |response|'s [=response/URL=]' s [=url/scheme=] ?
2269+ Related to https://github.com/w3c/attribution/issues/146.
2270+
2271+ </div>
20512272
20522273Modify [=HTTP-network fetch=] as follows:
20532274
@@ -3136,6 +3357,7 @@ urlPrefix:https://tc39.es/ecma262/#;type:dfn;spec:ecma-262
31363357spec:structured header; type:dfn; urlPrefix: https://httpwg.org/specs/rfc9651;
31373358 text: structured header; url: #name-introduction
31383359 for: structured header
3360+ text: decimal; url: #decimal
31393361 text: dictionary; url: #dictionary
31403362 text: parse structured fields; url: #text-parse
31413363 text: string; url: #string
0 commit comments