事件时序 API

W3C 工作草案,

关于本文档的更多信息
此版本:
https://www.w3.org/TR/2026/WD-event-timing-20261013/
最新发布版本:
https://www.w3.org/TR/event-timing/
编辑草案:
https://w3c.github.io/event-timing/
先前版本:
历史记录:
https://www.w3.org/standards/history/event-timing/
测试套件:
https://github.com/web-platform-tests/wpt/tree/master/event-timing
反馈:
GitHub
编辑:
(Google)
前编辑:
(Google)
(Google)

摘要

本文档定义了一个 API,能够为网页作者提供由用户交互触发的某些事件的延迟情况洞察。

本文档状态

本节描述了本文档在发布时的状态。 当前 W3C 出版物列表以及本技术报告的最新修订版可在 W3C 标准与草案索引中找到。

本文档由 Web 性能工作组 按照 推荐规范流程 以工作草案的形式发布。 发布为工作草案并不代表 W3C 及其成员的认可。

本文档为草稿文件, 可能随时被更新、替换或废止。 除非作为正在进行中的工作引用,否则不适合作为正式文献引用。

GitHub Issues 是本规范讨论的首选渠道。

本文档受 2025年8月18日 W3C 流程文件管理。

本文档由遵循 W3C 专利政策的工作组制作。 W3C 维护一份 公开的专利披露列表 ,该列表与工作组的交付物相关; 该页面还包含专利披露说明。 若个人实际知悉某项专利且认为其包含核心权利要求, 必须根据 W3C 专利政策第6节进行披露。

1. 简介

1.1. 概述

本节为非规范性内容。

当用户与网站交互时,他们希望自己的操作能够快速引发网站的变化。 实际上,研究 表明,任何未在100毫秒内处理的用户输入都会被认为是慢的。 因此,及时展示未达到这一标准的输入事件的性能时序信息非常重要。

监测事件延迟的常见方法是注册一个事件监听器。 可以通过事件的 timeStamp 获取事件被创建的时间戳。 此外,还可以在事件处理逻辑开始和结束时调用 performance.now()。 将硬件时间戳与事件处理开始时获得的时间戳相减,开发者可以计算输入延迟:即输入开始被处理所需的时间。 将事件处理开始和结束时的时间戳相减,可以计算事件处理器内执行的同步工作量。 最后,对于同步处理的输入,事件的硬件时间戳到处理事件后的下一次绘制之间的持续时间是一个有用的用户体验指标。

这种做法存在几个根本性的缺陷。 首先,要求事件监听器会阻止在页面加载早期测量事件延迟, 因为监听器可能还没有注册。 其次,只关注输入延迟的开发者可能被迫为原本没有监听器的事件新增监听器。 这会给事件延迟计算带来不必要的性能负担。 最后,通过这种方式很难测量事件引发的异步工作。

本规范为事件延迟监测提供了另一种解决方案,解决了上述部分问题。 由于时间戳由用户代理计算,测量性能不再需要事件监听器。 这意味着,即使是页面加载早期发生的事件也能被捕获。 同时,这种方式能让分析服务商无需尝试订阅所有可能的事件,也能观察到慢事件。 此外,网站性能不会因不必要的事件监听器而受到影响。 最后,本规范允许开发者获取事件处理后紧接着发生的渲染时序的详细信息, 这有助于衡量事件触发的网站修改所带来的性能开销。

1.2. 交互

本节不具有规范性。

单次用户 Interaction (有时称为手势)通常由多个物理硬件输入事件组成。 每个物理输入事件都可能使用户代理分派多个 UIEvent 事件, 而其中每个事件都可能触发多个自定义事件监听器,或触发不同的默认操作。

例如,用户在触摸屏设备上的一次“轻触”交互实际上由一系列 物理输入事件组成:

这些物理输入事件可能分派一系列 UIEvent 事件:

这些单独的 UIEvent 事件 都将分别成为各自 PerformanceEventTiming 条目报告的候选对象,这有助于进行详细的计时。

注: pointermove 和 touchmove 目前尚未被纳入事件计时的考量范围。

不过,本规范还定义了一种机制,用于将相关的 PerformanceEventTiming 条目 通过 Interaction 进行分组 ,使用 interactionId。 此机制可用于定义一项名为 交互到 下一次绘制(INP) 的页面响应能力指标。

1.3. 首次输入

本节为非规范性内容。

用户的第一个交互 通常对用户体验影响极大,也往往表现出较慢的响应。

为此,事件时序API会暴露 首次输入 的时序信息, 即 Window 的第一个 PerformanceEventTiming 条目,且 interactionId 不为0。

与大多数 PerformanceEventTiming 不同, 首次输入条目即使未超过指定的 durationThreshold 也会被报告,并且即使未超过默认104毫秒的阈值也会被缓冲。 这一机制可用于定义页面响应性指标,称为首次输入延迟(FID)。

这也使开发者能够更好地衡量百分位数和性能提升,即使是那些始终响应非常快的页面,也能纳入数据,无需注册事件处理器。

1.4. 暴露的事件

事件时序 API 只为特定事件暴露时序信息。

给定一个 event,要确定它是否应被纳入事件计时的考量范围,运行以下 步骤:
  1. 如果 event 的 isTrusted 属性值设置为 false,则返回 false。

  2. 如果 event 的 type 为以下任一值: auxclick, click, contextmenu, dblclick, mousedown, mouseenter, mouseleave, mouseout, mouseover, mouseup, pointerover, pointerenter, pointerdown, pointerup, pointercancel, pointerout, pointerleave, gotpointercapture, lostpointercapture, touchstart, touchend, touchcancel, keydown, keypress, keyup, beforeinput, input, compositionstart, compositionupdate, compositionend, dragstart, dragend, dragenter, dragleave, dragover, drop, 则返回 true。

  3. 返回 false。

注: mousemove, pointermove, pointerrawupdate, touchmove, wheel, 和 drag 被排除在外,因为这些是“连续”事件。 当前 API 尚未提供足够的指导,说明如何对这些事件进行计数和聚合,以获得 基于条目的有意义的性能指标。 因此,不会暴露这些事件类型。

1.5. 事件的测量时机

本节为非规范性内容。 它以较高层次说明了§ 3 处理模型部分所暴露的信息。

事件时序信息只在特定事件发生时被暴露,并且仅当用户输入与后续绘制操作之间的时间差超过某个持续时间阈值时才会暴露。

事件时序 API 会暴露一个 duration 值,表示物理用户输入发生的时间 (通过 Event 的 timeStamp 估算) 到其 Event 的 相关全局对象的 关联文档被更新的下一次渲染之间的时间。 此值以 8 毫秒粒度提供。

默认情况下,事件时序 API 会在 duration 大于等于 104 毫秒时进行缓冲和暴露, 但开发者可以通过 PerformanceObserver 设置不同的阈值来观察未来条目。 注意,这不会改变已缓冲的条目,因此 buffered 标志仅用于接收持续时间大于等于默认阈值的历史条目。

一个 Event 的 延迟是浏览器即将运行事件处理器的时间与 Event 的 timeStamp 之间的差值。 前者作为 PerformanceEventTiming 的 processingStart 暴露, 后者作为 PerformanceEventTiming 的 startTime 暴露。 因此,事件的延迟可按如下方式计算:processingStart - startTime。

需注意,事件时序 API 会为事件创建条目,无论是否有事件监听器。 特别是,第一个点击或第一个按键可能并不是用户实际打算与页面功能交互; 许多用户在阅读时会选择文本,或点击空白区域以控制焦点。 这是为了捕获页面监听器注册过晚的问题,也为了捕获那些虽然没有监听器但仍有意义的输入(如悬停效果)的性能。 开发者可以通过忽略那些 processingEnd - processingStart 基本为零的条目来忽略这些情况, 因为 processingEnd 是事件分发算法结束的时间。

1.6. 使用示例

const observer = new PerformanceObserver(function(list, obs) {
    for (let entry of list.getEntries()) {
        // 输入延迟
        const inputDelay = entry.processingStart - entry.startTime;
        // 处理时长
        const processingDuration = entry.processingEnd - entry.processingStart;
        // 呈现延迟(近似)
        const presentationDelay = Math.max(0, entry.startTime + entry.duration - entry.processingEnd);

        // 获取一些关于此事件目标的信息,如id。
        const targetId = entry.target ? entry.target.id : 'unknown-target';

        console.log(entry.entryType, entry.name, entry.duration, { inputDelay, processingDuration, presentationDelay });
    }
});
observer.observe({ type: 'first-input', buffered: true });
observer.observe({ type: 'event', buffered: true, durationThreshold: 40 });

如下示例计算一个字典,将interactionId 映射到其事件的最大持续时间。 该字典随后可聚合并上报给分析系统。

let maxDurations = {};
new PerformanceObserver(list => {
    for (let entry of list.getEntries()) {
        if (entry.interactionId > 0) {
            let id = entry.interactionId;
            if (!maxDurations[id]) {
                maxDurations[id] = entry.duration;
            } else {
                maxDurations[id] = Math.max(maxDurations[id], entry.duration);
            }
        }
    }
}).observe({ type: 'event', buffered: true, durationThreshold: 16 });

下列为使用此API可实现的示例场景:

2. 事件时序

事件时序增加了如下接口:

2.1. PerformanceEventTiming 接口

[Exposed=Window]
interface PerformanceEventTiming : PerformanceEntry {
    readonly attribute DOMHighResTimeStamp processingStart;
    readonly attribute DOMHighResTimeStamp processingEnd;
    readonly attribute boolean cancelable;
    readonly attribute Node? target;
    readonly attribute DOMString targetSelector;
    readonly attribute unsigned long long interactionId;
    [Default] object toJSON();
};

PerformanceEventTiming 对象报告一个关联的Event的时序信息。

每个PerformanceEventTiming 对象都具有关联概念,初始值均设为null:

target 属性的getter需执行以下步骤:

  1. 如果 this 的 eventTarget 不满足 为绘制时序暴露(参数为null),则返回null。

  2. 返回 this 的 eventTarget。

注意: 实现事件时序API的用户代理需在supportedEntryTypes中包含"first-input"和"event", 适用于Window环境。 这样开发者可以检测事件时序的支持情况。

本节其余内容为非规范性说明。 PerformanceEventTiming 的属性值在§ 3 处理模型中设定, 此处为各属性设定方式的概要说明。

PerformanceEventTiming 扩展了如下PerformanceEntry 接口属性:

name
name 属性的getter返回关联事件的type。
entryType
entryType 属性的getter返回"event"(用于长事件)或"first-input"(用于第一次用户交互)。
startTime
startTime 属性的getter返回关联事件的timeStamp。
duration
duration 属性的getter返回 更新渲染步骤在关联事件的Document 被分发后完成的时间,与startTime的差值,结果四舍五入到最近的8ms。

PerformanceEventTiming 还具备以下附加属性:

processingStart
processingStart 属性的getter返回在事件分发算法开始时捕获的时间戳。即事件处理器即将执行时。
processingEnd
processingEnd 属性的getter返回在事件分发算法结束时捕获的时间戳。即事件处理器执行完毕时。若无处理器,则等于processingStart。
cancelable
cancelable 属性的getter返回关联事件的cancelable属性值。
target
target 属性的getter返回关联事件的最后一个target,且该Node 未断开连接且不在shadow DOM中。
targetSelector
targetSelector 属性的 getter 会返回一个字符串,用于标识关联事件的最后一个 target。
interactionId
interactionId 属性的getter返回唯一标识触发关联事件的用户交互的编号。 当关联事件的type 不属于以下情况时,该属性为0:

2.2. EventCounts 接口

[Exposed=Window]
interface EventCounts {
    readonly maplike<DOMString, unsigned long long>;
};

EventCounts 对象是一个映射,其键为事件 类型, 值为已分发该 类型 事件的数量。 只有其 类型 被PerformanceEventTiming 条目支持的事件(见§ 1.4 公开事件),才会通过此映射计数。

2.3. 对Performance 接口的扩展

[Exposed=Window]
partial interface Performance {
    [SameObject] readonly attribute EventCounts eventCounts;
    readonly attribute unsigned long long interactionCount;
};

eventCounts 属性的 getter 返回this的相关全局对象的eventCounts。

interactionCount 属性的 getter 返回this的相关全局对象的interactionCount。

3. 处理模型

3.1. 对 DOM 规范的修改

一旦 [DOM] 被修改,本节将被移除。

我们对 事件分发算法做如下修改。

在第1步之后,添加以下步骤:

  1. 令 interactionId 为 计算 interactionId(传入 event)的结果。

  2. 令 timingEntry 为 初始化事件时序(传入 event、当前高精度时间 和 interactionId)的结果。

在该算法返回步骤之前,添加如下步骤:

  1. 完成事件时序,传入 timingEntry、event、target 以及 当前高精度时间 作为输入。

注意: 如果某个用户代理跳过了 事件分发算法,它仍然可以选择为该 Event 包含一个条目。 在这种情况下,它会估算 processingStart 的值,并将 processingEnd 设置为同一值。

3.2. 对 HTML 规范的修改

一旦 [HTML] 被修改,本节将被移除。

每个 Window 具有以下相关概念:

在 更新渲染步骤(位于 事件循环处理模型中),在调用 标记绘制时序的步骤之后,添加如下步骤:
  1. 对于 docs 中每个完全激活的 Document, 调用算法 分发待处理事件时序条目,传入该 Document。

3.3. 对 Performance Timeline 规范的修改

一旦 [PERFORMANCE-TIMELINE-2] 被修改,本节将被移除。

PerformanceObserverInit 字典进行了增强:

partial dictionary PerformanceObserverInit {
    DOMHighResTimeStamp durationThreshold;
};

3.4. 应添加 PerformanceEventTiming

注意: 下述算法用于 [PERFORMANCE-TIMELINE-2] 规范中,以决定何时将 PerformanceEventTiming 条目加入 PerformanceObserver 的缓冲区或性能时序,如 注册表所述。

给定一个 PerformanceEventTiming entry 和一个 PerformanceObserverInit options,判断是否应添加 PerformanceEventTiming,传入 entry 和可选 options,按以下步骤执行:
  1. 如果 entry 的 entryType 属性值等于 "first-input",返回 true。

  2. 断言 entry 的 entryType 属性值等于 "event"。

  3. 令 minDuration 计算如下:

    1. 若 options 不存在,或 options 的 durationThreshold 不存在,则 minDuration 设为 104。

    2. 否则,minDuration 取 16 与 options 的 durationThreshold 值之间的最大值。

  4. 若 entry 的 duration 属性值大于等于 minDuration,返回 true。

  5. 否则,返回 false。

3.5. 增加交互计数

当请求增加交互计数,传入 Window window 对象时,执行以下步骤:
  1. 将 window 的 用户交互值 增加一个由用户代理选择的小数。

  2. 令 interactionCount 为 window 的 interactionCount。

  3. 将 interactionCount 设为 interactionCount + 1。

注意:用户交互值每次增加一个由用户代理选择的小数,而不是1,是为了防止开发者将其视为网页应用中发生的用户交互次数计数器。 这样,用户代理可以选择在pointerdown时主动分配一个用户交互值,然后在(如pointercancel)后丢弃,而不是延迟计算该值。

用户代理可以选择每次增加一个小的随机整数,或者选择一个常数。 用户代理不得为所有 Windows 使用共享的全局 用户交互值,因为这可能导致跨域泄露。

3.6. 计算 interactionId

当要求计算 interactionId,并以 event 为输入时,运行以下 步骤:
  1. 如果 event 的 isTrusted 属性值为 false,则返回 0。

  2. 令 type 为 event 的 type 属性值。

  3. 如果 type 不是 keyup, compositionstart, input, pointercancel, pointerup, click, 或 contextmenu 中的任一个, 则返回 0。

    注: keydown 和 pointerdown 在完成事件计时时被标记为待处理状态,之后在 为后续事件计算 interactionId(例如 keyup 和 pointerup)时进行更新。

  4. 令 window 为 event 的相关全局对象。

  5. 令 pendingKeyDowns 为 window 的待处理的按键按下事件。

  6. 令 pointerMap 为 window 的指针 交互值映射。

  7. 令 pendingPointerDowns 为 window 的待处理的指针按下事件。

  8. 如果 type 为 keyup:

    1. 如果 event 的 isComposing 属性值为 true,则返回 0。

    2. 令 code 为 event 的 keyCode 属性值。

    3. 如果 pendingKeyDowns[code] 不存在,则返回 0。

    4. 令 entry 为 pendingKeyDowns[code]。

    5. 对 window 增加交互计数。

    6. 令 interactionId 为 window 的用户交互 值的值。

    7. 将 entry 的 interactionId 设置为 interactionId。

    8. 将 entry 添加到 window 的待 入队条目中。

    9. 移除 pendingKeyDowns[code]。

    10. 返回 interactionId。

  9. 如果 type 为 compositionstart:

    1. 对于 pendingKeyDowns 的值中的每个 entry:

      1. 将 entry 追加到 window 的待 入队条目中。

    2. 清空 pendingKeyDowns。

    3. 返回 0。

  10. 如果 type 为 input:

    1. 如果 event 不是 InputEvent 的实例, 则返回 0。 注:此检查旨在排除那些 Event 事件, 其 type 为 input, 但并不涉及文本内容修改的事件。

    2. 如果 event 的 isComposing 属性值为 false,则返回 0。

    3. 对 window 增加交互计数。

    4. 返回 window 的用户交互值。

  11. 否则(type 为 pointercancel, pointerup, click, 或 contextmenu):

    1. 令 pointerId 为 event 的 pointerId 属性值。

    2. 如果 type 为 click:

      1. 如果 pointerMap[pointerId] 不存在,则返回 0。

      2. 令 value 为 pointerMap[pointerId]。

      3. 移除 pointerMap[pointerId]。

      4. 返回 value。

    3. 断言 type 为 pointerup, pointercancel, 或 contextmenu。

    4. 如果 pendingPointerDowns[pointerId] 不存在:

      1. 如果 type 为 contextmenu, 则返回 window 的用户 交互值。

      2. 如果 type 为 pointerup 且 window 的是否触发 contextmenu 标志为 true:

        1. 将 window 的是否触发 contextmenu 标志设置为 false。

        2. 返回 window 的用户交互值。

      3. 否则,返回 0。

    5. 令 pointerDownEntry 为 pendingPointerDowns[pointerId]。

    6. 断言 pointerDownEntry 是一个 PerformanceEventTiming 条目。

    7. 如果 type 为 pointerup 或 contextmenu:

      1. 对 window 增加交互计数。

      2. 将 pointerMap[pointerId] 设置为 window 的用户交互值。

      3. 将 pointerDownEntry 的 interactionId 设置为 pointerMap[pointerId]。

    8. 将 pointerDownEntry 追加到 window 的待 入队条目中。

    9. 移除 pendingPointerDowns[pointerId]。

    10. 如果 type 为 contextmenu, 则将 window 的是否触发 contextmenu 设置为 true。

    11. 如果 type 为 pointercancel, 则返回 0。

    12. 返回 pointerMap[pointerId]。

注: 该算法尝试将事件分配给 对应的交互 ID。 对于键盘事件,keydown 会触发一个新的交互 ID,而 keyup 必须与先前的 keydown 使用相同的 ID。 对于指针事件,当我们收到 pointerdown 时, 必须等到 pointercancel 或 pointerup 发生后才能确定其 interactionId。 我们尝试将 click 与先前 pointerdown 的交互 ID 相匹配。 如果 pointercancel 或 pointerup 发生,我们就可以为 interactionId 对应的已存储 pointerdown 条目设置该值。 如果发生的是 pointercancel, 这意味着我们不希望为 pointerdown 分配新的交互 ID。 如果发生的是 pointerup, 我们会计算一个新的交互 ID,并将其同时设置在 pointerdown 和 pointerup 上(以及随后可能发生的 click 事件)。

3.7. 初始化事件时序

当请求初始化事件时序,传入 event、processingStart 和 interactionId 时,执行以下步骤:
  1. 如果用于判断 event 是否纳入事件时序考虑的算法返回 false,则返回 null。

  2. 令 timingEntry 为在 event 的 相关领域中新建的 PerformanceEventTiming 对象。

  3. 将 timingEntry 的 name 设为 event 的 type 属性值。

  4. 将 timingEntry 的 entryType 设为 "event"。

  5. 将 timingEntry 的 startTime 设为 event 的 timeStamp 属性值。

  6. 将 timingEntry 的 processingStart 设为 processingStart。

  7. 将 timingEntry 的 cancelable 设为 event 的 cancelable 属性值。

  8. 将 timingEntry 的 interactionId 设为 interactionId。

  9. 返回 timingEntry。

3.8. 完成事件时序

当被要求完成事件计时,并以 timingEntry、event、target 和 processingEnd 作为输入时,执行以下步骤:
  1. 如果 timingEntry 为 null,则返回。

  2. 令 relevantGlobal 为 target 的 相关全局对象。

  3. 如果 relevantGlobal 未 实现 Window, 则返回。

  4. 将 timingEntry 的 processingEnd 设为 processingEnd。

  5. 断言 target 实现了 Node。

    注意:该断言成立是因为事件计时 API 支持的事件类型。

  6. 将 timingEntry 的关联 eventTarget 设为 target。

    注意:这会将 eventTarget 设为最后的事件目标。因此,如果发生 重定向,则会采用最接近 根节点 的最后目标。

  7. 将 timingEntry 的 targetSelector 设为对 target 执行生成 CSS 选择器算法的结果。

  8. 如果 event 的 type 属性值为 pointerdown:

    1. 令 pendingPointerDowns 为 relevantGlobal 的 待处理 pointerdown。

    2. 令 pointerId 为 event 的 pointerId。

    3. 如果 pendingPointerDowns[pointerId] 存在:

      1. 令 previousPointerDownEntry 为 pendingPointerDowns[pointerId]。

      2. 将 previousPointerDownEntry 添加到 relevantGlobal 的 待入队条目。

    4. 将 pendingPointerDowns[pointerId] 设为 timingEntry。

    5. 将 relevantGlobal 的 是否触发了 contextmenu 设为 false。

  9. 否则,如果 event 的 type 属性值为 keydown:

    1. 如果 event 的 isComposing 属性值为 true:

      1. 将 timingEntry 添加到 relevantGlobal 的 待入队条目。

      2. 返回。

    2. 令 pendingKeyDowns 为 relevantGlobal 的 待处理 keydown。

    3. 令 code 为 event 的 keyCode 属性值。

    4. 如果 pendingKeyDowns[code] 存在:

      1. 令 previousKeyDownEntry 为 pendingKeyDowns[code]。

      2. 如果 code 不等于 229:

        1. 将 relevantGlobal 的 用户交互值 增加一个由用户代理选择的小数值。

        2. 将 previousKeyDownEntry 的 interactionId 设为 relevantGlobal 的 用户交互值。

        注意:229 是特殊情况,因为它对应于 IME 键盘事件。用户代理有时会发送多个这样的事件,并且它们不对应于长按某个按键。

      3. 将 previousKeyDownEntry 添加到 relevantGlobal 的 待入队条目。

    5. 将 pendingKeyDowns[code] 设为 timingEntry。

  10. 否则:

    1. 将 timingEntry 添加到 relevantGlobal 的 待入队条目。

3.9. 分发待处理事件时序条目

当请求分发待处理事件时序条目给一个 Document doc 时,执行以下步骤:
  1. 令 window 为 doc 的 相关全局对象。

  2. 令 renderingTimestamp 为 当前高精度时间。

  3. 对于 window 的 待入队条目中的每个 timingEntry:

    1. 设置事件时序条目持续时间,传入 timingEntry、window 和 renderingTimestamp。

    2. 若 timingEntry 的 duration 属性值大于等于 16,则 入队 timingEntry。

  4. 将 window 的 待排队的条目 设为一个空列表。

  5. 对于 window 的 待处理 pointerdown中的所有值中的每个 pendingPointerDownEntry:

    1. 设置事件时序条目持续时间,传入 pendingPointerDownEntry、window 和 renderingTimestamp。

  6. 对于 window 的 待处理 keydown中的所有值中的每个 pendingKeyDownEntry:

    1. 设置事件时序条目持续时间,传入 pendingKeyDownEntry、window 和 renderingTimestamp。

当请求设置事件时序条目持续时间,给定 PerformanceEventTiming timingEntry、Window window 和 DOMHighResTimeStamp renderingTimestamp 时,执行以下步骤:
  1. 若 timingEntry 的 duration 属性值非零,则返回。

  2. 令 start 为 timingEntry 的 startTime 属性值。

  3. 将 timingEntry 的 duration 设为 DOMHighResTimeStamp 类型的 renderingTimestamp - start,粒度为 8ms 或更小。

  4. 令 name 为 timingEntry 的 name 属性值。

  5. 执行以下步骤以更新事件计数:

    1. 令 eventCounts 为 window 的 eventCounts。

    2. 断言 eventCounts 包含 name。

    3. 设定 eventCounts[name] 为 eventCounts[name] + 1。

  6. 若 window 的 已分发输入事件为 false,且 timingEntry 的 interactionId 不为 0,执行以下步骤:

    1. 令 firstInputEntry 为 timingEntry 的副本。

    2. 将 firstInputEntry 的 entryType 设为 "first-input"。

    3. 入队 firstInputEntry。

    4. 将 window 的 已分发输入事件 设为 true。

3.10. 目标选择器

若要根据 EventTarget target 生成 CSS 选择器,执行以下步骤:
  1. 如果 target 是 Node, 执行以下步骤:

    1. 令 selector 为字符串,其初始值为 target 的 nodeName。

    2. 如果 target 是 Element, 执行以下步骤:

      1. 如果 target 有 `id` 属性,则将 selector 设为 « selector、"#"、`id` 属性值 » 的拼接结果。

      2. 否则,如果 target 有 `src` 属性,则将 selector 设为 « selector、"[src="、`src` 属性值、"]" » 的拼接结果。

    3. 返回 selector。

  2. 否则,返回空字符串。

4. 安全与隐私注意事项

我们不希望为 Web 平台引入更多高精度定时器,因为这类定时器会带来安全隐患。 事件处理器的时间戳与 performance.now() 的精度相同。 由于 processingStart 和 processingEnd 可以在不使用本 API 的情况下被计算出来, 暴露这些属性不会带来新的攻击面。 因此,duration 是唯一需要额外考虑的属性。

duration 的粒度是 8 毫秒(通过四舍五入计算而得)。 因此,这些时间戳无法用于生成高精度计时器。 不过,它确实引入了开发者无法直接获取的新信息:即事件处理后像素被绘制的时间。 我们认为公开该时间戳不会有安全或隐私方面的顾虑,尤其考虑到其粒度。 为了只暴露对开发者有用的最少新信息,我们选择了 8 毫秒作为粒度。 这让即便是 120Hz 显示器也能达到较精确的计时。

选择 104ms 作为 duration 的默认阈值,只是第一个大于 100ms 的 8 的倍数。 一个事件如果经四舍五入后的持续时间大于等于 104ms,则其未四舍五入前的持续时间必然大于等于 100ms。 这类事件未能在 100ms 内被处理,极可能对用户体验造成负面影响。

选择 16ms 作为 durationThreshold 的最小允许值,是因为这能满足典型场景需求, 即确保响应足够流畅。在 120Hz 显示器下,响应若跳过超过一帧,至少会达到 16ms,因此该用户输入对应的条目会在 API 中以最小值被暴露。

一致性

文档约定

一致性要求通过描述性断言和 RFC 2119 术语的组合表达。 在本文件规范性部分中,“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL” 这些关键字应按照 RFC 2119 的说明进行解释。 但为了可读性,本规范中这些词不会全部用大写字母表示。

本规范的所有文本均为规范性内容,除非明确标注为非规范性部分、示例和注释。[RFC2119]

本规范中的示例以“例如”开头,或用 class="example" 与规范性文本区分, 如下所示:

这是一个说明性示例。

说明性注释以“注意”开头,并用 class="note" 与规范性文本区分, 如下所示:

注意,这是一个说明性注释。

一致性算法

作为算法一部分,以祈使句表达的要求(如“去除所有前导空格字符”或“返回 false 并终止这些步骤”),应按照用于引入算法的关键字(“must”、“should”、“may”等)含义解释。

以算法或具体步骤形式表达的一致性要求,可以以任何方式实现,只要最终结果是等效的。 特别是,本规范中定义的算法旨在易于理解,而不是高性能。 鼓励实现者进行优化。

索引

本规范定义的术语

引用定义的术语

参考文献

规范性引用

[DOM]
Anne van Kesteren. DOM 标准. 现行标准。 URL: https://dom.spec.whatwg.org/
[HR-TIME-2]
Ilya Grigorik. 高精度时间第 2 级. 21 日, 2019 年 11 月。REC。URL: https://www.w3.org/TR/hr-time-2/
[HTML]
Anne van Kesteren; 等。HTML 标准. 现行标准。URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. 基础设施 标准. 现行标准。URL: https://infra.spec.whatwg.org/
[PAINT-TIMING]
Ian Clelland; Noam Rosenthal. 绘制计时. 2 日, 2026 年 10 月。WD。URL: https://www.w3.org/TR/paint-timing/
[PERFORMANCE-TIMELINE-2]
Nicolas Pena Moreno. 性能 时间线. 2025 年 5 月 21 日。CRD。URL: https://www.w3.org/TR/performance-timeline/
[POINTEREVENTS4]
Patrick Lauke; Robert Flack. 指针 事件. 2026 年 10 月 8 日。WD。URL: https://www.w3.org/TR/pointerevents4/
[RFC2119]
S. Bradner. RFC 中用于 表示要求级别的关键词. 1997 年 3 月。最佳当前实践。URL: https://datatracker.ietf.org/doc/html/rfc2119
[TOUCH-EVENTS]
Doug Schepers; 等。触摸事件. 10 日, 2013 年 10 月。REC。URL: https://www.w3.org/TR/touch-events/
[UIEVENTS]
Xiaoqian Wu. UI 事件. 2026 年 2 月 21 日。WD。 URL: https://www.w3.org/TR/uievents/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL 标准. 现行 标准。URL: https://webidl.spec.whatwg.org/

IDL 索引

[Exposed=Window]
interface PerformanceEventTiming : PerformanceEntry {
    readonly attribute DOMHighResTimeStamp processingStart;
    readonly attribute DOMHighResTimeStamp processingEnd;
    readonly attribute boolean cancelable;
    readonly attribute Node? target;
    readonly attribute DOMString targetSelector;
    readonly attribute unsigned long long interactionId;
    [Default] object toJSON();
};

[Exposed=Window]
interface EventCounts {
    readonly maplike<DOMString, unsigned long long>;
};

[Exposed=Window]
partial interface Performance {
    [SameObject] readonly attribute EventCounts eventCounts;
    readonly attribute unsigned long long interactionCount;
};

partial dictionary PerformanceObserverInit {
    DOMHighResTimeStamp durationThreshold;
};

MDN

EventCounts

Firefox89+SafariNoneChrome85+
Opera?Edge85+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

Performance/eventCounts

Firefox89+SafariNoneChrome85+
Opera?Edge85+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
Node.jsNone
MDN

PerformanceEventTiming/cancelable

Firefox89+SafariNoneChrome76+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
⚠MDN

PerformanceEventTiming/interactionId

In only one current engine.

FirefoxNoneSafariNoneChrome96+
Opera?Edge96+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

PerformanceEventTiming/processingEnd

Firefox89+SafariNoneChrome76+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

PerformanceEventTiming/processingStart

Firefox89+SafariNoneChrome76+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

PerformanceEventTiming/target

Firefox89+SafariNoneChrome85+
Opera?Edge85+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

PerformanceEventTiming/toJSON

Firefox89+SafariNoneChrome76+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

PerformanceEventTiming

Firefox89+SafariNoneChrome76+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?