用户计时

W3C 候选推荐草案

关于本文档的更多细节
此版本:
https://www.w3.org/TR/2026/CRD-user-timing-20260311/
最新发布版本:
https://www.w3.org/TR/user-timing/
最新编辑草案:
https://w3c.github.io/user-timing/
历史:
https://www.w3.org/standards/history/user-timing
提交历史
测试套件:
https://wpt.fyi/results/user-timing
实现报告:
https://wpt.fyi/results/user-timing
编辑:
Nicolás Peña Moreno (Google)
前编辑:
Ilya Grigorik (Google)
Jatinder Mann (Microsoft Corp.) (至 2014 年 2 月)
Zhiheng Wang (Google Inc.) (至 2013 年 7 月)
Anderson Quach (Microsoft Corp.) (至 2011 年 3 月)
反馈:
GitHub w3c/user-timing (拉取请求, 新议题, 开放议题)
public-web-perf@w3.org, 主题行为 [UserTiming](归档)
实现
我能使用 User Timing 吗?

摘要

本规范定义了一个接口,通过允许 Web 开发者访问高精度时间戳,帮助他们测量其应用程序的性能。

本文档状态

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

本 User Timing 规范旨在取代 [USER-TIMING-2],并包括:

本文档由 Web Performance Working Group 作为候选推荐草案发布,使用的是 推荐标准轨道。

作为候选推荐发布并不 意味着 W3C 及其成员的认可。候选 推荐草案整合了工作组 打算纳入后续候选推荐快照的、相对于前一候选推荐所作的变更。

这是一份草案文档,随时可能被更新、替换或废弃并由其他 文档取代。不应将本文档作为进行中的工作以外的内容来引用。

本文档由一个按照 W3C 专利 政策运作的小组制作。 W3C 维护一份 任何专利 披露的公开列表, 这些披露与该小组的交付成果相关;该页面还包括 披露专利的说明。实际 知悉某项专利,且认为该专利包含 必要权利要求的个人, 必须按照 W3C 专利政策第 6 节披露相关信息。

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

1. 简介

本节是非规范性的。

Web 开发者需要能够评估和理解其 应用程序的性能特征。虽然 JavaScript [ECMA-262] 提供了一种测量 应用程序延迟的机制(从 Date.now() 方法取得当前时间戳),但该 时间戳的精度会因用户代理而异。

本文档定义了 PerformanceMark 和 PerformanceMeasure 接口,以及对 Performance 接口的扩展,它们公开高 精度、单调递增的时间戳,使开发者能够更好地测量其应用程序的性能 特征。

下面的脚本展示了开发者如何使用本文档定义的接口来获得 与开发者脚本相关的计时数据。

async function run() {
  performance.mark("startTask1");
  await doTask1(); // 一些开发者代码
  performance.mark("endTask1");

  performance.mark("startTask2");
  await doTask2(); // 一些开发者代码
  performance.mark("endTask2");

  // 将它们记录出来
  const entries = performance.getEntriesByType("mark");
  for (const entry of entries) {
    console.table(entry.toJSON());
  }
}
run();
注

[PERFORMANCE-TIMELINE-2] 定义了两种 可用于检索已记录指标的机制:getEntries() 和 getEntriesByType() 方法,以及 PerformanceObserver 接口。前者最适合 你想要在某个单一时间点按名称检索特定指标的场景, 后者则针对你可能希望在新指标变得 可用时接收通知的场景进行了优化。

再举一个例子,假设有一个元素,当它被 点击时,会获取一些新内容并指示该内容已获取完成。 我们想要报告从用户点击到获取 完成之间的时间。我们不能标记点击处理程序执行的时间,因为那样 会漏掉处理事件的延迟,所以我们改用事件的 硬件时间戳。我们还想知道组件名称,以便获得 更详细的分析数据。

element.addEventListener("click", e => {
  const component = getComponent(element);
  fetch(component.url).then(() => {
    element.textContent = "Updated";
    const updateMark = performance.mark("update_component", {
      detail: {component: component.name},
    });
    performance.measure("click_to_update_component", {
      detail: {component: component.name},
      start: e.timeStamp,
      end: updateMark.startTime,
    });
  });
});

2. 一致性

除标记为非规范性的章节外,本规范中的所有编写指南、图示、示例和注释都是 非规范性的。本规范中的其他所有内容都是规范性的。

本文档中的关键词 MAY 和 MUST 在且仅在它们如这里所示以全大写形式出现时, 应按 BCP 14 [RFC2119] [RFC8174] 中所描述的方式解释。

某些一致性要求以对属性、 方法或对象的要求来表述。这些要求应解释为 对用户代理的要求。

本规范中的 IDL 片段 MUST 按 Web IDL 规范中关于一致性 IDL 片段的描述来解释。 [WEBIDL]

3. User Timing

3.1 对 Performance 接口的扩展

Performance 接口和 DOMHighResTimeStamp 定义于 [HR-TIME-2]。 PerformanceEntry 接口定义于 [PERFORMANCE-TIMELINE-2]。

WebIDLdictionary PerformanceMarkOptions {
    any detail;
    DOMHighResTimeStamp startTime;
};

dictionary PerformanceMeasureOptions {
    any detail;
    (DOMString or DOMHighResTimeStamp) start;
    DOMHighResTimeStamp duration;
    (DOMString or DOMHighResTimeStamp) end;
};

partial interface Performance {
    PerformanceMark mark(DOMString markName, optional PerformanceMarkOptions markOptions = {});
    undefined clearMarks(optional DOMString markName);
    PerformanceMeasure measure(DOMString measureName, optional (DOMString or PerformanceMeasureOptions) startOrMeasureOptions = {}, optional DOMString endMark);
    undefined clearMeasures(optional DOMString measureName);
};

3.1.1 mark() 方法

存储带有关联名称的时间戳(一个“mark”)。它 MUST 运行这些 步骤:

  1. 运行 PerformanceMark 构造器,并令 entry 为新创建的对象。
  2. 排队 entry。
  3. 将 entry 添加到 performance entry buffer。
  4. 返回 entry。
3.1.1.1 PerformanceMarkOptions 字典
detail
要包含在标记中的元数据。
startTime
要用作标记时间的时间戳。

3.1.2 clearMarks() 方法

移除带有关联名称的已存储时间戳。它 MUST 运行这些 步骤:

  1. 如果省略了 markName,则从 PerformanceMark 对象中移除所有位于 performance entry buffer 中的对象。
  2. 否则,移除 PerformanceMark 对象中列在 performance entry buffer 中且其 name 是 markName 的所有对象。
  3. 返回 undefined。

3.1.3 measure() 方法

存储两个标记之间的 DOMHighResTimeStamp 持续时间,并附带关联名称(一个“measure”)。它 MUST 运行这些步骤:

  1. 如果 startOrMeasureOptions 是一个 PerformanceMeasureOptions 对象,并且 start、end、duration 和 detail 中至少有一个 存在,则运行以下 检查:
    1. 如果给定了 endMark,则抛出一个 TypeError。
    2. 如果 startOrMeasureOptions 的 start 和 end 成员 都被省略,则抛出一个 TypeError。
    3. 如果 startOrMeasureOptions 的 start、duration 和 end 成员全部 存在,则抛出一个 TypeError。
  2. 按如下方式计算 end time:
    1. 如果给定了 endMark,则令 end time 为传入 endMark 运行 convert a mark to a timestamp 算法所返回的值。
    2. 否则,如果 startOrMeasureOptions 是一个 PerformanceMeasureOptions 对象,并且如果其 end 成员 存在, 则令 end time 为传入 startOrMeasureOptions 的 end 运行 convert a mark to a timestamp 算法所返回的值。
    3. 否则,如果 startOrMeasureOptions 是一个 PerformanceMeasureOptions 对象,并且如果其 start 和 duration 成员都 存在:
      1. 令 start 为传入 start 运行 convert a mark to a timestamp 算法所返回的值。
      2. 令 duration 为传入 duration 运行 convert a mark to a timestamp 算法所返回的值。
      3. 令 end time 为 start 加上 duration。
    4. 否则,令 end time 为 Performance 对象的 now() 方法将会返回的值。
  3. 按如下方式计算 start time:
    1. 如果 startOrMeasureOptions 是一个 PerformanceMeasureOptions 对象,并且如果其 start 成员 存在, 则令 start time 为传入 startOrMeasureOptions 的 start 运行 convert a mark to a timestamp 算法所返回的值。
    2. 否则,如果 startOrMeasureOptions 是一个 PerformanceMeasureOptions 对象,并且如果其 duration 和 end 成员都 存在:
      1. 令 duration 为传入 duration 运行 convert a mark to a timestamp 算法所返回的值。
      2. 令 end 为传入 end 运行 convert a mark to a timestamp 算法所返回的值。
      3. 令 start time 为 end 减去 duration。
    3. 否则,如果 startOrMeasureOptions 是一个 DOMString,则令 start time 为传入 startOrMeasureOptions 运行 convert a mark to a timestamp 算法所返回的值。
    4. 否则,令 start time 为 0。
  4. 使用 this 的 相关 realm 创建一个新的 PerformanceMeasure 对象 (entry)。
  5. 将 entry 的 name 属性设置为 measureName。
  6. 将 entry 的 entryType 属性设置为 DOMString "measure"。
  7. 将 entry 的 startTime 属性设置为 start time。
  8. 将 entry 的 duration 属性设置为从 start time 到 end time 的持续时间。所得持续时间值 MAY 为负数。
  9. 按如下方式设置 entry 的 detail 属性:
    1. 如果 startOrMeasureOptions 是一个 PerformanceMeasureOptions 对象,并且 startOrMeasureOptions 的 detail 成员 存在:
      1. 令 record 为对 startOrMeasureOptions 的 detail 调用 StructuredSerialize 算法的结果。
      2. 将 entry 的 detail 设置为 以 record 和 当前 realm 调用 StructuredDeserialize 算法的结果。
    2. 否则,将其设置为 null。
  10. 排队 entry。
  11. 将 entry 添加到 performance entry buffer。
  12. 返回 entry。
3.1.3.1 PerformanceMeasureOptions 字典
detail
要包含在度量中的元数据。
start
要用作开始时间的时间戳,或要用作开始标记的字符串。
duration
开始时间与结束时间之间的持续时间。
end
要用作结束时间的时间戳,或要用作结束标记的字符串。

3.1.4 clearMeasures() 方法

移除带有关联名称的已存储时间戳。它 MUST 运行这些步骤:

  1. 如果省略了 measureName,则移除 PerformanceMeasure 对象中位于 performance entry buffer 中的所有对象。
  2. 否则,移除 PerformanceMeasure 对象中列在 performance entry buffer 中且其 name 是 measureName 的所有对象。
  3. 返回 undefined。

3.2 PerformanceMark 接口

PerformanceMark 接口还会将通过 Performance 接口的 mark() 方法创建的标记暴露给 Performance Timeline。

WebIDL[Exposed=(Window,Worker)]
interface PerformanceMark : PerformanceEntry {
  constructor(DOMString markName, optional PerformanceMarkOptions markOptions = {});
  readonly attribute any detail;
};

PerformanceMark 接口扩展了 PerformanceEntry 接口的以下属性:

name 属性必须返回该标记的名称。

entryType 属性必须返回 DOMString "mark"。

startTime 属性必须返回一个 DOMHighResTimeStamp, 其值为该标记的时间值。

duration 属性必须返回值为 0 的 DOMHighResTimeStamp 。

PerformanceMark 接口包含以下 附加属性:

detail 属性必须返回其被设置为的值(它是从 PerformanceMarkOptions 字典复制而来的)。

3.2.1 PerformanceMark 构造器

PerformanceMark 构造器必须运行 以下步骤:

  1. 如果 当前 全局对象是一个 Window 对象,并且 markName 使用了与 PerformanceTiming 接口中的某个只读属性相同的名称, 则抛出一个 SyntaxError。
  2. 使用当前 全局对象的 realm 创建一个新的 PerformanceMark 对象 (entry)。
  3. 将 entry 的 name 属性设置为 markName。
  4. 将 entry 的 entryType 属性设置为 DOMString "mark"。
  5. 按如下方式设置 entry 的 startTime 属性:
    1. 如果 markOptions 的 startTime 成员 存在,则:
      1. 如果 markOptions 的 startTime 为负数,则抛出一个 TypeError。
      2. 否则,将 entry 的 startTime 设置为 markOptions 的 startTime 的值。
    2. 否则,将其设置为 Performance 对象的 now() 方法将会返回的值。
  6. 将 entry 的 duration 属性设置为 0。
  7. 如果 markOptions 的 detail 为 null,则将 entry 的 detail 设置为 null。
  8. 否则:
    1. 令 record 为对 markOptions 的 detail 调用 StructuredSerialize 算法的结果。
    2. 将 entry 的 detail 设置为 以 record 和 当前 realm 调用 StructuredDeserialize 算法的结果。

3.3 PerformanceMeasure 接口

PerformanceMeasure 接口还会将 通过 Performance 接口的 measure() 方法创建的度量暴露给 Performance Timeline。

WebIDL[Exposed=(Window,Worker)]
interface PerformanceMeasure : PerformanceEntry {
  readonly attribute any detail;
};

PerformanceMeasure 接口扩展了 PerformanceEntry 接口的以下属性:

name 属性必须返回该度量的名称。

entryType 属性必须返回 DOMString "measure"。

startTime 属性必须返回一个 DOMHighResTimeStamp, 其值为该度量的开始标记。

duration 属性必须返回一个 DOMHighResTimeStamp, 其值为该度量的持续时间。

PerformanceMeasure 接口包含 以下附加属性:

detail 属性必须返回其被设置为的值(它是 从 PerformanceMeasureOptions 字典复制而来的)。

4. 处理

注

实现 User Timing API 的用户代理需要在 supportedEntryTypes 中包含 "mark" 和 "measure"。这允许开发者检测对 User Timing 的支持。

4.1 将 mark 转换为 timestamp

要将标记转换为时间戳,给定一个作为 DOMString 或 DOMHighResTimeStamp 的 mark,运行这些步骤:

  1. 如果 mark 是一个 DOMString,并且它与 PerformanceTiming 接口中的某个只读属性同名,则令 end time 为在 name 设置为 mark 的值时运行 convert a name to a timestamp 算法 所返回的值。
  2. 否则,如果 mark 是一个 DOMString,则令 end time 为 performance entry buffer 中其 name 是 mark 的 PerformanceMark 对象的最近一次出现中的 startTime 属性值。如果未找到匹配项,则抛出一个 SyntaxError。
  3. 否则,如果 mark 是一个 DOMHighResTimeStamp:
    1. 如果 mark 为负数,则抛出一个 TypeError。
    2. 否则,令 end time 为 mark。

4.2 将 name 转换为 timestamp

要将名称转换为 时间戳, 给定一个作为 PerformanceTiming 接口中的只读 属性的 name,运行这些步骤:

  1. 如果全局 对象不是 Window 对象,则抛出一个 TypeError。
  2. 如果 name 是 navigationStart,则返回 0。
  3. 令 startTime 为 PerformanceTiming 接口中 navigationStart 的值。
  4. 令 endTime 为 PerformanceTiming 接口中 name 的值。
  5. 如果 endTime 是 0,则抛出一个 InvalidAccessError。
  6. 返回从 endTime 中减去 startTime 的结果。
注

PerformanceTiming 接口定义于 [NAVIGATION-TIMING],并且 现在被视为已废弃。支持使用来自 PerformanceTiming 接口的名称,是为了保持向后兼容,但未来没有计划将此 功能扩展到 [NAVIGATION-TIMING-2] 中定义的 PerformanceNavigationTiming 接口(或其他接口)中的名称。

6. 隐私与安全

本节是非规范性的。

本规范定义的接口会暴露页面中特定 JavaScript 活动的、可能 敏感的计时信息。有关暴露 高分辨率计时信息的隐私和安全注意事项,请参见 [HR-TIME-2]。

由于 Web 平台的设计遵循这样一个不变原则:页面中包含的任何 脚本都与同一页面中包含的任何其他脚本具有相同的访问权限, 无论这些脚本的源如何, 本规范定义的接口不会对 已记录计时信息的记录或检索施加任何限制——也就是说,页面中包含的任何脚本记录的 user timing mark 或 measure,都可以被 同一页面上运行的任何其他脚本读取,而不论其源如何。

A. 致谢

感谢 James Simonsen、 Jason Weber、 Nic Jansma、 Philippe Le Hegaret、 Karen Anderson、 Steve Souders、 Sigbjorn Vik、 Todd Reifsteck 和 Tony Gentilcore 对此工作的贡献。

B. 参考文献

B.1 规范性参考文献

[HR-TIME-2]
High Resolution Time Level 2. Ilya Grigorik. W3C. 21 November 2019. W3C Recommendation. URL: https://www.w3.org/TR/hr-time-2/
[HTML]
HTML Standard. Anne van Kesteren; Domenic Denicola; Dominic Farolino; Ian Hickson; Philip Jägenstedt; Simon Pieters. WHATWG. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[infra]
Infra Standard. Anne van Kesteren; Domenic Denicola. WHATWG. Living Standard. URL: https://infra.spec.whatwg.org/
[NAVIGATION-TIMING]
Navigation Timing. Zhiheng Wang. W3C. 17 December 2012. W3C Recommendation. URL: https://www.w3.org/TR/navigation-timing/
[PERFORMANCE-TIMELINE-2]
Performance Timeline. Nicolas Pena Moreno. W3C. 21 May 2025. CRD. URL: https://www.w3.org/TR/performance-timeline/
[RFC2119]
Key words for use in RFCs to Indicate Requirement Levels. S. Bradner. IETF. March 1997. Best Current Practice. URL: https://www.rfc-editor.org/rfc/rfc2119
[RFC8174]
Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words. B. Leiba. IETF. May 2017. Best Current Practice. URL: https://www.rfc-editor.org/rfc/rfc8174
[WEBIDL]
Web IDL Standard. Edgar Chen; Timothy Gu. WHATWG. Living Standard. URL: https://webidl.spec.whatwg.org/

B.2 资料性参考文献

[ECMA-262]
ECMAScript Language Specification. Ecma International. URL: https://tc39.es/ecma262/multipage/
[NAVIGATION-TIMING-2]
Navigation Timing Level 2. Yoav Weiss; Noam Rosenthal. W3C. 25 February 2026. W3C Working Draft. URL: https://www.w3.org/TR/navigation-timing-2/
[USER-TIMING-2]
User Timing Level 2. Ilya Grigorik. W3C. 26 February 2019. W3C Recommendation. URL: https://www.w3.org/TR/user-timing-2/