1. 引言
本节为非规范性内容。
知道关键元素何时显示在屏幕上,是理解页面加载性能的关键。 虽然快速渲染基本组件不足以带来令人满意的加载体验,但它是必要的。 因此,监测这些渲染时间戳对于改进页面加载并防止回退非常重要。
本规范为开发者和分析服务提供者提供了一种 API,用于测量关键元素的渲染时间戳。 目前还没有很好的方法可以为真实用户测量这些时间戳。 现有方法要么需要过早注册观察器,要么需要大量 DOM 操作。 这些方法会在 § 4 安全与隐私考量 一节中讨论。
Web 开发者是其站点中关键用户交互方面的专家,因此应允许他们 告知用户代理自己关心哪些元素。 因此,此 API 暴露关于 Web 开发者所标注元素的渲染计时信息。
1.1. 暴露的元素
Element Timing API 支持关于 符合计时条件的 元素的计时信息,如 [PAINT-TIMING] 所定义。
具有 "elementtiming" 内容属性的元素,会在 报告
图像元素计时 和 报告文本元素计时 算法中被报告。
1.2. 用法示例
以下示例展示了一个通过其 elementtiming 属性注册以供观察的图像,以及一个收集计时信息的观察器。
< img... elementtiming = 'foobar' /> < p elementtiming = 'important-paragraph' > This is text I care about.</ p > ...< script > const observer= new PerformanceObserver(( list) => { let perfEntries= list. getEntries(); // 通过迭代处理这些条目。 }); observer. observe({ type: 'element' , buffered: true }); </ script >
以下是一些示例元素,它们的渲染时间戳可以使用此 API 进行测量,并且 应与页面导航进行比较:
-
购物网站的图像轮播中的图像。
-
新闻网站报道中的主照片。
-
博客文章的标题。
-
百科网站条目中的第一段。
通过将渲染时间戳与输入时间戳进行比较,该 API 还可以用于页面加载之外的用例。 例如,开发者可以监测在触发某个 widget 的点击之后,该 widget 显示出来所需的时间。
2. Element Timing
Element Timing 涉及以下新接口:
2.1.
PerformanceElementTiming
接口
[Exposed =Window ]interface :PerformanceElementTiming PerformanceEntry {readonly attribute DOMHighResTimeStamp ;renderTime readonly attribute DOMHighResTimeStamp ;loadTime readonly attribute DOMRectReadOnly ;intersectionRect readonly attribute DOMString ;identifier readonly attribute unsigned long ;naturalWidth readonly attribute unsigned long ;naturalHeight readonly attribute DOMString ;id readonly attribute Element ?;element readonly attribute USVString ; [url Default ]object (); };toJSON PerformanceElementTiming includes PaintTimingMixin ;
一个 PerformanceElementTiming
对象会报告一个关联元素的计时信息。
每个 PerformanceElementTiming
对象都有这些关联概念,它们最初都被设置为 :
PerformanceElementTiming
的关联概念和一些属性在 § 3.3 报告图像元素
计时 和 § 3.4 报告文本元素计时 的处理模型中指定。
entryType
属性的 getter 必须返回 DOMString
。
name
属性的 getter 必须返回它被初始化为的值。
startTime
属性的 getter 必须返回 this 的 renderTime
的值,如果它不为 0;否则返回 this 的 loadTime
的值。
duration
属性的 getter 必须返回 0。
renderTime
属性 getter 步骤是:在给定 this 的绘制计时信息的情况下,返回默认绘制时间戳。
loadTime
属性的 getter 必须返回它被初始化为的值。
intersectionRect
属性必须返回它被初始化为的值。
identifier
属性的 getter 必须返回它被初始化为的值。
naturalWidth
属性必须返回它被初始化为的值。
naturalHeight
属性必须返回它被初始化为的值。
id
属性的 getter 必须返回它被初始化为的值。
element
属性的 getter 必须执行以下步骤:
注: 这意味着一个不再是 Document
的后代的元素,将不再由 element
属性 getter 返回。
url
属性的 getter 必须执行以下步骤:
注: 对于 data URL,URL 会被截断,以避免 条目中占用过多内存。
3. 处理模型
注: 实现 Element Timing API 的用户代理
需要在 Window
上下文的 supportedEntryTypes
中包含 。
这允许开发者检测对 element timing 的支持。
3.1. 对 DOM 规范的修改
一旦 [DOM] 规范被修改,本节将被移除。
我们按如下方式扩展 Element
接口:
partial interface Element { [CEReactions ,Reflect ]attribute DOMString ; };elementTiming
3.2. 报告元素计时
Document
doc、[/=paint timing info=] paintTimingInfo、一个由待处理图像记录组成的有序集合
paintedImages,以及一个由元素
组成的有序
集合 paintedTextNodes 的情况下报告
元素计时时,执行以下步骤:
3.3. 报告图像元素计时
Document
document 的情况下报告图像元素计时时,执行以下步骤:
-
如果 record 的元素的 "
elementtiming" 内容属性不存在,则中止这些步骤。 -
令 intersectionRect 为使用 record 的元素作为目标、viewport 作为根运行交叉矩形算法所返回的值。
-
使用 document 的相关 Realm,创建并初始化一个
PerformanceElementTiming对象 entry,其绘制计时信息为 paintTimingInfo。-
将 entry 的
intersectionRect初始化为 intersectionRect。 -
将 entry 的
identifier初始化为 record 的元素的 "elementtiming" 内容属性。 -
通过为一个
img的naturalWidth和naturalHeight属性 getter 运行相同步骤,但使用 record 的请求作为图像, 来初始化 entry 的naturalWidth和naturalHeight。
-
将 PerformanceEntry 入队 entry。
3.4. 报告文本元素计时
Element
element、一个 绘制计时信息 paintTimingInfo 和一个 Document
document 的情况下报告文本元素计时时,执行以下步骤:
-
如果 element 的 "
elementtiming" 内容属性 不存在,则中止这些步骤。 -
令 intersectionRect 为空矩形。
-
对 element 的拥有的文本节点集合中的每个
Text节点 text:-
增广 intersectionRect,使其成为包含 text 的 border box 和 intersectionRect 的最小矩形。
-
-
将 intersectionRect 与 visual viewport 相交。
-
使用 document 的相关 Realm,创建并初始化一个
PerformanceElementTiming对象 entry,其绘制计时信息为 paintTimingInfo。-
将 entry 的元素初始化为 element。
-
将 entry 的
loadTime初始化为 0。 -
将 entry 的
intersectionRect初始化为 intersectionRect。 -
将 entry 的
identifier初始化为 element 的 "elementtiming" 内容 属性。 -
将 entry 的
naturalWidth和naturalHeight初始化为 0。 -
将 entry 的
id初始化为 element 的 "id" 内容属性。
-
-
将 PerformanceEntry 入队 entry。
4. 安全与隐私考量
此 API 暴露了一些关于跨源图像的信息。 特别是,图像会暴露其资源加载时间,这可能成为隐私方面的担忧来源。
然而,这被认为不会向 Web 平台添加新的攻击,因为 ResourceTiming API 已经暴露了
类似的时间戳。
此外,onload 处理器会在可用时暴露加载计时,而资源加载时间与此非常接近。
在 onload 处理器开始时计算的当前高精度时间会提供图像加载时间。
我们选择暴露 loadTime,
因为即使没有 onload 处理器,也很容易获取它。
此外,我们相信,任何用于移除图像 onload 处理器或 ResourceTiming 所提供泄露的修复,
也可以修复此 API 所提供的泄露。
renderTime
(显示时间戳)确实是新暴露的信息。建议实现进一步粗化该时间戳,
至少粗化到 4 毫秒分辨率,以避免暴露跨源图像之间解码时间的差异。注意,其他检查
例如 `Timing-Allow-Origin` 在这里不起作用,因为同源图像和跨源图像会同时被渲染。
无论如何,暴露粗粒度的 renderTime
并不是一个实质性的攻击向量,因为图像自然尺寸
和加载时间已经通过其他方式暴露。
// 在攻击者 frame 中。< iframe src = attack.html ></ iframe > < script > window. onmessage= e=> { let timestamp= e. data; // 获取了 'victim.jpg' 的显示时间戳! } </ script > // 在 attack.html iframe 中。< img src = 'victim.jpg' /> < script > // 等到 onload 或某个 PaintTiming 条目可见的时刻。 onload() => { let entry= performance. getEntriesByType( 'paint' )[ 0 ]; top. postMessage( entry. startTime, '*' ); } </ script >
这里暴露的另一个非平凡参数是 intersectionRect。
这已经可以 polyfill,例如使用 IntersectionObserver。
polyfill 过程会类似:在目标图像或文本内容的 onload 处理器上添加一个 IntersectionObserver。
该方案效率低,因为它需要在内容加载后注册观察器,但仍应提供相同程度的准确性。
如果我们只计算到图像完全显示为止的 rect,那么也只能在那个时间之后暴露
条目。
如果我们不想暴露图像的渲染时间戳,最好立即将条目分发给
PerformanceObserver。
假设我们等待并在报告元素计时算法期间暴露所有条目。
攻击者就可以推断关于图像渲染时间戳的非平凡信息。
它会通过仅观察该图像的计时来做到这一点。
即使时间戳没有作为收到的 PerformanceElementTiming
条目的成员暴露,
我们等待到下一个更新渲染步骤这一事实,意味着攻击者可以通过测量收到
条目的时间来区分非常慢的渲染时间和非常快的渲染时间。
这会无意中泄露图像的一些显示计时。