Title: Topics API Status: w3c/CG-DRAFT ED: https://github.com/patcg-individual-drafts/topics Shortname: topics Level: 1 URL: https://github.com/patcg-individual-drafts/topics Editor: Yao Xiao, Google, yaoxia@chromium.org Editor: Josh Karlin, Google, jkarlin@chromium.org Abstract: This specification describes a method that could enable ad-targeting based on a person's general browsing interests without exposing their exact browsing history. !Participate: GitHub patcg-individual-drafts/topics (new issue, open issues) Group: patcg Repository: patcg-individual-drafts/topics Markup Shorthands: markdown yes
spec:html; type:attribute; text:document spec:webidl; type:dfn; text:resolve spec:dom; type:dfn; text:origin spec:fetch; type:dfn; for:/; text:request spec:dom; type:attribute; text:URL spec:infra; type:dfn; text:user agent spec:fetch; type:dfn; text:HTTP-network-or-cache fetch spec:url; type:dfn; for:url; text:host spec:url; type:dfn; for:url; text:origin spec:url; type:dfn; text:domain spec:fetch; type:dfn; for:/; text:header list spec:fetch; type:dfn; for:Response; text:response spec:infra; type:dfn; text:list
spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/ type: dfn text: node navigable; url: document-sequences.html#node-navigable text: relevant settings object; url: webappapis.html#relevant-settings-object text: top-level traversable; for:navigable; url: document-sequences.html#nav-top text: active document; for:navigable; url: document-sequences.html#nav-document text: navigable; for: Window; url: nav-history-apis.html#window-navigable spec: html; urlPrefix: https://wicg.github.io/nav-speculation/ type: dfn text: prerendering navigable; url: prerendering.html#prerendering-navigable text: post-prerendering activation steps list; url: prerendering.html#platform-object-post-prerendering-activation-steps-list spec: html; urlPrefix: https://www.rfc-editor.org/rfc/ type: dfn text: HMAC algorithm; url: rfc6234#section-8.3 spec: html; urlPrefix: https://www.rfc-editor.org/rfc/ type: dfn text: Structured Fields Token; url: rfc8941.html#name-tokens type: dfn text: Structured Fields Parameters; url: rfc8941.html#name-parameters
// document.browsingTopics() returns an array of BrowsingTopic objects. const topics = await document.browsingTopics(); // Get data for an ad creative. const response = await fetch('https://ads.example/get-creative', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(topics) }); // Get the JSON from the response. const creative = await response.json(); // Display the ad.
// A 'Sec-Browsing-Topics: [topics header value]' header will be sent in // the HTTP request. const response = await fetch('https://ads.example/get-creative', {browsingTopics: true}); const ad_creative = await response.json(); // Display the ad.
<browser vendor identifier>.<an integer version>
". The meaning may vary across browser vendors.
Given [=browsing topics types/configuration version=] |configurationVersion|, [=browsing topics types/taxonomy version=] |taxonomyVersion|, and [=browsing topics types/model version=] |modelVersion|, the version is the result of [=string/concatenating=] « |configurationVersion|, |taxonomyVersion|, |modelVersion| » using ":
".
The maximum version string length is the maximum possible string length of a [=browsing topics types/version=] that a user agent could possibly generate in a given software release. For example, in Chrome's experimentation phase, 13 was used for the [=browsing topics types/maximum version string length=] to account for a version like chrome.1:1:11
.
A user topics state is a struct with the following fields and default values:
- epochs: a list of [=epoch=]s, default to an empty list.
- hmac key: 128 bit number, default to 0.
An epoch is a struct with the following fields:
- taxonomy: a list of integers.
- taxonomy version: a string.
- model version: a string.
- config version: a string.
- top 5 topics with caller domains: a list of [=topic with caller domains=].
- time: a {{DOMHighResTimeStamp}} (from Unix epoch).
A topic with caller domains is a struct with the following fields:
- topic id: an integer.
- caller domains: a set of [=domains=].
A topics history entry is a struct with the following fields and default values:
- document id: an integer, default to 0.
- topics calculation input data: a string, default to an empty string.
- time: a {{DOMHighResTimeStamp}} (from Unix epoch).
- topics caller domains: an ordered set of [=domains=], default to an empty set.
A topics caller context is a struct with the following fields:
- caller domain: a [=domain=].
- top level context domain: a [=domain=].
- timestamp: a {{DOMHighResTimeStamp}} (from Unix epoch).
dictionary BrowsingTopic { [EnforceRange] unsigned long long topic; DOMString version; DOMString configVersion; DOMString modelVersion; DOMString taxonomyVersion; };
{ configVersion: "chrome.1", modelVersion: "1", taxonomyVersion: "1", topic: 43, version: "chrome.1:1:1" }
.
dictionary BrowsingTopicsOptions { boolean skipObservation = false; }; partial interface Document { [SecureContext] Promise<sequence<BrowsingTopic>> browsingTopics(optional BrowsingTopicsOptions options = {}); };
browsing-topics
feature.
- |document| is not [=allowed to use=] the interest-cohort
feature.
- The user preference setting disallows the access to topics from |topLevelDocument| given |document|'s [=Document/origin=].
Note: In Chrome's experimentation phase, it will additionally require a valid Origin Trial token to exist in |document|.
then:
1. [=Queue a global task=] on the browsing topics task source given |document|'s [=relevant global object=] to [=reject=] |promise| with a "{{NotAllowedError}}" {{DOMException}}.
1. Abort these steps.
1. Run the following steps [=in parallel=]:
1. Let |topics| be the result of running the [=calculate the topics for caller=] algorithm, with |topicsCallerContext| as input.
1. If options["{{BrowsingTopicsOptions/skipObservation}}"] is false:
1. Run the [=collect page topics calculation input data=] algorithm with |topLevelDocument| as input.
1. Run the [=collect topics caller domain=] algorithm with |topLevelDocument| and |topicsCallerContext|'s [=topics caller context/caller domain=] as input.
1. [=Queue a global task=] on the [=browsing topics task source=] given |document|'s [=relevant global object=] to perform the following steps:
1. [=Resolve=] |promise| with |topics|.
1. Return |promise|.
partial interface HTMLIFrameElement { [CEReactions] attribute boolean browsingTopics; };TODO: make the modification directly to the html spec.
partial dictionary RequestInit { boolean browsingTopics; };TODO: make the modification directly to the fetch spec.
Sec-Browsing-Topics
\` HTTP request headerSec-Browsing-Topics
\` HTTP request header. It is used to send the topics.
Sec-Browsing-Topics
\` header for |httpRequest|.
TODO: make the modification directly to the fetch spec.
Sec-Browsing-Topics
\` header, given a [=request=] |request|, run these steps:
1. If |request|'s [=request/send browsing topics header boolean=] is not true, then return.
1. [=header list/Delete=] [:Sec-Browsing-Topics:] from |request|'s [=header list=].
The topics a request is allowed to see can change within its redirect chain. For example, different caller domains may receive different topics, as the callers can only get the topics about the sites they were on. The timestamp can also affect the candidate epochs where the topics are derived from, thus resulting in different topics across redirects.
1. Let |initiatorWindow| be |request|'s [=request/window=]. 1. Let |requestOrigin| be |request|'s [=request/URL=]'s [=url/origin=]. 1. If |requestOrigin| is not a [=potentially trustworthy origin=], then return. 1. If |initiatorWindow| is not an [=environment settings object=], then return. 1. If |initiatorWindow| is not a [=secure context=], then return. 1. For each feature |f| in « "browsing-topic
", "interest-cohort
" »:
1. Run the Should request be allowed to use feature? algorithm with feature set to |f| and request set to |request|. If the algorithm returns false, then return.
Note: the above algorithm should include the pending update, i.e. the |request| should be considered to contain the equivalent opt-in flags for both "browsing-topic" and the "interest-cohort" feature.
1. Let |topLevelDocument| be |initiatorWindow|'s [=environment settings object/global object=]'s [=Window/navigable=]'s [=navigable/top-level traversable=]'s [=navigable/active document=].
1. Let |topicsCallerContext| be a [=topics caller context=] with default initial field values.
1. Set |topicsCallerContext|'s [=topics caller context/caller domain=] to |requestOrigin|'s [=origin/host=]'s [=host/registrable domain=].
1. Set |topicsCallerContext|'s [=topics caller context/top level context domain=] to |topLevelDocument|'s [=Document/origin=]'s [=origin/host=]'s [=host/registrable domain=].
1. Let |unsafeMoment| be the [=wall clock=]'s [=wall clock/unsafe current time=].
1. Let |moment| be the result of running [=coarsen time=] algorithm given |unsafeMoment| and [=wall clock=] as input.
1. Let |fromUnixEpochTime| be the [=duration from=] the [=Unix epoch=] to |moment|.
1. Set |topicsCallerContext|'s [=topics caller context/timestamp=] to |fromUnixEpochTime|.
1. If the user preference setting disallows the access to topics from |topLevelDocument| given |requestOrigin|, then return.
1. Let |topics| be the result of running the [=calculate the topics for caller=] algorithm, with |topicsCallerContext| as input.
1. Let |numVersionsInEpochs| be the result of running the [=get the number of distinct versions in epochs=] algorithm, with |topicsCallerContext| as input.
1. Let |versionsToTopics| be an [=ordered map=].
1. For each |topic| of |topics|:
1. Let |version| be |topic|["{{BrowsingTopic/version}}"].
1. Let |topicInteger| be |topic|["{{BrowsingTopic/topic}}"].
1. If |versionsToTopics|[|version|] does not exist, then set it to an empty list.
1. Append |topicInteger| to |versionsToTopics|[|version|].
1. Let |topicsStructuredFieldsList| be an empty Structured Fields List.
1. For each |version| → |topicIntegers| of |versionsToTopics|:
1. Let |innerList| be an empty Structured Fields Inner List.
1. Append all items from |topicIntegers| to |innerList|.
1. Let |topicParameters| be an empty [=Structured Fields Parameters=].
1. Set |topicParameters|["v
"] to a [=Structured Fields Token=] with value |version|.
1. Associate |topicParameters| with |innerList|.
1. Append |innerList| to |topicsStructuredFieldsList|.
1. If |numVersionsInEpochs| is 0, then set |numVersionsInEpochs| to 1.
1. Let |maxNumberOfEpochs| be 3 (i.e. topics are selected from the last 3 epochs).
1. Let |topicMaxLength| be number of base-10 digits in the maximum [=browsing topics types/topic id=] (e.g. for Chrome's initial taxonomy, |topicMaxLength| is 3, as the [=browsing topics types/topic id=] has maximum 3 digits).
1. Let |versionMaxLength| be the length of the current [=browsing topics types/maximum version string length=].
1. Let |listItemsSeparatorLength| be 2 (i.e. structured fields use a two characters (", ") to separate list items).
1. Let |perVersionedTopicsInnerListOverhead| be 5 (i.e. for "();v=")
1. Let |maxPaddingLength| be |maxNumberOfEpochs| * |topicMaxLength| + |maxNumberOfEpochs| - |numVersionsInEpochs| + |numVersionsInEpochs| * |perVersionedTopicsInnerListOverhead| + |numVersionsInEpochs| * |versionMaxLength| + (numVersionsInEpochs - 1) * |listItemsSeparatorLength|.
1. Let |paddingLength| be |maxPaddingLength|.
1. If |topicsStructuredFieldsList| is not empty:
1. Let |serializedTopicsList| be the result of executing the serializing structured fields algorithm on |topicsStructuredFieldsList|.
1. Decrement |paddingLength| by |serializedTopicsList|'s length.
1. Else:
1. Increment |paddingLength| by |listItemsSeparatorLength| (i.e. to account for the separator characters that would be added when |topics| are not empty).
1. If |paddingLength| < 0, then set |paddingLength| to 0.
Note: the padding should generally be ≥ 0. It may be negative in certain circumstances: when historically stored topic versions are greater (and use more digits) than the current [=browsing topics types/maximum version string length=]; or when there is a race between getting topics and getting the number of distinct topic versions. Clamp to 0 to prevent breakage in these rare circumstances.
1. Let |paddedToken| be "P".
1. Append |paddingLength| "0"
characters to the end of |paddedToken|.
1. Let |paddedEntryParameters| be an empty [=Structured Fields Parameters=].
1. Set |paddedEntryParameters|["p
"] to a [=Structured Fields Token=] with value |paddedToken|.
1. Let |emptyInnerList| be an empty Structured Fields Inner List.
1. Associate |paddedEntryParameters| with |emptyInnerList|.
1. Append |emptyInnerList| to |topicsStructuredFieldsList|.
1. [=Set a structured field value=] given ([:Sec-Browsing-Topics:], |topicsStructuredFieldsList|) in |request|'s [=request/header list=].
();p=P0000000000000000000000000000000
(1);v=chrome.1:1:2, ();p=P00000000000
(1 2);v=chrome.1:1:2, ();p=P000000000
(1);v=chrome.1:1:2, (1);v=chrome.1:1:4, ();p=P0000000000
(100);v=chrome.1:1:20, (200);v=chrome.1:1:40, (300);v=chrome.1:1:60, ();p=P
Observe-Browsing-Topics
\` HTTP response headerObserve-Browsing-Topics
\` HTTP response header can be used to record a caller's topics observation.
This specification defines a [=policy-controlled feature=] identified by the string
"browsing-topics
". Its default allowlist is *
.
For backward compatibility, this specification also defines a [=policy-controlled feature=] identified by the string
"interest-cohort
". Its default allowlist is *
.