1. Introduction
On today’s web, people’s interests are typically inferred based on observing what sites or pages they visit. This relies on tracking techniques such as third-party cookies, or less-transparent mechanisms like device fingerprinting. It would be better for privacy if interest-based advertising could be accomplished without needing to collect a particular individual’s browsing history.
This specification provides an API to enable ad-targeting based on a person’s general browsing interests, without exposing their exact browsing history.
document.browsingTopics()
JavaScript API:
(Inside an https://ads.example
iframe)
// 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.
Sec-Browsing-Topics
` HTTP request header sent by this invocation of fetch()
:
(Inside the top level context)
// 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 adCreative= await response. json(); // Display the ad.
2. Terminology and types
A taxonomy comprises a list of advertising topic ids as integers. A taxonomy is identified by a taxonomy version string. A topic id is no smaller than 1.The taxonomy must be in a tree hierarchy, where an ancestor topic id always represents something more general than its descendant topic ids. The browser should implement a get descendant topics algorithm, which takes a topic id, and returns its descendants' topic ids as a list.
The model version is a string that identifies the model used to classify a string into topic ids. The meaning may vary across browser vendors. The classification result topic ids should be relevant to the input string’s underlying content.
The configuration version identifies the algorithm (other than the model part) used to calculate the topic. It should take the form of "<browser vendor identifier>.<an integer version>
". The meaning may vary across browser vendors.
Given configuration version configurationVersion, taxonomy version taxonomyVersion, and model version modelVersion, the version is the result of concatenating « configurationVersion, taxonomyVersion, modelVersion » using ":
".
The maximum version string length is the maximum possible string length of a 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 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 epochs, 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).
3. User agent associated state
Each user agent has an associated user topics state user topics state with epochs initially empty, and hmac key initially a randomly generated 128-bit number.Each user agent has an associated topics history storage to store the information about the visited pages that are needed for topics calculation. It is a list of topics history entries, initially empty.
Each user agent has an associated taxonomy taxonomy (identified by taxonomy version taxonomy version) and model model (identified by model version model version).
The taxonomy and model may be shipped to the browser asynchronously with respect to the browser release, and may be unavailable at a given point. They must be updated atomically with respect to algorithms that access them (e.g. the calculate user topics algorithm).
Note: In Chrome versions M121 and later, the taxonomy used is taxonomy_v2.md. The expectation is that it will change over time.
Each user agent has an associated topics algorithm configuration (identified by configuration version configuration version). The initial value and meaning is browser defined.
Note: The configuration version allows the browser vendor to provide algorithms different from the ones specified in this specification. For example, for some of the algorithms in this specification, it may be possible to use a different constant value, while the system overall still has utility and meets the privacy goals.
When the configuration version is updated, the browser must properly migrate or delete data in user topics state and topics history storage so that the state and the configuration are consistent.
3.1. Expiring stored data
User agents must automatically delete stored data 28 days after its creation.4. BrowsingTopic dictionary
TheBrowsingTopic
dictionary is used to contain the IDL correspondences of topic id, version, configuration version, taxonomy version, and model version.
dictionary { [
BrowsingTopic EnforceRange ]unsigned long long ;
topic DOMString ;
version DOMString ;
configVersion DOMString ;
modelVersion DOMString ; };
taxonomyVersion
BrowsingTopic
object from Chrome: { configVersion: "chrome.1" , modelVersion: "1" , taxonomyVersion: "1" , topic: 43 , version: "chrome.1:1:1" }
. BrowsingTopic
dictionary a is code unit less than a BrowsingTopic
dictionary b if the following steps return true:
-
If a["
version
"] is code unit less than b["version
"], then return true. -
Return false.
5. document ID
EachDocument
has a document id, which is an implementation-defined unique identifier shared with no other Document
objects within or across browser sessions for a user agent.
6. Determine topics calculation input data
Given aDocument
, the browser must have a way to determine the topics calculation input data. topics calculation input data is a string that encodes the attributes to be used for topics classification, as determined by the browser vendor. By default, the attributes should be scoped to the document’s URL and metadata.
Note: unless specifically allowed, data beyond the document shouldn’t be included, such as data from localStorage or cookies.
Note: In Chrome’s experimentation phase, the host of a Document
's URL is used as the topics calculation input data, and the model is trained with human curated hostnames and topics.
7. Collect page topics calculation input data
Document
document:
-
If document’s node navigable is a prerendering navigable, then append the following steps to document’s post-prerendering activation steps list and return. Else, run the following steps in parallel:
-
Let documentId be document’s document id.
-
If user agent’s topics history storage contains a topics history entry whose document id is documentId, return.
-
Let topicsHistoryEntry be a topics history entry.
-
Set topicsHistoryEntry’s document id to documentId.
-
Set topicsHistoryEntry’s topics calculation input data to the topics calculation input data for document.
-
Let unsafeMoment be the wall clock's unsafe current time.
-
Let moment be the result of running coarsen time algorithm given unsafeMoment and wall clock as input.
-
Let fromUnixEpochTime be the duration from the Unix epoch to moment.
-
Set topicsHistoryEntry’s time to fromUnixEpochTime.
-
Append topicsHistoryEntry to user agent’s topics history storage.
-
8. Collect topics caller domain
Document
document and a domain callerDomain:
-
Run the following steps in parallel:
-
Let documentId be document’s document id.
-
If user agent’s topics history storage does not contain a topics history entry whose document id is documentId, return.
-
Let topicsHistoryEntry be the topics history entry in user agent’s topics history storage whose document id is documentId.
-
Append callerDomain to topicsHistoryEntry’s topics caller domains.
-
9. Derive top 5 topics
Given a list of topics history entries historyEntriesForUserTopics, the browser should provide an algorithm to derive top 5 topics, that are believed to be valuable for the Topics callers. The algorithm should return a list of 5 topic ids.
Given a list of topics history entries historyEntriesForUserTopics:
-
Let topicsCount be an empty map.
-
For each topics history entry historyEntry in historyEntriesForUserTopics:
-
Let topicIds be the result of classifying historyEntry’s topics calculation input data.
-
For each topicId in topicIds:
-
If topicsCount[topicId] does not exist:
-
Initialize topicsCount[topicId] to 0.
-
-
Increment topicsCount[topicId] by 1.
-
-
-
Let prioritizedTopicsCount be the result of sorting in ascending order topicsCount, with a less than algorithm compare topics based on priority and count.
-
Let top5Topics be the first up to 5 keys of prioritizedTopicsCount.
-
If top5Topics has less than 5 entries:
-
Pad top5Topics with random topic ids from user agent’s taxonomy, until top5Topics has 5 entries.
-
-
Return top5Topics.
To compare topics based on priority and count, given (topic1, count1) and (topic2, count2), perform the following steps. They return a boolean.
-
Assert: count1 > 0.
-
Assert: count2 > 0.
-
Let highUtilityTopics be « 57, 86, 126, 149, 172, 180, 196, 207, 239, 254, 263, 272, 289, 299, 332 ».
-
If highUtilityTopics contains topic1 and highUtilityTopics does not contain topic2, then return true.
-
If highUtilityTopics does not contain topic1 and highUtilityTopics contains topic2, then return false.
-
Return count1 > count2.
10. Periodically calculate user topics
At the start of a browser session, run the schedule user topics calculation algorithm.
-
Let unsafeMoment be the wall clock's unsafe current time.
-
Let moment be the result of running coarsen time algorithm given unsafeMoment and wall clock as input.
-
Let fromUnixEpochTime be the duration from the Unix epoch to moment.
-
Let presumedNextCalculationDelay be a duration of 0.
-
If user agent’s user topics state's epochs is not empty:
-
Let numEpochs be user agent’s user topics state's epochs's size.
-
Let lastTopicsCalculationTime be user agent’s user topics state's epochs[numEpochs − 1].
-
Let presumedNextCalculationDelay be lastTopicsCalculationTime + (a duration of 7 days) − fromUnixEpochTime.
-
If presumedNextCalculationDelay < (a duration of 0), then set presumedNextCalculationDelay to (a duration of 0).
-
Else if presumedNextCalculationDelay ≥ (a duration of 14 days), then set presumedNextCalculationDelay to (a duration of 0).
Note: This could happen if the machine time has gone backward since the last topics calculation. Recalculate immediately to align with the expected schedule rather than potentially stop calculating for a very long time.
-
-
Schedule the calculate user topics algorithm to run at Unix epoch + fromUnixEpochTime + presumedNextCalculationDelay.
-
Let unsafeMoment be the wall clock's unsafe current time.
-
Let moment be the result of running coarsen time algorithm given unsafeMoment and wall clock as input.
-
Let fromUnixEpochTime be the duration from the Unix epoch to moment.
-
If either user agent’s model or taxonomy isn’t available:
-
Let epoch be an epoch struct with default initial field values.
-
Set epoch’s time to fromUnixEpochTime.
-
Append epoch to user agent’s user topics state's epochs.
-
If user agent’s user topics state's epochs has more than 4 entries, remove the oldest epoch (i.e. the epoch with index 0).
-
Schedule this calculate user topics algorithm to run at Unix epoch + fromUnixEpochTime + (a duration of 7 days).
-
Return.
-
-
Let historyEntriesForUserTopics be an empty list.
-
Let topicsCallers be an empty map.
-
Let userTopicsDataStartTime be fromUnixEpochTime − (a duration of 7 days).
-
Let topicsCallerDataStartTime be fromUnixEpochTime − (a duration of 21 days).
-
For each topics history entry topicsHistoryEntry in user agent’s topics history storage:
-
Let visitTime be topicsHistoryEntry’s time.
-
If visitTime is before topicsCallerDataStartTime, then continue.
-
Let topicIds be the result of classifying topicsHistoryEntry’s topics calculation input data.
-
If visitTime is greater than userTopicsDataStartTime:
-
Append topicsHistoryEntry to historyEntriesForUserTopics.
-
-
For each topicId in topicIds:
-
If topicsCallers[topicId] does not exist:
-
Initialize topicsCallers[topicId] to be an empty list.
-
-
For each callerDomain in topicsHistoryEntry’s topics caller domains:
-
Append callerDomain to topicsCallers[topicId].
-
-
-
-
Let top5Topics be the result of running derive top 5 topics algorithm, given historyEntriesForUserTopics.
-
Let top5TopicsWithCallerDomains be an empty list.
-
For each topTopicId in top5Topics:
-
Let topicWithCallerDomains be a topic with caller domains struct with topic id initially 0 and caller domains initially empty.
-
If topTopicId is allowed by user preference setting:
-
Set topicWithCallerDomains’s topic id to topicId.
-
Let topicWithDescendantIds be the result of running get descendant topics given topTopicId.
-
Add topTopicId to topicWithDescendantIds.
-
For each topicId in topicWithDescendantIds:
-
If topicId is allowed by user preference setting:
-
Insert all elements in topicsCallers[topicId] to topicWithCallerDomains’s caller domains.
-
-
-
-
Append topicWithCallerDomains to top5TopicsWithCallerDomains.
-
-
Let epoch be an epoch struct with default initial field values.
-
Set epoch’s taxonomy version to user agent’s taxonomy version.
-
Set epoch’s model version to user agent’s model version.
-
Set epoch’s config version to user agent’s configuration version.
-
Set epoch’s top 5 topics with caller domains to top5TopicsWithCallerDomains.
-
Set epoch’s time to fromUnixEpochTime.
-
Append epoch to user agent’s user topics state's epochs.
-
If user agent’s user topics state's epochs has more than 4 entries, remove the oldest epoch.
-
Schedule this calculate user topics algorithm to run at Unix epoch + fromUnixEpochTime + (a duration of 7 days).
11. Epochs for caller
-
Let epochs be user agent’s user topics state's epochs.
-
If epochs is empty, then return an empty list.
-
Let numEpochs be epochs’s size.
-
Let lastEpochTime be epochs[numEpochs − 1]'s time.
-
Let epochSwitchTimeDecisionMessageArray be the concatenation of "epoch-switch-time-decision|", lastEpochTime, and callerContext’s top level context domain.
-
Let epochSwitchTimeDecisionHmacOutput be the output of the HMAC algorithm, given input parameters: whichSha=SHA256, key=user agent’s user topics state's hmac key, and message_array=epochSwitchTimeDecisionMessageArray.
-
Let epochSwitchTimeDecisionHash be 64-bit truncation of epochSwitchTimeDecisionHmacOutput.
-
Let epochSwitchTimeDelayIntroduction be a duration of (epochSwitchTimeDecisionHash % 172800) seconds (i.e. 172800 is 2 days in seconds).
-
Let epochPhaseOutTimeDecisionMessageArray be the concatenation of "epoch-phase-out-time-decision|", lastEpochTime, and callerContext’s top level context domain.
-
Let epochPhaseOutTimeDecisionHmacOutput be the output of the HMAC algorithm, given input parameters: whichSha=SHA256, key=user agent’s user topics state's hmac key, and message_array=epochPhaseOutTimeDecisionMessageArray.
-
Let epochPhaseOutTimeDecisionHash be 64-bit truncation of epochPhaseOutTimeDecisionHmacOutput.
-
Let epochPhaseOutTimeOffset be a duration of (epochPhaseOutTimeDecisionHash % 172800) seconds (i.e. 172800 is 2 days in seconds).
-
Let timestamp be callerContext’s timestamp.
-
Let result be an empty list.
-
Let startEpochIndex be -1.
-
Let endEpochIndex be -1.
-
If timestamp ≤ lastEpochTime + epochSwitchTimeDelayIntroduction:
-
Set startEpochIndex to max(numEpochs − 4, 0).
-
Set endEpochIndex to numEpochs − 2.
-
-
Else:
-
Set startEpochIndex to max(numEpochs − 3, 0).
-
Set endEpochIndex to numEpochs − 1.
-
-
If endEpochIndex ≥ 0:
-
Return result.
12. Get the number of distinct versions in epochs
-
Let epochs be the result of running the calculate the epochs for caller algorithm given callerContext as input.
-
Let distinctVersions be an empty set.
-
For each epoch in epochs:
-
If epoch’s taxonomy version is empty (implying that the topics calculation for that epoch didn’t occur), then continue.
-
Insert tuple (epoch’s taxonomy version, epoch’s model version) to distinctVersions.
-
-
Return distinctVersions’s size.
13. Topics for caller
BrowsingTopic
s.
-
Let epochs be the result of running the calculate the epochs for caller algorithm given callerContext as input.
-
Let result be an empty list.
-
For each epoch in epochs:
-
If epoch’s top 5 topics with caller domains is empty (implying the topics calculation failed for that epoch), then continue.
-
Let topic be null.
-
Let topTopicIndexDecisionMessageArray be the concatenation of "top-topic-index-decision|", epoch’s time, and callerContext’s top level context domain.
-
Let topTopicIndexDecisionHmacOutput be the output of the HMAC algorithm, given input parameters: whichSha=SHA256, key=user agent’s user topics state's hmac key, and message_array=topTopicIndexDecisionMessageArray.
-
Let topTopicIndexDecisionHash be 64-bit truncation of topTopicIndexDecisionHmacOutput.
-
Let topTopicIndex be topTopicIndexDecisionHash % 5.
-
Let topTopicWithCallerDomains be epoch’s top 5 topics with caller domains[topTopicIndex].
-
If topTopicWithCallerDomains’s caller domains contains callerContext’s caller domain:
-
Set topic to an empty
BrowsingTopic
dictionary.
-
-
If topic is null, or if topic’s
topic
is 0 (i.e. the candidate topic was cleared), then continue. -
Let randomOrTopTopicDecisionMessageArray be the concatenation of "random-or-top-topic-decision|", epoch’s time, and callerContext’s top level context domain.
-
Let randomOrTopTopicDecisionHmacOutput be the output of the HMAC algorithm, given input parameters: whichSha=SHA256, key=user agent’s user topics state's hmac key, and message_array=randomOrTopTopicDecisionMessageArray.
-
Let randomOrTopTopicDecisionHash be 64-bit truncation of randomOrTopTopicDecisionHmacOutput.
-
If randomOrTopTopicDecisionHash % 100 < 5:
-
Let randomTopicIndexDecisionMessageArray be the concatenation of "random-topic-index-decision|", epoch’s time, and callerContext’s top level context domain.
-
Let randomTopicIndexDecisionHmacOutput be the output of the HMAC algorithm, given input parameters: whichSha=SHA256, key=user agent’s user topics state's hmac key, and message_array=randomTopicIndexDecisionMessageArray.
-
Let randomTopicIndexDecisionHash be 64-bit truncation of randomTopicIndexDecisionHmacOutput.
-
Let randomTopicIndex be randomTopicIndexDecisionHash % epoch’s taxonomy's size.
-
-
Set topic["
configVersion
"] to epoch’s config version. -
Set topic["
modelVersion
"] to epoch’s model version. -
Set topic["
taxonomyVersion
"] to epoch’s taxonomy version. -
Determine the version version, given topic’s
configVersion
,modelVersion
andtaxonomyVersion
as input. -
Set topic["
version
"] to version. -
Add topic to result.
-
-
Sort entries in result given the less-than comparator for the
BrowsingTopic
dictionary. -
Remove duplicate entries in result. Two
BrowsingTopic
dictionaries a and b are considered equal if a is not code unit less than b and b is not code unit less than a. -
Return result.
14. The JavaScript API
The Topics API lives under the Document
interface, and is only available if the document is in secure context.
dictionary {
BrowsingTopicsOptions boolean =
skipObservation false ; };partial interface Document { [SecureContext ]Promise <sequence <BrowsingTopic >>browsingTopics (optional BrowsingTopicsOptions = {}); };
options
browsingTopics(options)
method steps are:
-
Let document be this.
-
Let topLevelDocument be document’s node navigable's top-level traversable's active document.
-
Let promise be a new promise.
-
Let topicsCallerContext be a topics caller context.
-
Set topicsCallerContext’s caller domain to document’s origin's host's registrable domain.
-
Set topicsCallerContext’s top level context domain to topLevelDocument’s origin's host's registrable domain.
-
Let unsafeMoment be the wall clock's unsafe current time.
-
Let moment be the result of running coarsen time algorithm given unsafeMoment and wall clock as input.
-
Let fromUnixEpochTime be the duration from the Unix epoch to moment.
-
Set topicsCallerContext’s timestamp to fromUnixEpochTime.
-
If any of the following is true:
-
document’s origin is an opaque origin.
-
document is not allowed to use the
browsing-topics
feature. -
document is not allowed to use the
interest-cohort
feature.
then:
-
Queue a global task on the browsing topics task source given document’s relevant global object to reject promise with a "
NotAllowedError
"DOMException
. -
Abort these steps.
-
-
Run the following steps in parallel:
-
Let topics be an empty list.
-
If the user preference setting and other user agent-defined mechanisms like enrollment allow access to topics from topLevelDocument given document’s origin:
-
Set topics to the result of running the calculate the topics for caller algorithm, with topicsCallerContext as input.
-
If options["
skipObservation
"] is false:-
Run the collect page topics calculation input data algorithm with topLevelDocument as input.
-
Run the collect topics caller domain algorithm with topLevelDocument and topicsCallerContext’s caller domain as input.
-
-
-
Queue a global task on the browsing topics task source given document’s relevant global object to perform the following steps:
-
Resolve promise with topics.
-
-
-
Return promise.
15. fetch() and iframe integration
Topics can be sent in the HTTP header forfetch()
requests and for iframe navigation requests. The response header for a topics related request can specify whether the caller should be recorded.
15.1. send browsing topics header boolean associated with Request
A request has an associated send browsing topics header boolean. Unless stated otherwise it is false.TODO: make the modification directly to the fetch spec.
15.2. browsingtopics content attribute for HTMLIframeElement
The iframe element contains abrowsingtopics
content attribute. The IDL attribute browsingTopics
reflects the browsingtopics
content attribute.
partial interface HTMLIFrameElement { [CEReactions ]attribute boolean browsingTopics ; };
TODO: make the modification directly to the html spec.
15.3. browsingTopics attribute in RequestInit
The RequestInit dictionary contains a browsingTopics attribute:partial dictionary RequestInit {boolean ; };
browsingTopics
TODO: make the modification directly to the fetch spec.
15.4. Modification to request constructor steps
The following step will be added to the new Request(input, init) constructor steps, before step "Set this’s request to request":-
If init["
browsingTopics
"] exists, then set request’s send browsing topics header boolean to it.
TODO: make the modification directly to the fetch spec.
15.5. Modification to "create navigation params by fetching" steps
The following step will be added to the create navigation params by fetching steps, after step "Let request be a new request, with ...":-
If navigable’s container is an
iframe
element, and if it has abrowsingtopics
content attribute, then set request’s send browsing topics header boolean to true.
TODO: make the modification directly to the html spec.
15.6. The `Sec-Browsing-Topics
` HTTP request header
This specification defines a `Sec-Browsing-Topics
` HTTP request header. It is used to send the topics.
15.7. Modification to HTTP-network-or-cache fetch algorithm
The following step will be added to the HTTP-network-or-cache fetch algorithm, before step "Modify httpRequest’s header list per HTTP. ...":-
Append or modify a request `
Sec-Browsing-Topics
` header for httpRequest.
TODO: make the modification directly to the fetch spec.
15.8. Append or modify a request Sec-Browsing-Topics
header
Sec-Browsing-Topics
` header, given a request request, run these steps:
-
If request’s send browsing topics header boolean is not true, then return.
-
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.
-
Let initiatorWindow be request’s window.
-
If requestOrigin is not a potentially trustworthy origin, then return.
-
If initiatorWindow is not an environment settings object, then return.
-
If initiatorWindow is not a secure context, then return.
-
For each feature f in « "
browsing-topic
", "interest-cohort
" »:-
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.
-
-
Let topLevelDocument be initiatorWindow’s global object's navigable's top-level traversable's active document.
-
Let topicsCallerContext be a topics caller context with default initial field values.
-
Set topicsCallerContext’s caller domain to requestOrigin’s host's registrable domain.
-
Set topicsCallerContext’s top level context domain to topLevelDocument’s origin's host's registrable domain.
-
Let unsafeMoment be the wall clock's unsafe current time.
-
Let moment be the result of running coarsen time algorithm given unsafeMoment and wall clock as input.
-
Let fromUnixEpochTime be the duration from the Unix epoch to moment.
-
Set topicsCallerContext’s timestamp to fromUnixEpochTime.
-
Let topics be an empty list.
-
Let numVersionsInEpochs be 0.
-
If the user preference setting and other user agent-defined mechanisms like enrollment allow access to topics from topLevelDocument given requestOrigin:
-
Set topics to the result of running the calculate the topics for caller algorithm, with topicsCallerContext as input.
-
Set numVersionsInEpochs to the result of running the get the number of distinct versions in epochs algorithm, with topicsCallerContext as input.
-
-
Let versionsToTopics be an ordered map.
-
For each topic of topics:
-
Let topicsStructuredFieldsList be an empty Structured Fields List.
-
For each version → topicIntegers of versionsToTopics:
-
Let innerList be an empty Structured Fields Inner List.
-
Append all items from topicIntegers to innerList.
-
Let topicParameters be an empty Structured Fields Parameters.
-
Set topicParameters["
v
"] to a Structured Fields Token with value version. -
Associate topicParameters with innerList.
-
Append innerList to topicsStructuredFieldsList.
-
-
If numVersionsInEpochs is 0, then set numVersionsInEpochs to 1.
-
Let maxNumberOfEpochs be 3 (i.e. topics are selected from the last 3 epochs).
-
Let topicMaxLength be number of base-10 digits in the maximum topic id (e.g. for Chrome’s current taxonomy, topicMaxLength is 3, as the topic id has maximum 3 digits).
-
Let versionMaxLength be the length of the current maximum version string length.
-
Let listItemsSeparatorLength be 2 (i.e. structured fields use two characters (", ") to separate list items).
-
Let perVersionedTopicsInnerListOverhead be 5 (i.e. for "();v=")
-
Let maxPaddingLength be maxNumberOfEpochs * topicMaxLength + maxNumberOfEpochs - numVersionsInEpochs + numVersionsInEpochs * perVersionedTopicsInnerListOverhead + numVersionsInEpochs * versionMaxLength + (numVersionsInEpochs - 1) * listItemsSeparatorLength.
-
Let paddingLength be maxPaddingLength.
-
If topicsStructuredFieldsList is not empty:
-
Let serializedTopicsList be the result of executing the serializing structured fields algorithm on topicsStructuredFieldsList.
-
Decrement paddingLength by serializedTopicsList’s length.
-
-
Else:
-
Increment paddingLength by listItemsSeparatorLength (i.e. to account for the separator characters that would be added when topics are not empty).
-
-
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 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.
-
Let paddedToken be "P".
-
Append paddingLength
"0"
characters to the end of paddedToken. -
Let paddedEntryParameters be an empty Structured Fields Parameters.
-
Set paddedEntryParameters["
p
"] to a Structured Fields Token with value paddedToken. -
Let emptyInnerList be an empty Structured Fields Inner List.
-
Associate paddedEntryParameters with emptyInnerList.
-
Append emptyInnerList to topicsStructuredFieldsList.
-
Set a structured field value given (`
Sec-Browsing-Topics
`, topicsStructuredFieldsList) in request’s header list.
();p=P0000000000000000000000000000000
(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
Why adding paddings: servers typically have a GET request size limit e.g. 8KB, and will return an error when the limit is reached. An attacker can rely on this to learn the number of topics for a different domain, and/or a small amount of information about the topics themselves (e.g whether the topic ids are < 10, < 100, etc.)
The various lengths being returned (that depends on the number of distinct versions) could leak which epochs the user had disabled topics or didn’t use the browser, if it coincided with the version change. But this leak is minor. The most common cases (i.e. returning same version topics, or no topics) will have the same length.
15.9. The `Observe-Browsing-Topics
` HTTP response header
The `Observe-Browsing-Topics
` HTTP response header can be used to record a caller’s topics observation.
-
If request’s header list does not contain `
Sec-Browsing-Topics
` (implying the request’s current URL is not eligible for topics), then return. -
Let topLevelDocument be request’s window's global object's navigable's top-level traversable's active document.
-
Let callerOrigin be request’s current URL's origin.
-
If the user preference setting or other user agent-defined mechanisms like enrollment disallows access to topics from topLevelDocument given callerOrigin, then return.
-
Let callerDomain be callerOrigin’s host's registrable domain.
-
Let list be response’s header list.
-
Let observe be the result of running get a structured field value algorithm given `
Observe-Browsing-Topics
`, "item", and list as input. -
If observe is true:
-
Run the collect page topics calculation input data algorithm with topLevelDocument as input.
-
Run the collect topics caller domain algorithm with topLevelDocument and callerDomain as input.
-
15.10. Modification to HTTP fetch steps
The following step will be added to the HTTP fetch steps, before checking the redirect status (i.e. "If actualResponse’s status is a redirect status, ..."):-
Handle topics response, given response actualResponse and request request as input.
TODO: make the modification directly to the fetch spec.
16. Permissions policy integration
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 *
.
17. Privacy considerations
The Topics API attempts to provide just enough relevant interest information for advertisers to be able to personalize their ads for the user while maintaining user privacy. Some privacy safeguards include: usage in secure contexts only, topic limitation to a human curated taxonomy, different topics given to different sites in the same epoch to prevent cross-site reidentification, noised topics, a limited number of topics provided per epoch, user opt outs, site opt outs, and a suggestion that user agents provide UX to give users choice in which Topics are returned.