高分辨率时间

W3C 工作草案

更多关于本文档的详情
此版本:
https://www.w3.org/TR/2024/WD-hr-time-3-20241107/
最新发布版本:
https://www.w3.org/TR/hr-time-3/
最新编辑草稿:
https://w3c.github.io/hr-time/
历史记录:
https://www.w3.org/standards/history/hr-time-3/
提交历史
测试集:
https://wpt.live/hr-time/
实现报告:
https://wpt.fyi/hr-time/
编辑:
Yoav Weiss (Google LLC)
前编辑:
(Google LLC) - 截至
(Google LLC) - 截至
(Microsoft 公司) - 截至
反馈:
GitHub w3c/hr-time (拉取请求, 新建议, 开放议题)
浏览器支持:
caniuse.com

摘要

本规范定义了一个 API,提供时间原点以及当前时间的亚毫秒分辨率,并且不受系统时钟偏移或调整影响。

本文档状态

本节描述了本文档在发布时的状态。当前 W3C 出版物列表以及本技术报告的最新修订版可在 W3C 技术报告索引 https://www.w3.org/TR/ 查阅。

本文档由 Web 性能工作组 作为工作草案,采用 推荐流程发布。

作为工作草案发布并不代表 W3C 及其成员的认可。

本文档为草稿版本,可能随时被更新、替换或废止。不应将本文件作为除“进行中的工作”之外的引用。

本文档由遵循 W3C 专利政策 的工作组编写。 W3C 维护着 与本工作组成果相关的专利公开名单 ,该页面还包括 专利披露说明。若个人实际知晓某项专利且认为其包含 基本权利要求 ,则必须按 《W3C专利政策》第6节规定披露信息。

本文档受 2023年11月03日 W3C 流程文件管理。

1. 引言

本节为非规范性内容。

ECMAScript语言规范 [ECMA-262] 定义了 Date 对象,用于表示自1970年1月1日UTC以来的毫秒时间值。对于大多数用途,这种时间定义已经足够,因为这些值能以毫秒精度表示任何距离1970年1月1日UTC约285,616年内的时刻。

实际上,这些时间定义会受到时钟偏移和系统时钟调整的影响。时间值不一定总是单调递增,后续的值可能会减少或保持不变。

例如,以下脚本计算的duration可能为正数、负数或零:

var mark_start = Date.now();
doTask(); // 某个任务
var duration = Date.now() - mark_start;

对于某些任务,这种时间定义可能不够用,因为它:

本规范不建议修改 Date.now() [ECMA-262] 的行为,因为它在获取当前日历时间方面非常有用,并且有悠久的使用历史。DOMHighResTimeStamp类型, Performance.now()方法,以及 Performance.timeOrigin属性能够通过 提供单调递增的亚毫秒时间值解决上述问题。

注意

提供亚毫秒分辨率并非本规范的强制要求。各实现可因隐私和安全原因,选择限制所暴露的计时器分辨率,而不提供亚毫秒计时器。当这种情况发生时,依赖亚毫秒分辨率的用例可能无法满足。

1.1 用例

本节为非规范性内容。

本规范定义了几种不同的能力:它提供了基于稳定单调时钟的时间戳,可在不同上下文间比较,并可能具备亚毫秒分辨率。

当谈论性能测量时,对稳定单调时钟的需求源于时钟偏移可能扭曲测量结果并使其无效。例如,在准确测量导航到一个文档、资源获取或脚本执行的耗时时,单调递增且亚毫秒分辨率的时钟是理想的。

在不同上下文间比较时间戳非常重要,例如在Worker与主线程之间同步工作,或在记录这些工作以创建统一事件时间线时。

最后,亚毫秒计时器的需求主要围绕以下用例:

1.2 示例

本节为非规范性内容。

开发者可能希望构建整个应用的时间线,包括来自WorkerSharedWorker的事件, 它们有不同的时间原点。为了在同一时间线显示这些事件,应用可以 利用DOMHighResTimeStampPerformance.timeOrigin属性进行时间转换。

// ---- worker.js -----------------------------
// 共享worker脚本
onconnect = function(e) {
  var port = e.ports[0];
  port.onmessage = function(e) {
    // 在worker中计时执行
    var task_start = performance.now();
    result = runSomeWorkerTask();
    var task_end = performance.now();
  }

  // 向另一个上下文发送结果和与纪元相关的时间戳
  port.postMessage({
    'task': '某个worker任务',
    'start_time': task_start + performance.timeOrigin,
    'end_time': task_end + performance.timeOrigin,
    'result': result
  });
}

// ---- application.js ------------------------
// 在文档中计时任务
var task_start = performance.now();
runSomeApplicationTask();
var task_end = performance.now();

// 开发者自定义方法上传运行时性能数据
reportEventToAnalytics({
  'task': '某个文档任务',
  'start_time': task_start,
  'duration': task_end - task_start
});

// 将worker时间戳转换为文档的时间原点
var worker = new SharedWorker('worker.js');
worker.port.onmessage = function (event) {
  var msg = event.data;

  // 将与纪元相关的时间戳转换为文档的时间原点
  msg.start_time = msg.start_time - performance.timeOrigin;
  msg.end_time = msg.end_time - performance.timeOrigin;

  reportEventToAnalytics(msg);
}

2. 时间概念

2.1 时钟

时钟用于追踪时间的流逝,并可以报告算法步骤正在执行时的 不安全当前时间。 时钟的种类有很多。Web平台上的所有时钟都试图每1毫秒时钟时间对应1毫秒现实世界时间,但它们处理无法完全准确的情况方式不同。

2.2 时刻与持续时间

每个时钟不安全当前时间会返回一个 不安全时刻粗化时间会将这些不安全时刻转换为 粗化时刻,简称 时刻。 来自不同时钟的不安全时刻时刻不可相互比较。

注意

时刻不安全时刻 代表时间点,意味着它们不能直接存储为数字。实现通常会用某个固定时间点到时刻持续时间来表示,但规范应直接处理时刻本身。

持续时间是同一个 时钟 上两个时刻之间的距离。两端不能是不安全时刻,这样无论是 持续时间 还是持续时间的差值,都可以缓解 9.1 时钟分辨率中的问题。 持续时间以毫秒、秒等单位计量。由于所有 时钟都试图以相同速率计数, 持续时间没有关联的时钟, 从一个时钟的两个时刻计算出的 持续时间可以加到另一个时钟的 时刻上,得到该时钟上的另一个 时刻

持续时间从 ab 的结果算法如下:

  1. 断言ab 由同一个时钟创建。
  2. 断言ab 都是 粗化时刻
  3. 持续时间的形式返回 ab 的时间量。 如果b早于a,则为负的 持续时间

持续时间可隐式作为DOMHighResTimeStamp使用。 要隐式将持续时间转换为时间戳,给定 持续时间 d,返回d中的毫秒数。

3. 规范作者工具

如需在单个页面内测量时间(即在单个 环境设置对象上下文内),请使用 settingsObject当前相对时间戳, 定义为 settingsObject时间原点 到该 settingsObject当前单调时间持续时间。该值可通过 持续时间 隐式转换DOMHighResTimeStamp,直接暴露给 JavaScript。

如需在单次UA执行内测量时间,且某个 环境设置对象时间原点 不适合作为比较基准时,可使用该 当前单调时间 创建 时刻。某 环境设置对象 settingsObject当前单调时间 计算步骤如下:

  1. unsafeMonotonicTime单调时钟不安全当前时间
  2. 返回以 unsafeMonotonicTimesettingsObject跨域隔离能力 调用 粗化时间的结果。

来自单调时钟时刻 不能直接在 JavaScript 或 HTTP 中表示。应暴露两个此类 时刻之间的 持续时间

如需跨多个UA执行测量时间,可使用 当前粗化挂钟时间创建 时刻, 或(如果需要在 跨域隔离上下文 中获得更高精度)使用 环境设置对象当前挂钟时间当前粗化挂钟时间 是以 挂钟不安全当前时间 调用 粗化时间的结果。

环境设置对象 settingsObject当前挂钟时间 计算步骤如下:

  1. unsafeWallTime挂钟不安全当前时间
  2. 返回以 unsafeWallTimesettingsObject跨域隔离能力 调用 粗化时间的结果。

使用 挂钟时刻时,务必设计以应对用户调整时钟(无论向前还是向后)的情况。

挂钟时刻可通过将自 Unix纪元 到该 时刻的毫秒数传递给 Date 构造器,或将自 Unix纪元 到该 时刻的纳秒数传递给 Temporal.Instant 构造器,在JavaScript中表示。

避免在计算机间发送类似表示,因为这样会暴露用户的时钟偏移,这是一个 追踪向量。 应采用类似于 单调时钟 时刻 的方式, 发送两个 时刻之间的持续时间。

3.1 示例

可以使用如下方式报告DOM事件发生的时间:

  1. eventtimeStamp 属性初始化为 this相关设置对象当前相对时间戳

错误报告的年龄可用如下方式计算:

  1. report生成时间 初始化为 settings当前单调时间

之后:

  1. data 为包含下列键值对的映射:
    age
    report生成时间context相关设置对象当前单调时间 之间的毫秒数,四舍五入为整数。
    ...

多日归因报告过期可按如下方式处理:

  1. source 为新的归因源结构体,其项目为:
    ...
    源时间
    context当前挂钟时间
    到期
    value["expiry"] 解析 持续时间字符串

若干天后:

  1. 如果 context当前挂钟时间 小于 source 的源时间 + source 的到期, 则发送报告。

4. 时间原点

Unix纪元挂钟上的 时刻, 对应1970年1月1日00:00:00 UTC。

所有可能以任何方式通信的一组 环境设置对象 都有一个Unix纪元单调时间估值, 即 单调时钟上的 时刻, 其值初始化步骤如下:

  1. wall time挂钟不安全当前时间
  2. monotonic time单调时钟不安全当前时间
  3. epoch timemonotonic time - (wall time - Unix纪元)
  4. Unix纪元单调时间估值 初始化为以 epoch time 调用 粗化时间的结果。
问题 1
上述“可能通信的设置对象集合”需要更好地规范。它类似于familiar with,但包括 Worker

性能测量报告的是从相关 环境设置对象 初始化早期的某个 时刻持续时间。 该时刻 存储于该设置对象的时间原点中。

获取时间原点时间戳, 给定一个 全局对象 global, 执行以下步骤,返回一个 持续时间

  1. timeOriginglobal相关设置对象时间原点

    注意

    Window上下文中, 此值表示导航开始的时间。 在WorkerServiceWorker内容中, 此值表示worker运行的时间。 [service-workers]

  2. 返回 持续时间, 从 Unix纪元单调时间估值timeOrigin
注意

获取时间原点时间戳返回的值约等于 globalUnix纪元之后发生的 时间原点的时间。 它可能与在时间原点执行的 Date.now()返回的值不同, 因为前者是基于 单调时钟记录的, 不受系统和用户时钟调整、时钟偏移等影响。

粗化时间算法, 给定某个时钟上的 不安全时刻 timestamp, 以及可选布尔值crossOriginIsolatedCapability(默认false),执行如下步骤:
  1. time resolution 为100微秒,或更高的实现自定义值。
  2. 如果 crossOriginIsolatedCapability 为 true,则将 time resolution 设为5微秒,或更高的实现自定义值。
  3. 实现自定义方式,对timestamp进行粗化和可能的抖动,使其分辨率不超过time resolution
  4. timestamp作为时刻返回。
相对高分辨率时间, 给定来自单调时钟不安全时刻 time全局对象 global, 返回以下步骤得到的持续时间
  1. coarse time 为以 timeglobal相关设置对象跨域隔离能力 调用 粗化时间的结果。
  2. 返回coarse timeglobal相对高分辨率粗化时间
相对高分辨率粗化时间 给定来自 单调时钟时刻 coarseTime全局对象 global, 是从global相关设置对象时间原点coarseTime持续时间

当前高分辨率时间 给定 全局对象 current global, 必须返回以 相对高分辨率时间 给定 不安全共享当前时间current global 的结果。

粗化共享当前时间 给定可选布尔值crossOriginIsolatedCapability(默认false),必须返回以 不安全共享当前时间crossOriginIsolatedCapability 调用粗化时间的结果。

不安全共享当前时间 必须返回 单调时钟不安全当前时间

5. DOMHighResTimeStamp 类型定义

DOMHighResTimeStamp 类型用于存储以毫秒为单位的 持续时间。根据其上下文,它可能表示某个 时刻, 即某个基准 时刻(如 时间原点Unix纪元)之后的该 持续时间

WebIDLtypedef double DOMHighResTimeStamp;

DOMHighResTimeStamp 应当 以足够精确的毫秒表示时间, 以便测量,同时防止计时攻击——更多内容见 9.1 时钟分辨率

注意

DOMHighResTimeStampdouble, 因此只能以有限分辨率表示与纪元相关的时间——即从 Unix纪元 到某个 时刻 的毫秒数。对于2023年的 时刻而言, 该分辨率约为0.2微秒。

6. EpochTimeStamp 类型定义

WebIDLtypedef unsigned long long EpochTimeStamp;
注意:平台遗留特性

EpochTimeStamp 表示从 Unix纪元挂钟上的指定 时刻 的整数毫秒数(不包括闰秒)。使用该类型的规范会定义如何解释毫秒数。

7. Performance 接口

WebIDL[Exposed=(Window,Worker)]
interface Performance : EventTarget {
    DOMHighResTimeStamp now();
    readonly attribute DOMHighResTimeStamp timeOrigin;
    [Default] object toJSON();
};

7.1 now() 方法

now() 方法 必须返回 当前高分辨率时间(以 this相关全局对象为参数)的毫秒数(一个 持续时间)。

当对具有相同 时间原点Performance对象调用 now()方法时, 返回的时间值必须使用同一个 单调时钟。 从now()方法按时间顺序记录的任意两次返回值之间的差值, 在时间原点相同的情况下必须永远不为负数。

7.2 timeOrigin 属性

timeOrigin 属性 必须返回 获取时间原点时间戳(以 相关全局对象 为参数)返回的 持续时间的毫秒数。

获取 Performance.timeOrigin时, 返回的时间值必须使用由 单调时钟共享的 时间原点, 其参考点为 [ECMA-262] time定义——见 9. 安全注意事项

7.3 toJSON() 方法

当调用 toJSON()时, 执行 [WEBIDL] 的 默认 toJSON 步骤

8. WindowOrWorkerGlobalScope 混入的扩展

8.1 performance 属性

接口混入 performance 属性允许从 WindowOrWorkerGlobalScope 访问与性能相关的属性和方法, 这些属性和方法属于 全局对象

WebIDLpartial interface mixin WindowOrWorkerGlobalScope {
  [Replaceable] readonly attribute Performance performance;
};

9. 安全注意事项

9.1 时钟分辨率

访问精确的时间信息,无论是用于测量还是调度,都是许多应用的常见需求。例如,页面上的动画、声音和其他活动的协调需要高分辨率时间,以提供良好的用户体验。同样,测量功能可以让开发者跟踪关键代码组件的性能,检测回退等。

但对同样精确的时间信息的访问,有时也可能被攻击者用于恶意目的,以猜测和推断他们无法直接看到或访问的数据。例如,缓存攻击、统计指纹识别和微架构攻击是隐私和安全方面的担忧,恶意网站可能利用各种浏览器或应用发起操作的高分辨率计时数据来区分用户子集、识别特定用户或暴露同进程的无关用户数据——详情请参阅 [CACHE-ATTACKS] 和 [SPECTRE]。

本规范定义了一个提供亚毫秒时间分辨率的 API,比此前 EpochTimeStamp 暴露的毫秒分辨率更精确。然而,即使没有这个新 API,攻击者也可能通过多次执行和统计分析获得高分辨率估算值。

为确保新 API 不会显著提升此类攻击的准确性或速度,DOMHighResTimeStamp 类型的最小分辨率应足够不精确,以阻止这些攻击。

必要时,用户代理应在 粗化时间 的处理模型中为 time resolution 设置更高的分辨率值,以应对因架构或软件限制等原因而产生的隐私和安全问题或其他考虑因素。

为减轻此类攻击,用户代理可采用任何他们认为必要的技术。这些技术的部署可能会根据浏览器架构、用户设备、内容及其恶意读取跨域数据的能力或其他实际情况而有所不同。

这些技术可能包括:

完全缓解此类计时侧信道攻击在实际中几乎不可能:要么所有操作都必须在与任何机密信息值无关的固定时间内执行,要么应用必须与任何与时间相关的原语(时钟、定时器、计数器等)隔离。由于涉及的复杂性及其对浏览器和应用开发者的影响,以及对应用性能和响应性的负面影响,这两种方式都不实际。

注意
时钟分辨率是一个尚未解决且不断演进的研究领域,目前没有适用于所有浏览器的行业共识或权威推荐。请参阅 Issue 79 跟进讨论。

9.2 时钟漂移

本规范还定义了一个 API,可提供时间原点零时的亚毫秒分辨率,这需要并向应用暴露了 单调时钟,且必须在所有浏览器上下文中共享。单调时钟无需与物理时间绑定,但建议参考 [ECMA-262] 的 time 定义,以避免暴露关于用户的新指纹熵——例如,这种时间应用已能轻易获得,而暴露新的逻辑时钟则会提供新的信息。

但即使采用上述机制,单调时钟仍可能提供额外的时钟漂移分辨率。当前,应用可在同一上下文内多点记录时间(通过 Date.now()now()),并观察它们之间的漂移——例如由于自动或用户时钟调整。通过 timeOrigin 属性,攻击者还可以比较 时间原点(由 单调时钟 报告)与当前时间估算(即 performance.timeOriginDate.now() - performance.now() 之差),并有可能在较长时间观察这些时钟的漂移。

实际上,应用可在多次导航间观察到同样的时间漂移:应用可记录每个上下文的逻辑时间,并使用客户端或服务端时间同步机制推断用户时钟的变化。同样,底层机制如 TCP 时间戳可向服务器暴露同样的高分辨率信息,无需多次访问。因此,本 API 提供的信息不应暴露关于用户的任何重要或先前不可获得的熵。

10. 隐私注意事项

当前针对 时间原点 的定义, 对于 Document, 会暴露跨源重定向到请求到达文档源之前的总耗时。这会暴露跨源信息,但如何在不引发性能指标重大故障的前提下缓解这一点尚未决定。

相关讨论请参阅 Navigation Timing Issue 160

11. 一致性

除标为“非规范性”的章节外,本规范中的所有编写指南、图示、示例和注释均为非规范性内容。除此之外的所有内容均为规范性内容。

本文件中的关键字 MUSTSHOULD, 应按照 BCP 14 [RFC2119] [RFC8174] 的描述进行解释,仅在其以全大写形式出现时按此解释。

某些一致性要求以属性、方法或对象为对象提出。此类要求应理解为对用户代理的要求。

A. 索引

A.1 本规范定义的术语

A.2 引用定义的术语

B. IDL 索引

WebIDLtypedef double DOMHighResTimeStamp;

typedef unsigned long long EpochTimeStamp;

[Exposed=(Window,Worker)]
interface Performance : EventTarget {
    DOMHighResTimeStamp now();
    readonly attribute DOMHighResTimeStamp timeOrigin;
    [Default] object toJSON();
};

partial interface mixin WindowOrWorkerGlobalScope {
  [Replaceable] readonly attribute Performance performance;
};

C. 致谢

感谢 Arvind Jain、Angelos D. Keromytis、Boris Zbarsky、Jason Weber、Karen Anderson、Nat Duca、Philippe Le Hegaret、Ryosuke Niwa、Simha Sethumadhavan、Todd Reifsteck、Tony Gentilcore、Vasileios P. Kemerlis、Yoav Weiss 和 Yossef Oren 对本工作的贡献。

D. 参考文献

D.1 规范性引用

[dom]
DOM 标准。Anne van Kesteren。WHATWG。实时标准。URL: https://dom.spec.whatwg.org/
[ECMA-262]
ECMAScript 语言规范。Ecma International。URL: https://tc39.es/ecma262/multipage/
[html]
HTML 标准。Anne van Kesteren;Domenic Denicola;Dominic Farolino;Ian Hickson;Philip Jägenstedt;Simon Pieters。WHATWG。实时标准。URL: https://html.spec.whatwg.org/multipage/
[infra]
Infra 标准。Anne van Kesteren;Domenic Denicola。WHATWG。实时标准。URL: https://infra.spec.whatwg.org/
[RFC2119]
在RFC中用于指示需求级别的关键字。S. Bradner。IETF。1997年3月。最佳当前实践。URL: https://www.rfc-editor.org/rfc/rfc2119
[RFC8174]
RFC 2119关键字中大小写歧义。B. Leiba。IETF。2017年5月。最佳当前实践。URL: https://www.rfc-editor.org/rfc/rfc8174
[SPECTRE]
Spectre 攻击:利用推测执行。Paul Kocher;Jann Horn;Anders Fogh;Daniel Genkin;Daniel Gruss;Werner Haas;Mike Hamburg;Moritz Lipp;Stefan Mangard;Thomas Prescher;Michael Schwarz;Yuval Yarom。2018年1月。URL: https://spectreattack.com/spectre.pdf
[Temporal]
Temporal。ECMA TC39。第3阶段提案。URL: https://tc39.es/proposal-temporal/
[WEBIDL]
Web IDL 标准。Edgar Chen;Timothy Gu。WHATWG。实时标准。URL: https://webidl.spec.whatwg.org/

D.2 参考性引用

[CACHE-ATTACKS]
沙箱中的间谍——JavaScript中的实际缓存攻击。Yossef Oren;Vasileios P. Kemerlis;Simha Sethumadhavan;Angelos D. Keromytis。2015年3月1日。URL: https://arxiv.org/abs/1502.07373
[reporting]
Reporting API。Douglas Creager;Ian Clelland;Mike West。W3C。2024年8月13日。W3C工作草案。URL: https://www.w3.org/TR/reporting-1/
[service-workers]
Service Workers。Jake Archibald;Marijn Kruisselbrink。W3C。2022年7月12日。W3C候选推荐。URL: https://www.w3.org/TR/service-workers/