1. Introduction
Modern web applications often dynamically update content in response to user interactions without performing a full page navigation. These interaction-initiated effects—such as structural DOM modifications, contentful paints, and history state changes—have historically been difficult to measure and attribute to the correct user actions.
Consider a typical Single Page Application pattern: a user clicks a product link, which triggers a
click event handler. This handler initiates a network fetch for product details. When the
response arrives, a callback is executed that dynamically injects the new content into the DOM and
uses the History or Navigation API to update the URL. While this appears to the user as a
navigation, existing metrics like Largest Contentful Paint (LCP) ([LARGEST-CONTENTFUL-PAINT]) only
measure the initial page load, and Interaction to Next Paint (INP) only measures the immediate
visual feedback of the click itself, leaving the significant subsequent rendering and "soft"
navigation uncaptured.
This specification leverages the [EVENT-TIMING] API to define interactions and the [ASYNC-CONTEXT] proposal to track causality across asynchronous task boundaries, mapping modified DOM nodes to interactions, enabling attribution of contentful paints to interactions that caused them. It defines how browsers can identify and report these effects, including "Soft Navigations," by integrating with [PAINT-TIMING] and [LARGEST-CONTENTFUL-PAINT] to attribute rendering changes to the performance timeline.
2. Performance Timeline Slicing
To attribute performance metrics (such as event timings, layout shifts, resource loads, or
long animation frames) to their initiating navigation, the performance timeline is sliced into
segments using a navigationId attribute on PerformanceEntry.
Determining the exact slice point for a transition that spans time is complex. To ensure consistent attribution, this specification adopts a model where the timeline is not sliced immediately upon every URL modification; instead, the transition is deferred until a soft navigation is ready to be emitted to the performance timeline.
For a detailed discussion of the design trade-offs, attribution rules, and edge cases, see the [PERFORMANCE-TIMELINE] issue comment.
2.1. Navigation ID
A navigation id is a unique identifier assigned to each navigation (both hard and soft) within a global object’s lifetime.
Each Window has:
-
a current navigation id, a 64-bit unsigned integer, initially set to the
Window’s initial navigation id value. -
a navigation count, a 64-bit unsigned integer, initially 0.
-
a initial navigation id value, a 64-bit unsigned integer, initially set to a random integer between 100 and 10000.
-
a navigation id increment, a 64-bit unsigned integer, initially set to a small positive integer chosen by the user agent.
Note: The initial navigation id value and navigation id increment are
used to calculate navigationId values (see
increment the current navigation id).
This discourages developers from relying on it to count the exact number of navigations or
assuming it starts at zero.
User agents are expected not to use shared global navigation values across different Window
objects to prevent cross-origin leaks.
Window window:
-
Set window’s navigation count to window’s navigation count plus 1.
-
Let newId be window’s initial navigation id value plus (window’s navigation count times window’s navigation id increment).
-
Set window’s current navigation id to newId.
2.2. The PerformanceEntry extension
[Exposed =(Window ,Worker )]partial interface PerformanceEntry {readonly attribute unsigned long long ; };navigationId
PerformanceEntry has an associated navigation id,
a 64-bit unsigned integer, initially 0.
The navigationId attribute’s getter must return this’s navigation id.
2.3. Performance Timeline Integration
In queue a PerformanceEntry, after step 1 (initializing the entry), add the following steps:
-
If newEntry’s navigation id is 0:
-
If global is a
Windowobject:-
Set newEntry’s navigation id to global’s current navigation id.
-
-
3. Interaction Infrastructure
3.1. Interaction Context Intro
This specification leverages the TC39 [ASYNC-CONTEXT] proposal to handle this propagation. Every
new user interaction (as defined by Event Timing) or relevant navigation event creates a new
InteractionContext. This context is stored in a hidden, internal-only AsyncContext
(denoted as ).
The web platform’s integration with AsyncContext ensures that this variable is automatically
attached to asynchronous continuations (e.g., setTimeout, fetch, ), allowing the browser
to attribute later effects back to the original interaction.
In addition to script propagation, this specification defines how user interactions that modify the DOM establish a set of modified nodes associated with the interaction, allowing subsequent rendering effects, like contentful paints, to be traced back to the initiating interaction context even when no asynchronous script is currently running.
3.2. The InteractionContext struct
-
id, a 64-bit unsigned integer respresenting the
interactionIdassociated with this context. -
context document, a Document.
-
start time, a number.
-
navigation type, a
NavigationTypeor null, initially null. -
first URL value, a string or null, initially null.
-
first URL update timestamp, a number, initially 0.
-
first scroll timestamp, a number, initially 0.
-
first input timestamp, a number, initially 0.
-
first contentful paint, an
InteractionContentfulPaintor null, initially null. -
largest contentful paint, an
InteractionContentfulPaintor null, initially null. -
current largest contentful paint candidate, a largest contentful paint candidate or null, initially null.
-
last URL value, a string or null, initially null.
-
emitted, a boolean, initially false.
Note: Future versions of this specification might track all URL updates that occur during an
interaction context to provide a more complete history of the navigation. The last URL value is
tracked internally to ensure accurate attribution of effects back to the final state of the
interaction, even if only the first URL value is currently exposed in the
PerformanceSoftNavigation’s name.
3.3. Infrastructure Algorithms
-
Let context be the value of the internal
AsyncContext. Variable.[[ ActiveInteractionContext]] -
If context is not null and context’s context document is not equal to document, return null.
-
Return context.
PerformanceEventTiming timing entry:
-
Let interaction id be timing entry’s
interactionId. -
Assert interaction id is greater than 0.
-
If document’s interaction id to interaction context[interaction id] exists, return document’s interaction id to interaction context[interaction id].
-
Let interaction context be a new InteractionContext.
-
Set interaction context’s id to interaction id.
-
Set interaction context’s context document to document.
-
Set interaction context’s start time to timing entry’s
startTime. -
Set document’s interaction id to interaction context[interaction id] to interaction context.
-
Return interaction context.
-
Let timestamp be the current high resolution time given document’s relevant global object.
-
Let is scroll be true if event’s type is "scroll", and false otherwise.
-
Let is input be true if event’s type is an event type that would trigger has dispatched input event (as defined in [EVENT-TIMING]), and false otherwise.
-
If is scroll is false and is input is false, return.
-
For each interaction context of document’s interaction id to interaction context’s values:
-
If is scroll is true and interaction context’s first scroll timestamp is 0:
-
Set interaction context’s first scroll timestamp to timestamp.
-
-
If is input is true and interaction context’s first input timestamp is 0:
-
Set interaction context’s first input timestamp to timestamp.
-
-
PerformanceEventTiming object timing entry:
-
If timing entry is null, return null.
-
Let interaction id be timing entry’s
interactionId. -
If interaction id is 0, return null.
-
Let event be timing entry’s associated event.
-
Let document be event’s relevant global object’s associated Document.
-
Update the interaction contexts for an event given document and event.
-
Let interaction context be the result of getting or createing the context for an interaction given document and timing entry.
-
Set the value of the internal
AsyncContext. Variableto interaction context.[[ ActiveInteractionContext]] -
Return interaction context.
-
Set the value of the internal
AsyncContext. Variableto null.[[ ActiveInteractionContext]]
4. Interaction Contentful Paints
4.1. The InteractionContentfulPaint interface
[Exposed =Window ]interface :InteractionContentfulPaint PerformanceEntry {readonly attribute LargestContentfulPaint ;largestContentfulPaint readonly attribute unsigned long long ;interactionId object (); };toJSON InteractionContentfulPaint includes PaintTimingMixin ;
Each InteractionContentfulPaint has:
-
An associated context, an InteractionContext.
-
An associated largest contentful paint, a
LargestContentfulPaintentry. -
An associated paint timing info.
The largestContentfulPaint attribute’s getter must return this’s
largest contentful paint.
The interactionId attribute’s getter must return this’s
context’s id.
The name attribute’s getter must return the empty string.
The entryType attribute’s getter must return .
The startTime attribute’s getter must return this’s
context’s start time.
The duration attribute’s getter must return the difference between the
default paint timestamp for this’s paint timing info and this’s
context’s start time.
Note: While the entry dynamically queries static interaction properties (like interactionId and
startTime) from its associated InteractionContext, it captures a static snapshot of the
paint’s paint timing info and the candidate LargestContentfulPaint entry at creation time to
ensure these metrics reflect the exact state of that visual update.
4.2. Interaction Contentful Paint Algorithms
Window window, an
InteractionContext interaction context, a LargestContentfulPaint lcpEntry, and a
paint timing info paintTimingInfo:
-
Let entry be a new
InteractionContentfulPaintobject in window’s realm. -
Set entry’s context to interaction context.
-
Set entry’s largest contentful paint to lcpEntry.
-
Set entry’s associated paint timing info to paintTimingInfo.
-
Return entry.
-
If interaction context’s first scroll timestamp is greater than 0 and lcpEntry’s
renderTimeis greater than interaction context’s first scroll timestamp, return. -
If interaction context’s first input timestamp is greater than 0 and lcpEntry’s
renderTimeis greater than interaction context’s first input timestamp, return. -
Let lcpEntry be the result of creating a LargestContentfulPaint entry with lcpCandidate, paintTimingInfo, and document.
-
Let global be document’s relevant global object.
-
Let paint timing info be lcpEntry’s associated paint timing info.
-
Let icpEntry be the result of creating an interaction contentful paint entry given global, interaction context, lcpEntry, and paint timing info.
-
Queue icpEntry.
-
Add icpEntry to global’s performance entry buffer.
-
If interaction context’s first contentful paint is null:
-
Set interaction context’s first contentful paint to icpEntry.
-
-
Set interaction context’s largest contentful paint to icpEntry.
-
Set interaction context’s current largest contentful paint candidate to lcpCandidate.
Note: InteractionContentfulPaint entries are emitted to the performance timeline as they are
detected, independently of whether the interaction eventually results in a soft navigation. This
allows developers to monitor rendering updates for all interactions.
Document
document, a paint timing info paintTimingInfo, an ordered set of
pending image records paintedImages, and an ordered set of elements
paintedTextNodes:
-
Let contextToCandidateSets be a new map.
-
For each record of paintedImages:
-
Let element be record’s element.
-
Let interaction context be the result of getting the paint attribution interaction context for element.
-
If interaction context is not null:
-
-
For each element of paintedTextNodes:
-
Let interaction context be the result of getting the paint attribution interaction context for element.
-
If interaction context is not null:
-
-
For each interaction context → candidateSets in contextToCandidateSets:
-
Let newCandidate be the result of computing a new largest contentful paint candidate given document, candidateSets[0], candidateSets[1], and interaction context’s current largest contentful paint candidate.
-
If newCandidate is not null:
-
Process a new attributed contentful paint candidate given newCandidate, document, and interaction context, and paintTimingInfo.
-
Evaluate soft navigation emission given document and interaction context.
-
-
4.3. Interaction Contentful Paint Attribution
-
context, an InteractionContext, initially null.
-
modification id, a 64-bit unsigned integer, initially 0.
-
Return true if other node state is null or node state’s modification id is greater than other node state’s modification id, and false otherwise.
-
Let document be node’s node document.
-
Let node state be document’s propagated node state[node] with default null.
-
If node state is null return null, otherwise return node state’s context.
-
Let document be node’s node document.
-
If target node is not exposed for paint timing given document, return.
-
Let interaction context be the result of getting the current interaction context given document.
-
If interaction context is null, return.
-
If document’s last modification context is not equal to interaction context:
-
Set document’s last modification context to interaction context.
-
Increment document’s current modification generation id by 1.
-
-
Let previous node state be document’s marked node state[target node] with default null.
-
If previous node state is not null and previous node state’s modification id equals document’s current modification generation id, return.
-
Let node state to a new InteractionPaintTimingNodeState.
-
Set node state’s context to interaction context.
-
Set node state’s modification id to document’s current modification generation id.
-
Set document’s marked node state[node] to node state.
-
Set document’s is interaction paint timing dirty to true.
-
If document’s is interaction paint timing dirty is false, return.
-
Update the interaction context for a node and its descendants given document and null.
-
Set document’s is interaction paint timing dirty to false.
-
Let document be node’s node document.
-
Let node state be document’s marked node state[node] with default null.
-
If node state is not null:
-
If node state is more recent than inherited node state:
-
Set inherited node state to node state.
-
If node is a
Textnode, then remove document’s marked node state[node].
-
-
Otherwise, remove document’s marked node state[node].
-
-
If inherited node state is not null and node is timing-eligible:
-
Let attribution element be the result of getting the attribution element for node.
-
Let previous propagated state be propagated node state[attribution element] with default null.
-
If inherited node state is more recent than previous propagated state:
-
Set propagated node state[attribution element] to inherited node state.
-
If previous propagated state is null or previous propagated state’s context is not equal to inherited node state’s context, then reset paint tracking for attribution element.
-
-
-
For each child child node of node, update the interaction context for a node and its descendants given child node and inherited node state.
Note: For simplicity, this algorithm walks the entire DOM whenever the propagated state needs to to
be reprocessed. This can be optimized to only process parts of the DOM that are paintable, e.g.
subtrees that are known to be non-visible or skipped by existing mechanisms like
content or CSS containment could also be skipped during this process, as long as they
are updated before the next time they are painted. Also note that this can be further optimized by
merging these steps with another (pre-paint) tree walk, as long as the relevant state is updated
before paint.
Note: The [CONTAINER-TIMING] API provides a mechanism for grouping rendering effects by their common DOM ancestor and report aggregated paint information to the performance timeline. In a future version of this specification, we plan to expand Container Timing to support multiple clients for a single container (i.e. the global "containertiming" attribute and per-interaction timing) to allow implementing interaction paint timing in terms of Container Timing. Each recorded node would be considered a container for the interaction that modified it, so that each interaction is associated with a set of containers. Then, this specification would track the largest contentful paint that occurs in a marked container, for each interaction. Furthermore, we could expose other useful information that Container Timing tracks, e.g. the aggregated total painted area of containers for each interaction.
5. Soft Navigations
5.1. The PerformanceSoftNavigation interface
[Exposed =Window ]interface :PerformanceSoftNavigation PerformanceEntry {readonly attribute NavigationType ;navigationType readonly attribute unsigned long long ;interactionId InteractionContentfulPaint ?(); };getLargestInteractionContentfulPaint PerformanceSoftNavigation includes PaintTimingMixin ;
Each PerformanceSoftNavigation has:
-
An associated context, an InteractionContext.
-
An associated paint timing info.
The navigationType attribute’s getter must return this’s
context’s navigation type.
The interactionId attribute’s getter must return this’s
context’s id.
The getLargestInteractionContentfulPaint() method must return this’s
context’s largest contentful paint.
The name attribute’s getter must return this’s
context’s first URL value.
The entryType attribute’s getter must return .
The startTime attribute’s getter must return this’s
context’s start time.
The duration attribute’s getter must return the difference between this’s
presentationTime and this’s context’s
start time at the time of emission.
Note: In practice, getLargestInteractionContentfulPaint() is expected to return a non-null
entry, as a confirmed soft navigation requires at least one detected interaction contentful paint to
trigger its emission.
However, future iterations could evaluate whether a soft navigation could be considered committed
immediately upon URL modification, prior to the first paint. In such a model, this method might
return null at the time of emission.
The primary design consideration for this timing is the attribution of other timeline entries (e.g.,
LayoutShift, PerformanceResourceTiming, LongAnimationFrameTiming, etc) that occur in the
interval between the URL modification and the first paint. Many sites commit the new URL
immediately upon initiating a fetch request, for example, even while the current page remains in the
previous navigation state. To ensure consistent timeline slicing, this specification attributes all
such entries to the previous navigation identifier until the first paint confirms the transition.
5.2. Soft Navigation Algorithms
A soft navigation is a same-document navigation that satisfies the following conditions:
-
A same-document URL change occurs while an InteractionContext is active.
-
A contentful paint occurs that is attributed to the same InteractionContext.
-
If interaction context’s emitted is true, return.
-
If document’s active soft navigation candidate is not interaction context, return.
-
If interaction context’s first URL value is null, return.
-
If interaction context’s first contentful paint is null, return.
-
Let window be document’s relevant global object.
-
Let entry be the result of creating a soft navigation entry given window and interaction context.
-
Emit a soft navigation entry given window and entry.
-
Set interaction context’s emitted to true.
Window window, and an
InteractionContext interaction context:
-
Let entry be a new
PerformanceSoftNavigationobject in window’s realm. -
Set entry’s context to interaction context.
-
Let first paint be interaction context’s first contentful paint.
-
Assert first paint is not null.
-
Set entry’s associated paint timing info to first paint’s associated paint timing info.
-
Return entry.
Window window and a
PerformanceSoftNavigation entry:
-
Increment the current navigation id given window.
-
Queue entry.
-
Add entry to window’s performance entry buffer.
Note: The navigationId for this PerformanceSoftNavigation entry is set automatically as
part of queuing it to the performance timeline, matching the behavior of other
PerformanceEntry types.
NavigationType navigation type:
-
If url is equal to document’s url, return.
-
Let interaction context be the result of getting the current interaction context given document.
-
If interaction context is null, return.
-
Set interaction context’s last URL value to url.
-
If interaction context’s first URL update timestamp is null:
-
Set interaction context’s first URL update timestamp to the current high resolution time given document’s relevant global object.
-
Set interaction context’s first URL value to url.
-
Set interaction context’s navigation type to navigation type.
-
-
Set document’s active soft navigation candidate to interaction context.
-
Evaluate soft navigation emission given document and interaction context.
6. Specification Integrations
6.1. DOM integration
6.1.1. Document
Each document has an interaction id to interaction context, a map, initially empty.
Each document has an active soft navigation candidate, an InteractionContext or null, initially null.
Each document has a current modification generation id, a 64-bit unsigned integer initilized to 0.
Note: The current modification generation id changes when a relevant DOM modification occurs with a different InteractionContext from the previous modification. This allows grouping together related modifications, which enables comparing InteractionPaintTimingNodeState objects in terms of recency and optimizing which nodes need to be tracked.
Each document has a last modification context, an InteractionContext or null, initially null.
Each document has a marked node state, a map of Node to InteractionPaintTimingNodeState, initially empty.
Each document has a propagated node state, a map of Node to InteractionPaintTimingNodeState, initially empty.
Each document has an is interaction paint timing dirty, a boolean, initially false.
6.1.2. Node
-
The
orclass styleattribute of an element is modified. -
A resource attribute (such as
srcon animgorvideoelement) is modified.
Run the following steps:
6.2. HTML integration
6.2.1. History
documentsEntryChanged is
true and if documentIsNew is false), process a same document commit given the Document,
the target entry’s url, and "traverse".
push" or
"replace").
6.2.2. Hard Navigation
When a new Window is created (e.g., during a "hard" navigation), its
current navigation id is initialized as specified in § 2.1 Navigation ID.
PerformanceNavigationTiming entry for the
initial navigation, the user agent must set its navigationId to the Window’s
current navigation id.
6.3. Event Timing integration
This specification extends the [EVENT-TIMING] definition of interactions to include additional event types that are relevant for modern web applications and Single Page Applications.
The following event types are considered to be part of an interaction (as defined in [EVENT-TIMING]):
-
navigate -
popstate -
hashchange
When these events are dispatched as a result of a user interaction, the user agent must assign them
a unique interactionId obtained by getting the next interactionId
for the document’s relevant global object. If an event is triggered by a previous interaction
that already has an assigned interactionId, the user agent should reuse
that same identifier.
Note:These events are only reported as primary interactions when they are the initiating event from
a user action (e.g., clicking the browser UI’s back button). In most other scenarios—such as a
click handler manually manipulating history—the subsequent popstate or navigate events are not
considered independent user interactions.
While these programmatically triggered events might not have the isTrusted flag set, they are
correctly attributed back to the original interaction via [ASYNC-CONTEXT], as the history and
navigation APIs are treated as asynchronous continuations of the initiating task.
Note: The events navigate, popstate, and hashchange are used as internal signals for
tracking soft navigations and assigning interactionId. This specification does not require these
event types to be exposed as PerformanceEventTiming entries to the performance timeline, leaving
that determination to the [EVENT-TIMING] specification.
Note: The [EVENT-TIMING] specification is expected to be updated to explicitly define
processingStart and processingEnd hooks, and to ensure interactionId assignment happens early
enough for this integration. Today, interactionId assignment is typically deferred until the end
of the event processing.
This specification uses unsigned for interactionId to ensure a safe global
counter, while [EVENT-TIMING] currently defines it as unsigned . This mismatch is expected
to be resolved in future versions of both specifications.
Window window:
-
Set window’s interaction count to window’s interaction count plus 1.
-
Return window’s initial interactionId value plus (window’s interaction count times window’s interactionId increment).
-
Right after the step that sets timingEntry to the result of initialize and record event timing processing start, run the following step:
-
Set the current interaction context for event dispatch given timingEntry.
-
-
Right before the step that calls record event timing processing end, run the following step:
6.4. Paint Timing integration
This specification extends the [PAINT-TIMING] specification to reset paint tracking for elements that have been modified by interactions and to trigger paint attribution for rendered documents.
Document
document:
Mark paint time mixes together steps that should come before and after the UA paints the document. The step added here is expected to occur before paint. [w3c/paint-timing Issue #126]
Document document, a paint timing info paintTimingInfo,
an ordered set of pending image records paintedImages, and an ordered set of
elements paintedTextNodes:
-
Report interaction contentful paints and soft navigations given document, paintTimingInfo, paintedImages, and paintedTextNodes.
Add the following algorithm:
-
Reset the text element tracking node.
-
Reset the image element tracking node.
This is a placeholder algorithm. We need to allow repaints for relevant elements, but the repaints should not affect Element Timing or LCP. Text and images have different mechanisms for detecting when a relevant paint has occurred, and both will need to be updated to support repaints.
Add the following algorithm:
-
If node is a
Textnode, then return theElementthat determines the containing block of node. -
Otherwise, return node.
6.5. Largest Contentful Paint (LCP) integration
7. Security & privacy considerations
Exposing Soft Navigations to the performance timeline doesn’t have security and privacy implications on its own. However, resetting the various paint timing entries as a result of a detected soft navigation can have implications, especially before visited links are partitioned. As such, exposing such paint operations without partitioning the :visited cache needs to only be done after careful analysis of the paint operations in question, to make sure they don’t expose the user’s history across origins.8. Appendix: Overlapping Interactions and Race Conditions
Web applications often process multiple user interactions in rapid succession. This specification handles such overlapping interactions through the following model:
-
Context Independence: Each interaction manages its own InteractionContext independently. Multiple contexts can be "in flight" simultaneously, each tracking its own URL modifications and contentful paints.
-
Active Candidate Singleton: While many interactions can be active, the Document recognizes only one active soft navigation candidate at any given time.
-
Preemption: An interaction context becomes the active soft navigation candidate the moment it triggers its first same-document URL modification. If a subsequent interaction modification occurs, it preempts the previous one and becomes the new candidate.
-
Emission Validation: A
PerformanceSoftNavigationis only emitted for the interaction context that is the active soft navigation candidate at the moment the emission criteria (URL change + contentful paint) are met. This ensures that soft navigation reporting remains consistent with the document’s current visual and navigation state. -
Persistent Attribution: Even if an interaction is preempted as a soft navigation candidate, it continues to attribute and report its own
InteractionContentfulPaintentries as long as it remains active. This allows for accurate measurement of concurrent rendering updates that are not themselves considered "navigations."