Copyright © 2024 World Wide Web Consortium. W3C® liability, trademark and permissive document license rules apply.
本规范定义了一个 API,提供时间原点以及当前时间的亚毫秒分辨率,并且不受系统时钟偏移或调整影响。
本节描述了本文档在发布时的状态。当前 W3C 出版物列表以及本技术报告的最新修订版可在 W3C 技术报告索引 https://www.w3.org/TR/ 查阅。
本文档由 Web 性能工作组 作为工作草案,采用 推荐流程发布。
作为工作草案发布并不代表 W3C 及其成员的认可。
本文档为草稿版本,可能随时被更新、替换或废止。不应将本文件作为除“进行中的工作”之外的引用。
本文档由遵循 W3C 专利政策 的工作组编写。 W3C 维护着 与本工作组成果相关的专利公开名单 ,该页面还包括 专利披露说明。若个人实际知晓某项专利且认为其包含 基本权利要求 ,则必须按 《W3C专利政策》第6节规定披露信息。
本文档受 2023年11月03日 W3C 流程文件管理。
本节为非规范性内容。
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
属性能够通过
提供单调递增的亚毫秒时间值解决上述问题。
提供亚毫秒分辨率并非本规范的强制要求。各实现可因隐私和安全原因,选择限制所暴露的计时器分辨率,而不提供亚毫秒计时器。当这种情况发生时,依赖亚毫秒分辨率的用例可能无法满足。
本节为非规范性内容。
本规范定义了几种不同的能力:它提供了基于稳定单调时钟的时间戳,可在不同上下文间比较,并可能具备亚毫秒分辨率。
当谈论性能测量时,对稳定单调时钟的需求源于时钟偏移可能扭曲测量结果并使其无效。例如,在准确测量导航到一个文档、资源获取或脚本执行的耗时时,单调递增且亚毫秒分辨率的时钟是理想的。
在不同上下文间比较时间戳非常重要,例如在Worker
与主线程之间同步工作,或在记录这些工作以创建统一事件时间线时。
最后,亚毫秒计时器的需求主要围绕以下用例:
本节为非规范性内容。
开发者可能希望构建整个应用的时间线,包括来自Worker
或SharedWorker
的事件,
它们有不同的时间原点。为了在同一时间线显示这些事件,应用可以
利用DOMHighResTimeStamp
和
Performance
.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);
}
时钟用于追踪时间的流逝,并可以报告算法步骤正在执行时的 不安全当前时间。 时钟的种类有很多。Web平台上的所有时钟都试图每1毫秒时钟时间对应1毫秒现实世界时间,但它们处理无法完全准确的情况方式不同。
单调时钟的 不安全当前时间 永不减少,因此不会被系统时钟调整影响。单调时钟仅存在于 用户代理 的单次执行中,因此不能用于比较可能发生在不同执行中的事件。
由于单调时钟不能调整为用户的时间观念, 它应当用于测量而非用户可见时间。与用户交流时间时应使用挂钟。
用户代理在浏览器重启、开始隔离浏览会话(如隐身或类似模式)、或创建无法与现有设置对象通信的
环境设置对象
时,可以选择新的
Unix纪元单调时间估值。
因此,开发者不应将共享时间戳作为绝对时间,认为其在所有过去、现在和未来上下文都保持单调性;实际上,单调性仅适用于能够通过消息机制(如
postMessage
(message, options)
、
BroadcastChannel
等)相互通信的上下文。
某些场景下(如标签页被后台处理),用户代理可能选择限制该上下文中定时器和周期性回调的运行,甚至完全冻结。此类限制不应影响单调时钟返回时间的分辨率或准确性。
每个时钟的不安全当前时间会返回一个 不安全时刻。 粗化时间会将这些不安全时刻转换为 粗化时刻,简称 时刻。 来自不同时钟的不安全时刻和 时刻不可相互比较。
持续时间是同一个 时钟 上两个时刻之间的距离。两端不能是不安全时刻,这样无论是 持续时间 还是持续时间的差值,都可以缓解 9.1 时钟分辨率中的问题。 持续时间以毫秒、秒等单位计量。由于所有 时钟都试图以相同速率计数, 持续时间没有关联的时钟, 从一个时钟的两个时刻计算出的 持续时间可以加到另一个时钟的 时刻上,得到该时钟上的另一个 时刻。
持续时间从 a 到 b 的结果算法如下:
持续时间可隐式作为DOMHighResTimeStamp
使用。
要隐式将持续时间转换为时间戳,给定
持续时间 d,返回d中的毫秒数。
如需在单个页面内测量时间(即在单个
环境设置对象上下文内),请使用 settingsObject 的 当前相对时间戳,
定义为 settingsObject 的 时间原点 到该 settingsObject 的 当前单调时间 的
持续时间。该值可通过
持续时间 隐式转换为
DOMHighResTimeStamp
,直接暴露给
JavaScript。
如需在单次UA执行内测量时间,且某个 环境设置对象 的 时间原点 不适合作为比较基准时,可使用该 当前单调时间 创建 时刻。某 环境设置对象 settingsObject 的 当前单调时间 计算步骤如下:
来自单调时钟的 时刻 不能直接在 JavaScript 或 HTTP 中表示。应暴露两个此类 时刻之间的 持续时间。
如需跨多个UA执行测量时间,可使用 当前粗化挂钟时间创建 时刻, 或(如果需要在 跨域隔离上下文 中获得更高精度)使用 环境设置对象 的 当前挂钟时间。当前粗化挂钟时间 是以 挂钟 的 不安全当前时间 调用 粗化时间的结果。
某 环境设置对象 settingsObject 的 当前挂钟时间 计算步骤如下:
使用 挂钟 的 时刻时,务必设计以应对用户调整时钟(无论向前还是向后)的情况。
挂钟 的
时刻可通过将自
Unix纪元 到该
时刻的毫秒数传递给
构造器,或将自
Unix纪元 到该
时刻的纳秒数传递给
Temporal.Instant
构造器,在JavaScript中表示。
Date
避免在计算机间发送类似表示,因为这样会暴露用户的时钟偏移,这是一个 追踪向量。 应采用类似于 单调时钟 时刻 的方式, 发送两个 时刻之间的持续时间。
错误报告的年龄可用如下方式计算:
之后:
Unix纪元是 挂钟上的 时刻, 对应1970年1月1日00:00:00 UTC。
所有可能以任何方式通信的一组 环境设置对象 都有一个Unix纪元单调时间估值, 即 单调时钟上的 时刻, 其值初始化步骤如下:
monotonic time - (wall time - Unix纪元)
Worker
。
性能测量报告的是从相关 环境设置对象 初始化早期的某个 时刻 的 持续时间。 该时刻 存储于该设置对象的时间原点中。
要获取时间原点时间戳, 给定一个 全局对象 global, 执行以下步骤,返回一个 持续时间:
令 timeOrigin 为 global 的 相关设置对象的 时间原点。
在Window
上下文中,
此值表示导航开始的时间。
在Worker
和
ServiceWorker
内容中,
此值表示worker运行的时间。
[service-workers]
获取时间原点时间戳返回的值约等于
global 的
Unix纪元之后发生的
时间原点的时间。
它可能与在时间原点执行的
Date.now()
返回的值不同,
因为前者是基于
单调时钟记录的,
不受系统和用户时钟调整、时钟偏移等影响。
当前高分辨率时间 给定 全局对象 current global, 必须返回以 相对高分辨率时间 给定 不安全共享当前时间 和 current global 的结果。
粗化共享当前时间 给定可选布尔值crossOriginIsolatedCapability(默认false),必须返回以 不安全共享当前时间 和 crossOriginIsolatedCapability 调用粗化时间的结果。
DOMHighResTimeStamp
类型用于存储以毫秒为单位的
持续时间。根据其上下文,它可能表示某个
时刻,
即某个基准
时刻(如
时间原点或
Unix纪元)之后的该
持续时间。
WebIDLtypedef double DOMHighResTimeStamp
;
DOMHighResTimeStamp
应当
以足够精确的毫秒表示时间,
以便测量,同时防止计时攻击——更多内容见
9.1
时钟分辨率。
DOMHighResTimeStamp
是
double
,
因此只能以有限分辨率表示与纪元相关的时间——即从
Unix纪元
到某个
时刻
的毫秒数。对于2023年的
时刻而言,
该分辨率约为0.2微秒。
WebIDLtypedef unsigned long long EpochTimeStamp
;
EpochTimeStamp
表示从
Unix纪元
到
挂钟上的指定
时刻
的整数毫秒数(不包括闰秒)。使用该类型的规范会定义如何解释毫秒数。
WebIDL[Exposed=(Window,Worker)]
interface Performance
: EventTarget {
DOMHighResTimeStamp
now
();
readonly attribute DOMHighResTimeStamp
timeOrigin
;
[Default] object toJSON
();
};
now()
方法 必须返回
当前高分辨率时间(以
this 的
相关全局对象为参数)的毫秒数(一个
持续时间)。
当对具有相同
时间原点的
Performance
对象调用
now
()
方法时,
返回的时间值必须使用同一个
单调时钟。
从now()
方法按时间顺序记录的任意两次返回值之间的差值,
在时间原点相同的情况下必须永远不为负数。
timeOrigin
属性 必须返回
获取时间原点时间戳(以
相关全局对象
为参数)返回的
持续时间的毫秒数。
获取
Performance
.timeOrigin
时,
返回的时间值必须使用由
单调时钟共享的
时间原点,
其参考点为
[ECMA-262]
time定义——见
9.
安全注意事项。
当调用
toJSON()
时,
执行 [WEBIDL] 的
默认 toJSON 步骤。
接口混入 performance
属性允许从
WindowOrWorkerGlobalScope
访问与性能相关的属性和方法,
这些属性和方法属于 全局对象。
WebIDLpartial interface mixin WindowOrWorkerGlobalScope {
[Replaceable] readonly attribute Performance
performance
;
};
访问精确的时间信息,无论是用于测量还是调度,都是许多应用的常见需求。例如,页面上的动画、声音和其他活动的协调需要高分辨率时间,以提供良好的用户体验。同样,测量功能可以让开发者跟踪关键代码组件的性能,检测回退等。
但对同样精确的时间信息的访问,有时也可能被攻击者用于恶意目的,以猜测和推断他们无法直接看到或访问的数据。例如,缓存攻击、统计指纹识别和微架构攻击是隐私和安全方面的担忧,恶意网站可能利用各种浏览器或应用发起操作的高分辨率计时数据来区分用户子集、识别特定用户或暴露同进程的无关用户数据——详情请参阅 [CACHE-ATTACKS] 和 [SPECTRE]。
本规范定义了一个提供亚毫秒时间分辨率的 API,比此前 EpochTimeStamp
暴露的毫秒分辨率更精确。然而,即使没有这个新 API,攻击者也可能通过多次执行和统计分析获得高分辨率估算值。
为确保新 API 不会显著提升此类攻击的准确性或速度,DOMHighResTimeStamp
类型的最小分辨率应足够不精确,以阻止这些攻击。
必要时,用户代理应在 粗化时间 的处理模型中为 time resolution 设置更高的分辨率值,以应对因架构或软件限制等原因而产生的隐私和安全问题或其他考虑因素。
为减轻此类攻击,用户代理可采用任何他们认为必要的技术。这些技术的部署可能会根据浏览器架构、用户设备、内容及其恶意读取跨域数据的能力或其他实际情况而有所不同。
这些技术可能包括:
完全缓解此类计时侧信道攻击在实际中几乎不可能:要么所有操作都必须在与任何机密信息值无关的固定时间内执行,要么应用必须与任何与时间相关的原语(时钟、定时器、计数器等)隔离。由于涉及的复杂性及其对浏览器和应用开发者的影响,以及对应用性能和响应性的负面影响,这两种方式都不实际。
本规范还定义了一个 API,可提供时间原点零时的亚毫秒分辨率,这需要并向应用暴露了 单调时钟,且必须在所有浏览器上下文中共享。单调时钟无需与物理时间绑定,但建议参考 [ECMA-262] 的 time 定义,以避免暴露关于用户的新指纹熵——例如,这种时间应用已能轻易获得,而暴露新的逻辑时钟则会提供新的信息。
但即使采用上述机制,单调时钟仍可能提供额外的时钟漂移分辨率。当前,应用可在同一上下文内多点记录时间(通过 Date.now()
和
now
()
),并观察它们之间的漂移——例如由于自动或用户时钟调整。通过
timeOrigin
属性,攻击者还可以比较 时间原点(由 单调时钟 报告)与当前时间估算(即 performance.timeOrigin
和
Date.now() - performance.now()
之差),并有可能在较长时间观察这些时钟的漂移。
实际上,应用可在多次导航间观察到同样的时间漂移:应用可记录每个上下文的逻辑时间,并使用客户端或服务端时间同步机制推断用户时钟的变化。同样,底层机制如 TCP 时间戳可向服务器暴露同样的高分辨率信息,无需多次访问。因此,本 API 提供的信息不应暴露关于用户的任何重要或先前不可获得的熵。
当前针对 时间原点 的定义,
对于 Document
,
会暴露跨源重定向到请求到达文档源之前的总耗时。这会暴露跨源信息,但如何在不引发性能指标重大故障的前提下缓解这一点尚未决定。
相关讨论请参阅 Navigation Timing Issue 160。
除标为“非规范性”的章节外,本规范中的所有编写指南、图示、示例和注释均为非规范性内容。除此之外的所有内容均为规范性内容。
本文件中的关键字 MUST 和 SHOULD, 应按照 BCP 14 [RFC2119] [RFC8174] 的描述进行解释,仅在其以全大写形式出现时按此解释。
某些一致性要求以属性、方法或对象为对象提出。此类要求应理解为对用户代理的要求。
DOMHighResTimeStamp
§5.
EpochTimeStamp
§6.
now()
方法 for
Performance
§7.1
performance
属性 for WindowOrWorkerGlobalScope
§8.1
Performance
接口
§7.
timeOrigin
属性 for
Performance
§7.2
toJSON()
方法 for
Performance
§7.3
Document
接口
EventTarget
接口
timeStamp
属性(用于 Event
)
BroadcastChannel
接口
realm
)
postMessage(message, options)
(用于
Window
)
Window
接口
WindowOrWorkerGlobalScope
接口
Worker
接口
ServiceWorker
接口
[Default]
扩展属性
double
类型
[Exposed]
扩展属性
object
类型
[Replaceable]
扩展属性
unsigned long long
类型
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
;
};
感谢 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 对本工作的贡献。
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in: