1. 简介
现代 Web 应用程序通常会响应用户交互动态更新内容,而不 执行完整的页面导航。这些由交互发起的影响——例如结构性 DOM 修改、内容绘制和历史状态变更——在历史上一直难以测量, 也难以归因到正确的用户操作。
考虑一种典型的单页应用程序模式:用户点击产品链接,这会触发一个 click 事件处理器。该处理器为产品详情发起网络 fetch。当响应到达时,会执行一个
回调,将新内容动态注入 DOM,并使用 History 或
Navigation API 更新 URL。虽然这在用户看来像一次导航,但诸如
Largest Contentful Paint (LCP) ([LARGEST-CONTENTFUL-PAINT]) 这样的现有指标只测量初始页面加载,
而 Interaction to Next Paint (INP) 只测量点击本身的即时视觉反馈,
使得后续重要的渲染和“软”导航未被捕获。
本规范利用 [EVENT-TIMING] API 来定义交互,并利用 [ASYNC-CONTEXT] 提案来跨异步任务边界跟踪因果关系。它定义浏览器如何识别 和报告这些影响,包括“软导航”,方法是与 [PAINT-TIMING] 和 [LARGEST-CONTENTFUL-PAINT] 集成,以将渲染变化归因到 性能时间线。
此外,本规范还与 [CONTAINER-TIMING] 提案集成。当 用户交互导致 DOM 修改时,这些被修改的节点会被指定为新的“容器根”。 这些子树内任何后续内容绘制都会归因到其各自的根,而这些根 随后会被追溯到原始交互。这允许高效且准确地测量 在初始事件派发很久之后发生的丰富动态页面更新。
2. 交互基础设施
2.1. 导航 ID
导航 id 是在一个 全局对象 的生命周期内分配给每次导航(硬导航和软导航)的唯一 标识符。
每个 全局对象 都有一个当前导航 id,一个 unsigned ,初始设置为与初始化 Document 的 initial interactionId value 以及 Navigation Timing 的初始
navigationId 相同的值。
2.2. 交互上下文简介
本规范利用 TC39 [ASYNC-CONTEXT] 提案来处理这种传播。每个新的用户
交互(由 Event Timing 定义)或相关导航事件都会创建一个新的
InteractionContext。此上下文存储在一个隐藏的、仅供内部使用的 AsyncContext 中(记作 )。
Web 平台与 AsyncContext 的集成确保此变量会自动附加到
异步延续(例如 setTimeout、fetch、),使
浏览器能够将后续影响归因回原始交互。
除了脚本传播之外,本规范还定义修改 DOM 的用户交互如何 建立 容器根(通过 [CONTAINER-TIMING])。这些根 使后续渲染影响(如内容绘制)即使在当前没有异步脚本活动时, 也能被追溯回发起该影响的交互上下文。
2.3. InteractionContext 结构体
-
id,初始未设置 - 与此上下文关联的交互 id。
-
开始时间,初始未设置 - 表示交互的开始 时间。
-
导航类型,初始未设置 - 表示 导航的类型(例如 "push"、"replace")。
-
第一个 URL 值,初始未设置 - 表示 第一次修改时 URL 的值。
-
第一个 URL 更新时间戳, 初始未设置 - 表示此交互期间第一次 URL 修改的时间。
-
第一个滚动时间戳,初始未设置 - 表示此交互期间第一次滚动事件的时间。
-
第一个输入时间戳,初始未设置 - 表示此交互期间第一次输入事件的时间。
-
第一次内容绘制,初始为 null - 此交互的第一个
InteractionContentfulPaint条目。 -
最大内容绘制,初始为 null - 到目前为止此交互的最大
InteractionContentfulPaint条目。 -
总绘制面积,初始为 0 - 归因到 此交互的所有内容绘制面积之和。
-
最后一个 URL 值,初始未设置 - 表示 此交互期间最近一次 URL 修改的值。
-
emitted 标志,初始为 false - 指示该交互已发出一个软 导航条目。
SoftNavigationEntry
的
name 中,也能将影响准确归因回交互的最终状态。
2.4. 基础设施算法
-
令 context 为内部
AsyncContext. Variable的值。[[ ActiveInteractionContext]] -
返回 context。
-
如果 document 的 interaction id 到 interaction context[interaction id] 存在,返回 document 的 interaction id 到 interaction context[interaction id]。
-
令 interaction context 为一个新的 InteractionContext。
-
将 interaction context 的 id 设置为 interaction id。
-
将 interaction context 的 开始 时间设置为 event 的
startTime。 -
设置 document 的 interaction id 到 interaction context[interaction id] 为 interaction context。
-
返回 interaction context。
-
令 is scroll 为 true,如果 event 的类型为 "scroll";否则为 false。
-
令 is input 为 true,如果 event 的类型是会触发 has dispatched input event 的事件类型(如 [EVENT-TIMING] 中所定义);否则为 false。
-
如果 is scroll 为 false 且 is input 为 false,则返回。
-
对于 document 的 interaction id 到 interaction context 的值中的每个 interaction context:
-
如果 interaction id 为 null,则返回 null。
-
令 interaction context 为使用 document、interaction id 和 event 调用 获取或创建交互的上下文 的结果。
-
将内部
AsyncContext. Variable的值设置为 interaction context。[[ ActiveInteractionContext]] -
返回 interaction context。
-
如果 interaction context 为 null,则返回。
-
将内部
AsyncContext. Variable的值设置为 null。[[ ActiveInteractionContext]]
3. 交互内容绘制
3.1. InteractionContentfulPaint 接口
[Exposed =Window ]interface :InteractionContentfulPaint PerformanceEntry {readonly attribute DOMHighResTimeStamp ;renderTime readonly attribute DOMHighResTimeStamp ;loadTime readonly attribute unsigned long long ;size readonly attribute DOMString ;id readonly attribute DOMString ;url readonly attribute Element ?;element readonly attribute unsigned long long ;interactionId object (); };toJSON InteractionContentfulPaint includes PaintTimingMixin ;
每个 InteractionContentfulPaint
都有一个关联的绘制时序信息。
renderTime
属性的 getter 必须返回 this 的关联绘制
时序信息的渲染更新结束时间。
loadTime
属性的 getter 必须返回 this 的关联绘制
时序信息的实现定义的
呈现时间。
size
属性的 getter 必须返回内容绘制的大小。
id
属性的 getter 必须返回内容绘制的 ID。
url
属性的 getter 必须返回内容绘制的 URL。
element
属性的 getter 必须返回与内容绘制关联的 Element,
或者如果该元素已从文档中移除,则返回 null。
interactionId
属性的 getter 必须返回触发此绘制的交互的交互
id。
3.2. 交互内容绘制算法
-
令 entry 为 global 的 realm 中的一个新的
InteractionContentfulPaint对象。 -
将 entry 的
entryType设置为 "interaction-contentful-paint"。 -
将 entry 的
element设置为 element。 -
将 entry 的
interactionId设置为 interaction context 的 id。 -
将 entry 的
renderTime、loadTime、size、id、 以及url设置为 report largest contentful paint 算法中定义的内容绘制候选的对应值, 如 [LARGEST-CONTENTFUL-PAINT] 中所述。 -
将 entry 的
duration设置为 entry 的renderTime与 entry 的startTime之间的差值。 -
返回 entry。
-
令 global 为 document 的相关全局对象。
-
令 paint entry 为使用 global、interaction context 和 element 调用 创建交互内容 绘制条目的结果。 注:对于需要资源加载的元素(例如带有新
src的图像),[PAINT-TIMING] 规范负责确保在触发最终调用 此算法的内容绘制检测之前,满足 "is loaded" 标准。 -
如果 interaction context 的 第一个滚动时间戳 已设置, 并且 paint entry 的
renderTime大于 interaction context 的 第一个滚动时间戳,则返回。 -
如果 interaction context 的 第一个输入时间戳 已设置,并且 paint entry 的
renderTime大于 interaction context 的 第一个输入时间戳,则返回。 -
排队 paint entry。
-
将 paint entry 添加到 global 的性能条目缓冲区。
-
如果 interaction context 的 第一次内容绘制 为 null:
-
将 interaction context 的 第一次内容绘制 设置为 paint entry。
-
-
将 interaction context 的 最大内容绘制设置为 paint entry。
-
将 interaction context 的 总绘制面积增加 paint entry 的 size。
InteractionContentfulPaint 条目会在检测到时发出到性能
时间线,而不取决于该交互最终是否产生软
导航。这允许开发者监测所有交互的渲染更新。
4. 软导航
4.1. SoftNavigationEntry 接口
[Exposed =Window ]interface :SoftNavigationEntry PerformanceEntry {readonly attribute DOMString ;navigationType readonly attribute unsigned long long ;interactionId readonly attribute InteractionContentfulPaint ?; };largestInteractionContentfulPaint SoftNavigationEntry includes PaintTimingMixin ;
每个 SoftNavigationEntry
都有一个关联的绘制时序信息。
navigationType
属性的 getter 必须返回触发
软导航的交互的导航类型。
interactionId
属性的 getter 必须返回触发软导航的交互的交互
id。
largestInteractionContentfulPaint
属性的 getter 必须返回一个 InteractionContentfulPaint
条目,该条目表示由于该交互发生的最大内容绘制;如果
没有发生此类绘制,则返回 null。
name
属性的 getter 必须返回触发软导航的交互的第一个 URL 值。
startTime
属性的 getter 必须返回触发
软导航的交互的开始时间。
duration
属性的 getter 必须返回在发出时 this 的 presentationTime
与 this 的
startTime
之间的差值。
largestInteractionContentfulPaint
预计为非 null,因为确认的软导航要求至少检测到一个交互
内容绘制来触发其发出。
不过,未来迭代可能会评估软导航是否可在 URL 修改后、 第一次绘制之前立即被视为已提交。在这样的模型中,此字段在发出时可能为 null。
此时序的主要设计考量是归因其他时间线条目(例如
LayoutShift、PerformanceResourceTiming、
LongAnimationFrameTiming 等),这些条目发生在 URL
修改与第一次绘制之间的区间内。例如,许多站点会在发起 fetch
请求时立即提交新的 URL,即使当前页面仍处于上一个导航状态。为确保
时间线切片一致,本规范将所有此类条目归因到前一个
导航标识符,直到第一次绘制确认转换。
4.2. 软导航算法
软导航是 满足以下条件的同文档导航:
-
在一个 InteractionContext 活动时发生同文档 URL 变更。
-
发生一个内容绘制,并且该绘制归因到同一个 InteractionContext。
-
如果 interaction context 的 emitted 为 true,则返回。
-
如果 document 的 活动软导航候选 不是 interaction context,则返回。
-
如果 interaction context 的 第一个 URL 值未设置,则返回。
-
如果 interaction context 的 第一次内容绘制为 null, 则返回。
-
令 global 为 document 的相关全局对象。
-
令 url 为 interaction context 的 第一个 URL 值。
-
令 entry 为使用 global、interaction context、 url 和 interaction context 的 开始 时间调用 创建 软导航条目的结果。
-
使用 global 和 entry 调用 发出软导航条目。
-
将 interaction context 的 emitted 设置为 true。
DOMHighResTimeStamp
start time,运行以下步骤:
-
令 entry 为 global 的 realm 中的一个新的
SoftNavigationEntry对象。 -
将 entry 的
name设置为 url。 -
将 entry 的
entryType设置为 "soft-navigation"。 -
将 entry 的
startTime设置为 start time。 -
令 first paint 为 interaction context 的 第一次内容绘制。
-
如果 first paint 不是 null:
-
将 entry 的
interactionId设置为 interaction context 的 id(如果可用)。 -
将 entry 的
largestInteractionContentfulPaint设置为 interaction context 的 最大内容绘制。 -
返回 entry。
navigationId 在后面,在 queue a PerformanceEntry 中设置。
SoftNavigationEntry
entry,运行以下步骤:
-
将 entry 的 导航 id 设置为 entry 的
interactionId。 -
将 global 的 当前导航 id 设置为 entry 的
interactionId。 -
排队 entry。
-
将 entry 添加到 global 的性能条目缓冲区。
-
令 interaction context 为调用 获取 当前交互上下文的结果。
-
如果 interaction context 为 null,则返回。
-
将 interaction context 的 最后一个 URL 值设置为 url。
-
如果 interaction context 的 第一个 URL 更新时间戳 未设置:
-
将 interaction context 的 第一个 URL 更新 时间戳设置为给定 document 的相关全局对象时的当前高精度时间。
-
将 interaction context 的 第一个 URL 值设置为 url。
-
将 interaction context 的 导航类型设置为 navigation type。
-
-
将 document 的 活动软导航候选 设置为 interaction context。
-
使用 document 和 interaction context 调用 评估软导航发出。
5. PerformanceEntry 扩展
[Exposed =(Window ,Worker )]partial interface PerformanceEntry {readonly attribute unsigned long long ; };navigationId
PerformanceEntry
都有一个关联的导航 id,一个 unsigned long long ,初始为 0。
navigationId
属性的 getter 必须返回 this 的导航 id。
6. 规范集成
6.1. HTML 集成
6.1.1. Document
每个 document 都有一个interaction id 到 interaction context,一个映射,初始为空。
每个 document 都有一个活动软导航 候选,为一个 InteractionContext 或 null,初始为 null。
6.1.2. History
documentsEntryChanged 为 true 且
documentIsNew 为 false),
使用该 Document、entry 的 url,以及 "traverse" 调用 处理同文档提交。
6.1.3. Node
每个 node 都有一个关联的交互 上下文,初始为 null。
6.1.4. 硬导航
当创建一个新的 全局对象 global 时(例如在一次“硬” 导航期间),其 当前导航 id 会按 § 2.1 导航 ID 中的规定 初始化。
PerformanceNavigationTiming
条目时,用户代理必须将其 navigationId 设置为
全局对象的当前
导航 id。
6.2. Event Timing 集成
本规范扩展了 [EVENT-TIMING] 对交互的定义,以包含与现代 Web 应用程序和单页应用程序 相关的其他事件类型。
以下事件类型被认为是交互 的一部分(如 [EVENT-TIMING] 中所定义):
-
navigate -
popstate -
hashchange
当这些事件作为用户交互的结果被派发时,用户代理必须为它们分配一个唯一的 交互 id,该 id 通过为文档的相关全局对象运行获取下一个 interactionId 的步骤来获得。如果事件由一个已分配 交互 id 的先前交互触发,用户代理应复用同一个标识符。
click 处理器手动操作历史记录——后续的 popstate 或 navigate 事件不被认为是
独立的用户交互。
虽然这些以编程方式触发的事件可能没有设置 isTrusted
标志,但它们会通过 [ASYNC-CONTEXT] 正确归因回原始交互,
因为历史记录和导航 API 被视为发起任务的异步延续。
navigate、popstate 和 hashchange 被用作跟踪软导航和
分配 interactionId 的内部信号。本规范不要求这些事件
类型作为 PerformanceEventTiming 条目暴露到性能
时间线,而是将该决定留给 [EVENT-TIMING] 规范。
processingStart
和 processingEnd 钩子,并确保 interactionId
分配足够早地发生以支持此集成。目前,
interactionId 分配通常会延迟到事件
处理结束时。
unsigned long long ,
以确保安全的全局计数器,而 [EVENT-TIMING] 当前将其定义为
unsigned long 。预计此不匹配会在
两个规范的未来版本中解决。
Window
window 获取下一个
interactionId:
-
将 window 的 interaction count 设置为 window 的 interaction count 加 1。
-
返回 window 的 initial interactionId value 加上 (window 的 interaction count 乘以 window 的 interactionId increment)。
-
使用 document 和 event 调用 更新事件的交互上下文。
-
令 interaction id 为 event 的 交互 id。
-
使用 document、interaction id 和 event 调用 交互事件处理开始。
-
使用 interaction context 调用 交互事件时序处理 结束。
6.3. Largest Contentful Paint (LCP) 集成
本规范挂接到 [LARGEST-CONTENTFUL-PAINT] 算法,以将绘制归因到 交互。
-
令 contextToNewLargestCandidate 为一个新的映射。
-
对于 paintedImages 的每个 record:
-
令 element 为 record 的 待处理图像记录元素。
-
令 interaction context 为 element 的关联交互上下文。
-
如果 interaction context 为 null,则继续。
-
令 size 为 element 的有效视觉大小。
-
如果 size 为 null,则继续。
-
如果 interaction context 的 最大内容 绘制不是 null,且 size 小于或等于 interaction context 的 最大内容 绘制的
size, 则继续。 -
如果 contextToNewLargestCandidate[interaction context] 未设置,或者 size 大于 contextToNewLargestCandidate[interaction context] 的
size:-
设置 contextToNewLargestCandidate[interaction context] 为 element。
-
-
-
对于 paintedTextNodes 的每个 textNode:
-
令 interaction context 为 textNode 的关联交互 上下文。
-
如果 interaction context 为 null,则继续。
-
令 size 为 textNode 的有效视觉大小。
-
如果 size 为 null,则继续。
-
如果 interaction context 的 最大内容 绘制不是 null,且 size 小于或等于 interaction context 的 最大内容 绘制的
size, 则继续。 -
如果 contextToNewLargestCandidate[interaction context] 未设置,或者 size 大于 contextToNewLargestCandidate[interaction context] 的
size:-
设置 contextToNewLargestCandidate[interaction context] 为 textNode。
-
-
-
对于 contextToNewLargestCandidate 中的每个 interaction context → newLargestElement:
-
使用 newLargestElement、document 和 interaction context 调用 发出交互内容 绘制条目。
-
使用 document 和 interaction context 调用 评估软导航 发出。
-
6.4. Container Timing 集成
[CONTAINER-TIMING] API 提供一种机制,用其共同 DOM 祖先对渲染影响 进行分组。本规范与该机制集成,以将渲染 变化归因回用户交互。
-
元素的
或class style属性被修改。 -
资源属性(例如
img或video元素上的src)被修改。
运行以下步骤:
-
令 interaction context 为调用 获取 当前交互上下文的结果。
-
如果 interaction context 不是 null:
-
将该节点的关联交互上下文 设置为 interaction context。
-
将该节点指定为一个 容器根(如 [CONTAINER-TIMING] 中所定义)。
-
对于该节点及其每个后代,将它们从 Document 的先前已报告绘制中移除(如 [PAINT-TIMING] 中所定义)。
-
appendChild、innerHTML
更新等)。由用户代理负责将这些操作映射到
跟踪文档结构修改的内部实现钩子。
此外,为避免在大型 DOM 修改期间执行昂贵的主线程工作,鼓励用户代理
优化从先前已报告绘制中移除后代的过程。
用户代理可以将修改后的根标记为 "dirty",并在后续树遍历期间
(例如布局或绘制期间)惰性传播该状态,而不是立即进行穷尽遍历。已知
不可见或被现有机制(如 content 或 CSS containment)跳过的子树,
在此重置过程中也可以被跳过。
6.5. Performance Timeline 集成
在 queue a PerformanceEntry 中,在步骤 1(初始化 条目)之后,添加以下步骤:
7. 重叠交互和竞态条件
Web 应用程序通常会快速连续处理多个用户交互。本规范通过以下模型处理 此类重叠交互:
-
上下文独立性:每个交互独立管理自己的 InteractionContext。 多个上下文可以同时“进行中”,每个上下文都跟踪自己的 URL 修改和 内容绘制。
-
抢占:交互上下文在触发第一次同文档 URL 修改的那一刻 成为活动软导航候选。 如果后续交互修改发生,它会抢占前一个候选,并成为新的候选。
-
发出验证:只有在满足发出标准(URL 变更 + 内容绘制) 的时刻,作为活动软导航候选 的交互上下文,才会发出
SoftNavigationEntry。 这确保软导航报告与文档当前的视觉和导航 状态保持一致。 -
持久归因:即使一个交互作为软导航 候选被抢占,只要它仍处于活动状态,它仍会继续归因并报告自己的
InteractionContentfulPaint条目。这允许准确测量并发的 渲染更新,即使这些更新本身并不被视为“导航”。