Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Update spec to current explainer #131

Merged
merged 11 commits into from
Apr 30, 2021
229 changes: 119 additions & 110 deletions index.bs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<pre class='metadata'>
Title: Conversion Measurement
Shortname: conversion-measurement
Title: Attribution Reporting
Shortname: attribution-reporting
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious if this causes any problems for the various spec-scraping tools. Probably not, since I think this spec isn't referenced from anywhere, and I doubt there's anything you could do in this spec to mitigate any problems.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully one change is enough :)

Level: 1
Status: CG-DRAFT
Group: wicg
Repository: WICG/conversion-measurement-api
URL: https://wicg.github.io/conversion-measurement-api
Editor: Charlie Harrison, Google Inc. https://google.com, [email protected]
Abstract: A new API to measure and attribute cross-site conversions.
Abstract: A new API to measure and attribute cross-site events.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect "attribute" to be used with "to something" at the end. Is there something you could say about where these events are being attributed to?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarified, this is about associating them to each other. Does this still seem vague to you?


Markup Shorthands: markdown on
Complain About: accidental-2119 on, missing-example-ids on
Expand All @@ -23,24 +23,24 @@ Introduction {#intro}
<em>This section is non-normative</em>

This specification describes how web browsers can provide a mechanism to the
web that allows measuring and attributing conversions (e.g. purchases) to ads
web that supports measuring and attributing conversions (e.g. purchases) to ads
a user interacted with on another site. This mechanism should remove one need
for cross site identifiers like third party cookies.

## Overview ## {#overview}

An anchor tag with <{a/impressiondata}> and <{a/conversiondestination}> attributes is
classified as an <dfn export>impression tag</dfn>. When impression tags are clicked, and the
resulting navigation commits in a document matching the <{a/conversiondestination}>,
then the impression is stored in UA storage.
A page can register an [=attribution source=] on a site by providing
<{a/attributionsourceeventid}> and <{a/attributiondestination}> attributes on an <{a}> element.
When such an <{a}> element is clicked, and the resulting navigation commits in a document matching
the <{a/attributiondestination}>, the [=attribution source=] is stored in UA storage.

At a later point, the <{a/conversiondestination}> site may fire an HTTP request to
trigger conversion registration, which matches up with any previously
stored impressions. If matching impressions exist, they are scheduled to be
At a later point, the <{a/attributiondestination}> site may fire an HTTP request to
trigger attribution, which matches an [=attribution trigger=] with any previously
stored sources. If matching sources exist, they are scheduled to be
reported at a later time, possibly multiple days in the future.

Reports are sent to reporting endpoints that are configured in impression tags
and conversion registration requests.
Reports are sent to reporting endpoints that are configured in attribution sources
and attribution trigger requests.

# Fetch monkeypatches # {#fetch-monkeypatches}

Expand All @@ -49,198 +49,207 @@ conversion domain.

# HTML monkeypatches # {#html-monkeypatches}

Rewrite the anchor element to accept the following attributes:
Rewrite the <{a}> to accept the following attributes:

<pre class="idl">
partial interface HTMLAnchorElement {
[CEReactions, Reflect] attribute DOMString conversiondestination;
[CEReactions, Reflect] attribute DOMString impressiondata;
[CEReactions, Reflect] attribute DOMString reportingorigin;
[CEReactions, Reflect] attribute unsigned long long impressionexpiry;
[CEReactions, Reflect] attribute DOMString attributiondestination;
[CEReactions, Reflect] attribute DOMString attributionsourceeventid;
[CEReactions, Reflect] attribute DOMString attributionreportto;
[CEReactions, Reflect] attribute unsigned long long attributionexpiry;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unsigned long long is not a type defined in https://html.spec.whatwg.org/#reflecting-content-attributes-in-idl-attributes

This either needs to be an unsigned long or a string. I can address this in a separate PR.

};
</pre>

The <dfn for="a" element-attr>conversiondestination</dfn> is the
declared destination [=scheme-and-registrable-domain=] of the anchor for
purposes of conversion measurement
The <dfn for="a" element-attr>attributiondestination</dfn> is an [=url/origin=]
which declares the intended final navigation url resulting from running
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An origin won't declare the exact final navigation URL. And we can incorporate the note below into this paragraph. Perhaps

Suggested change
which declares the intended final navigation url resulting from running
that is intended to be [=same site=] with the origin of the final navigation url resulting from running

<a spec="html">follow the hyperlink</a> with the <{a}> element.

The <dfn for="a" element-attr>impressiondata</dfn> is a string
containing information about the `impression tag` and will be supplied in the
`conversion report`.
Note: This origin does not need to match the exact final origin of the navigation.
However, this navigation and the final navigation url have to produce the same result when
passed to [=obtain a site=].

The <dfn for="a" element-attr>reportingorigin</dfn> declares the
intended [=origin=] to send the `conversion report` for this impression.
The <dfn for="a" element-attr>attributionsourceeventid</dfn> is a string
containing information about the `attribution source` and will be supplied in the
[=attribution report=].

The <dfn for="a" element-attr>impressionexpiry</dfn> is the amount
of time in milliseconds the impression should be considered for conversion
measurement and reporting reporting.
The <dfn for="a" element-attr>attributionreportto</dfn> declares the
intended [=origin=] to send the [=attribution report=] for this source.

The <dfn for="a" element-attr>attributionexpiry</dfn> is the amount
of time in milliseconds the attribution source should be considered for reporting.

Issue: Need monkey patches passing impression data in navigation, and a mechanism
for validating the resulting document matches the conversiondestination.
Issue: Need monkey patches passing attribution source in navigation, and a mechanism
for validating the resulting document matches the attributiondestination.

# Structures # {#structures}

<h3 dfn-type=dfn>Impression</h3>
<h3 dfn-type=dfn>Attribution source</h3>

An impression is a [=struct=] with the following items:
An attribution source is a [=struct=] with the following items:

<dl dfn-for="impression">
: <dfn>impression source</dfn>
<dl dfn-for="attribution source">
: <dfn>source origin</dfn>
:: An [=url/origin=].
: <dfn>impression data</dfn>
: <dfn>event id</dfn>
:: A [=string=].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're setting this to an integer in [=obtain an attribution source=].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. I noted this is a 64-bit integer, but I couldn't find any precedence in the html spec for this.

: <dfn>conversion destination</dfn>
: <dfn>attribution destination</dfn>
:: An [=url/origin=].
: <dfn>reporting endpoint</dfn>
:: An [=url/origin=].
: <dfn>expiry</dfn>
:: A point in time.
: <dfn>impression time</dfn>
: <dfn>source time</dfn>
:: A point in time.

</dl>

<h3 dfn-type=dfn>Conversion</h3>
<h3 dfn-type=dfn>Attribution trigger</h3>

A conversion is a [=struct=] with the following items:
An attribution trigger is a [=struct=] with the following items:

<dl dfn-for="conversion">
: <dfn>conversion source</dfn>
<dl dfn-for="attribution trigger">
: <dfn>trigger origin</dfn>
:: An [=url/origin=].
: <dfn>conversion data</dfn>
: <dfn>trigger data</dfn>
:: A [=string=].
: <dfn>conversion time</dfn>
: <dfn>trigger time</dfn>
:: A point in time.
: <dfn>reporting endpoint</dfn>
:: An [=url/origin=].

</dl>

<h3 dfn-type=dfn>Conversion report</h3>
<h3 dfn-type=dfn>Attribution report</h3>

A conversion report is a [=struct=] with the following items:
An attribution report is a [=struct=] with the following items:

<dl dfn-for="conversion report">
: <dfn>impression data</dfn>
<dl dfn-for="attribution report">
: <dfn>event id</dfn>
:: A [=string=].
: <dfn>conversion data</dfn>
: <dfn>trigger data</dfn>
:: A [=string=].
: <dfn>attribution credit</dfn>
: <dfn>credit</dfn>
:: An integer in the range [0, 100].

</dl>

# Algorithms # {#algorithms}

<h3 algorithm id="parsing-conversion-destination">Parsing a conversion destination</h3>
<h3 algorithm id="parsing-attribution-destination">Parsing an attribution destination</h3>

To <dfn>parse a conversion destination</dfn> from an <{a}> tag |anchor|,
To <dfn>parse an attribution destination</dfn> from a string |str|:
1. Let |url| be the result of running the [=URL parser=] on the value of
the |anchor|'s <{a/conversiondestination}>.
the |str|.
1. Return the result of [=obtain a site|obtaining a site=] from |url|'s
[=url/origin=].

<h3 algorithm id="creating-impression">Activating an impression</h3>
<h3 algorithm id="obtaining-attribution-source-anchor">Obtaining an attribution source from an <code>a</code> element</h3>

To <dfn>activate an impression</dfn> from an <{a}> tag |anchor|,
To <dfn>obtain an attribution source</dfn> from an <{a}> element |anchor|:
1. Let |currentTime| be the current time.
1. Let |impression| be a new [=impression=] struct whose items are:
1. Let |source| be a new [=attribution source=] struct whose items are:

: [=impression/impression source=]
: [=attribution source/source origin=]
:: |anchor|'s [=relevant settings object=]'s [=environment/top-level origin=].
: [=impression/impression data=]
:: The result of applying [=parsing conversion data=] to |anchor|'s
<{a/impressiondata}> attribute.
: [=impression/conversion destination=]
:: The result of running [=parse a conversion destination=] on |anchor|.
: [=impression/reporting endpoint=]
:: The [=url/origin=] of the result of running the [=URL parser=] on the value
of |anchor|'s <{a/reportingorigin}> attribute.
: [=impression/expiry=]
:: |currentTime| + <{a/impressionexpiry}> milliseconds.
: [=impression/impression time=]
: [=attribution source/event id=]
:: The result of running [=parse attribution data=] with |anchor|'s
<{a/attributionsourceeventid}> attribute and [=max event id value=].
: [=attribution source/attribution destination=]
:: The result of running [=parse an attribution destination=] with |anchor|'s <{a/attributiondestination}> attribute.
: [=attribution source/reporting endpoint=]
:: The [=url/origin=] of the result of running the [=URL parser=] with
|anchor|'s <{a/attributionreportto}> attribute.
: [=attribution source/expiry=]
:: |currentTime| + <{a/attributionexpiry}> milliseconds.
: [=attribution source/source time=]
:: |currentTime|.
1. Return |source|

<dfn>Max event id value</dfn> is a vendor specific integer which controls the meximum size value which can be used as an [=attribution source/event id=]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<dfn>Max event id value</dfn> is a vendor specific integer which controls the meximum size value which can be used as an [=attribution source/event id=]
<dfn>Max event id value</dfn> is a vendor specific integer which controls the maximum size value which can be used as an [=attribution source/event id=]


1. Issue: Need to spec how to store the impression.
1. Issue: Need to spec how to store the attribution source.

<h3 algorithm id="creating-a-conversion">Creating a conversion</h3>
<h3 algorithm id="attribution-trigger-creation">Creating an attribution trigger</h3>

To <dfn>create a conversion</dfn> from a [=url=] |url| and an
[=environment settings object=] |environment|, return a new [=conversion=]
struct with the items:
To <dfn>obtain an attribution trigger</dfn> given a [=url=] |url|, and an
[=environment settings object=] |environment|, return a [=attribution trigger=]
with the items:

: [=conversion/conversion source=]
: [=attribution trigger/trigger origin=]
:: |environment|'s [=environment/top-level origin=].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this the top-level origin instead of just the origin? Can attribution triggers happen in cross-origin iframes? Have you discussed somewhere the security implications of writing a different origin into the attribution trigger, and what it means for specs that need to trust or distrust that value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Attribution triggers can happen in cross-origin iframes. The API scopes all events to the top-level site, and then performs attribution in the top-level site namespace. One example would be, an ad was shown for shoes.example, and then shoes.example loads an iframe to ad-tech.example which handles triggering attribution on that site.

We have not discussed the security implications to my knowledge.

Speaking from Chrome's implementation, the browser uses a trusted origin for this struct member. Essentially, the browser knows the top-level browsing context and it's origin, and can look that up from the browsing context which created the attribution trigger in a trusted way. So this value isn't something inherently controlled from within the browsing context in practice.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was worried about a hostile iframe being able to create somehow-hostile attributions to the surrounding page, but I'm not sure what attacks that would actually be useful for. It helps that the user has to click in order to make the attribution source happen. Maybe hostile triggers are more worrying, since they might cause payment from the trigger to the source? In any case, this sort of attribution (no pun intended) of one origin's actions to a different origin is worth talking about in the security considerations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that would cause issues. Sources are also problematic: a hostile frame logging additional sources could cause credits to be assigned incorrectly, or cause the browser to delete already existing sources in some cases.

The main mitigation we have is requiring a permissions policy to invoke the API inside of iframes, which is disabled by default for cross-origin iframes.

I will make a note to address this in Security Considerations when the Permissions Policy is added to this draft spec.

: [=conversion/conversion data=]
:: The result of applying [=parsing conversion data=] to the value associated with the
`"conversion-data"` field of |url|'s [=url/query=].
: [=conversion/conversion time=]
: [=attribution trigger/trigger data=]
:: The result of applying [=parse attribution data=] with the value associated with the
`"data"` field of |url|'s [=url/query=] and the user agent's [=max trigger data value=].
: [=attribution trigger/trigger time=]
:: The current time.
: [=attribution trigger/reporting endpoint=]
:: |url|'s [=url/origin=]

<dfn>Max trigger data value</dfn> is a vendor specific integer which controls the values of [=attribution-report/trigger-data].

Issue: Formalize how to parse the query similar to URLSearchParams.

<h3 algorithm id="register-conversion">Register a conversion</h3>
<h3 algorithm id="triggering-attribution">Triggering attribution</h3>

To <dfn>register a conversion</dfn> from a [=request=] |request|, run the following steps:
To <dfn>trigger attribution</dfn> from a [=request=] |request|, run the following steps:

1. If |request|'s [=request/current url's=] [=url/path=] is not `.well-known/register-conversion`,
1. If |request|'s [=request/current url's=] [=url/path=] is not `.well-known/attribution-reporting/trigger-attribution`,
return.
1. If |request|'s [=request/redirect count=] is less than 1, return.
1. Let |previousUrl| be the second to last [=URL=] in |request|'s
[=request/URL list=].
1. If |request|'s [=request/current url's=] [=url/origin=] is not [=same origin=] with
|previousUrl|'s [=url/origin=], return.
1. Let |conversionToRegister| be the result of applying [=create a conversion=] from the
request's [=request/current url=].
1. Let |trigger| be the result of running [=obtain an attribution trigger=] with
|request|'s [=request/current url=].

Note: the restriction to require a redirect is needed to ensure that the
request's origin is aware and in control of the conversion registration.
request's origin is aware and in control of triggering attribution.

1. Issue: Need to spec how to store the conversion.
1. Issue: Need to spec how to store |trigger|.

<h3 algorithm id="parsing-data">Parsing data fields</h3>
<h3 algorithm id="parsing-data-fields">Parsing data fields</h3>

This section defines how to parse and extract both
[=impression/impression data=] and [=conversion/conversion data=] from a
[=string=] |input| and a unsigned long long |maxData|.
[=attribution source/event id=] and [=attribution trigger/trigger data=].

<dfn>Parsing conversion data</dfn> from |input| with |maxData| returns the result of
the following steps:
To <dfn>parse attribution data</dfn> given a [=string=] |input|, and an unsigned long long
|maxData| perform the following steps. They return an unsigned long long:

1. Let |decodedInput| be the result of decoding |input| as a base-16 integer.
1. Let |decodedInput| be the result of decoding |input| as a base-10 integer.
1. Let |clampedDecodedInput| be the remainder when dividing |decodedInput| by
|maxData|.
1. Let |encodedOutput| be the result of encoding |clampedDecodedInput| as a
base 16 encoding.
1. Return |encodedOutput|.
1. Return |clampedDecodedInput|.

<h3 algorithm id="delivery-time">Establishing report delivery time</h3>
The <dfn>report delivery time</dfn> for an [=impression=] |impression| and a
[=conversion/conversion time=] |conversionTime| is the result of the following steps:
1. Let |conversionTimeAfterImpression| be the difference between the
[=conversion/conversion time=] and [=impression/impression time=].
1. Let |expiryDelta| be the difference between the [=impression/expiry=] and
the [=impression/impression time=]

Note: |conversionTimeAfterImpression| should always be less than
|expiryDelta| because it should not be possible to convert an expired
impression.
To <dfn>obtain a report delivery time</dfn> given an [=attribution source=] |source| and a
[=attribution trigger/trigger time=] |triggerTime| perform the following steps. They
return a point in time.
1. Let |timeToTrigger| be the difference between
|triggerTime| and [=attribution source/source time=].
1. Let |expiryDelta| be the difference between the |source|'s [=attribution source/expiry=] and
the |source|'s [=attribution source/source time=]

Note: |timeToTrigger| is less than |expiryDelta| because it is not normally possible to
convert an expired attribution source.

1. If:
<dl class="switch">
<dt>|conversionTimeAfterImpression| <= (2 days - 1 hour)</dt>
<dd>return [=impression/impression time=] + 2 days.</dd>
<dt>|timeToTrigger| <= (2 days - 1 hour)</dt>
<dd>return [=attribution source/source time=] + 2 days.</dd>

<dt> |expiryDelta| > (2 days - 1 hour)
- and |expiryDelta| < (7 days - 1 hour)
- and |conversionTimeAfterImpression| <= |expiryDelta|
- and |timeToTrigger| <= |expiryDelta|
</dt>
<dd>return the [=impression/expiry=] + 1 hour.</dd>
<dd>return |source|'s [=attribution source/expiry=] + 1 hour.</dd>

<dt>|conversionTimeAfterImpression| <= (7 days - 1 hour)</dt>
<dd>return [=impression/impression time=] + 7 days</dd>
<dt>|timeToTrigger| <= (7 days - 1 hour)</dt>
<dd>return [=attribution source/source time=] + 7 days</dd>

<dt>Otherwise</dt>
<dd>return the [=impression/expiry=] + 1 hour.</dd>
<dd>return |source|'s [=attribution source/expiry=] + 1 hour.</dd>
</dl>

<h3 algorithm id="queuing-report">Queuing a conversion report</h3>
Expand Down