1. 引言
在页面加载时,以及之后用户与页面交互时,应用和浏览器都会将各种事件排入队列,然后由浏览器执行—— 例如,用户代理会根据用户的活动调度输入事件,应用会为 requestAnimationFrame 和其他回调 调度回调, 等等。一旦进入队列,浏览器就会逐个出队这些事件并执行它们。
然而,某些任务可能会花费很长时间(多个帧),如果/当这种情况发生时,UI 线程可能会被 阻塞,并且也阻塞所有其他任务。对用户来说,这通常表现为一个“卡死”的页面,其中 浏览器无法响应用户输入;这是当今 Web 上糟糕用户体验的一个主要来源:
- 延迟的“可交互时间”:
-
当页面正在加载,甚至已经完全视觉渲染完成时,长任务通常会占用主 线程,并阻止用户与页面交互。设计不佳的第三方内容 经常是罪魁祸首。
- 高/可变的输入延迟:
-
关键的用户交互事件(例如 tap、click、scroll、wheel 等)排在长任务之后, 这会产生卡顿且不可预测的用户体验。
- 高/可变的事件处理延迟:
-
与输入类似,处理事件回调(例如 onload 事件等)会延迟应用更新。
- 卡顿的动画和滚动:
-
某些动画和滚动交互需要合成器线程与主线程之间进行协调; 如果长任务阻塞了主线程,它就会影响动画和滚动的响应性。
一些应用(以及 RUM 供应商)已经 在尝试识别和跟踪发生“长任务”的情况。例如,一个已知模式是 安装一个较短的周期性计时器,并检查连续触发之间经过的时间:如果 经过的时间大于计时器周期,那么很可能有一个或多个长任务 延迟了事件循环的执行。这种方法大体可行,但有若干不良的性能 影响:通过轮询检测长任务,应用会阻止静止状态和长空闲块(见 requestIdleCallback);它对电池续航不利;也无法知道是什么导致了延迟(例如 第一方代码还是第三方代码)。
RAIL 性能模型 建议应用应在小于 100ms 内响应用户输入(对于触摸移动和滚动, 阈值为 16ms)。此 API 的目标是公开关于可能阻止 应用达到这些目标的任务的通知。此 API 会公开耗时 50ms 或更长的任务。没有 这些任务的网站应能在 100ms 以内响应用户输入:当接收到用户输入时, 完成正在执行的任务将少于 50ms,并且执行用于响应此类 用户输入的任务也少于 50ms。
1.1. 使用示例
const observer= new PerformanceObserver( function ( list) { for ( const entryof list. getEntries()) { // 处理长任务通知: // 回报给分析和监控系统 // ... } }); // 为过去和未来的长任务通知注册观察者。 observer. observe({ type: "long-animation-frame" , buffered: true }); // 此后的长脚本执行将导致在观察者中排队 // 并接收 "long-animation-frame" 条目。 // 为过去和未来的长动画帧通知注册观察者。 // 此后,主线程忙碌的长时间段将导致在观察者中排队 // 并接收 "long-animation-frame" 条目。 observer. observe({ type: "long-animation-frame" , buffered: true });
1.2. 长动画帧与长任务
虽然 long tasks 和长动画帧都衡量拥塞和卡顿,但长 动画帧 提供的信息与用户如何感知这类拥塞具有更好的相关性。 这是因为长动画帧衡量的是一个序列:该序列在主线程空闲时开始, 并在该帧渲染完成,或用户代理决定没有内容需要渲染时结束。
task 这个术语在某种程度上属于实现细节,而长动画 帧的补充 试图通过引入一种更以用户为中心的指标,来补救同一种主 线程拥塞/卡顿现象。
由于长动画帧保证最多只有一个渲染阶段,我们也可以
使用它们公开关于渲染阶段本身的额外信息,例如
renderStart
和 styleAndLayoutStart。
2. 长动画帧计时
long animation frame 指以下任一持续时间超过 50ms 的情况:
长动画帧计时涉及以下新接口:
2.1. PerformanceLongAnimationFrameTiming
接口
[Exposed =Window ]interface :PerformanceLongAnimationFrameTiming PerformanceEntry { /* Overloading PerformanceEntry */readonly attribute DOMHighResTimeStamp ;startTime readonly attribute DOMHighResTimeStamp ;duration readonly attribute DOMString ;name readonly attribute DOMString ;entryType readonly attribute DOMHighResTimeStamp ;renderStart readonly attribute DOMHighResTimeStamp ;styleAndLayoutStart readonly attribute DOMHighResTimeStamp ;blockingDuration readonly attribute DOMHighResTimeStamp ; [firstUIEventTimestamp SameObject ]readonly attribute FrozenArray <PerformanceScriptTiming >; [scripts Default ]object (); };toJSON PerformanceLongAnimationFrameTiming includes PaintTimingMixin ;
一个 PerformanceLongAnimationFrameTiming
具有一个 frame timing info
timing info。
entryType
属性的 getter 步骤是返回 。
name
属性的 getter 步骤是返回 。
startTime
属性的 getter 步骤是返回给定 relative high resolution time,其参数为 this 的 timing info 的 start time 以及 this 的 relevant global object。
duration
属性的 getter 步骤是返回 duration,该持续时间位于 this 的 startTime
与给定 relative high resolution time 之间,后者的参数为 this 的 timing info 的 end time 以及 this 的 relevant global object。
renderStart
属性的 getter 步骤是返回给定 relative high resolution time,其参数为 this 的 timing info 的 update the rendering start time 以及
this 的 relevant global object。
styleAndLayoutStart
属性的 getter 步骤是返回给定 relative high resolution time,其参数为 this 的 timing info 的 style and layout start time 以及 this 的 relevant global object。
firstUIEventTimestamp
属性的 getter 步骤是返回给定 relative high resolution time,其参数为 this 的 timing info 的 first ui event timestamp 以及 this 的 relevant global object。
blockingDuration
属性的 getter 步骤是:
-
令 sortedTaskDurations 为 timing info 的 task durations,并按 降序排序。
-
如果 this 的 timing info 的 update the rendering start time 不是零,则:
-
令 renderDuration 为 duration,位于 this 的
renderStart与 给定 relative high resolution time 之间, 该时间的参数为 this 的 timing info 的 end time。 -
将 sortedTaskDurations[0] 增加 renderDuration。
注: 这样可以使最长的 任务持续时间 + 渲染持续时间在其总 持续时间 >50ms 时被视为阻塞。
-
-
令 totalBlockingDuration 为 0。
-
对 sortedTaskDurations 中的每个 duration 执行,如果 duration 大于 50,则将 totalBlockingDuration 增加 duration - 50。
-
返回 totalBlockingDuration。
scripts
属性的 getter 步骤是:
-
令 scripts 为一个 list « »。
-
令 entryWindow 为 this 的 relevant global object。
-
对 每个 scriptInfo,其位于 this 的 frame timing info 的 scripts 中:
-
令 scriptWindow 为 scriptInfo 的 window。
-
令 scriptEntry 为 this 的 relevant realm 中的一个新的
PerformanceScriptTiming, 其 timing info 是 scriptInfo,且其 window attribution 是 与第一个匹配语句对应的值:- scriptWindow 为 undefined
- scriptWindow 是 entryWindow
- entryWindow 的关联
Document的 node navigable 的 ancestor navigables contains scriptWindow 的关联Document的 node navigable - scriptWindow 的关联
Document的 node navigable 的 ancestor navigables contains entryWindow 的关联Document的 node navigable - entryWindow 的关联
Document的 node navigable 的 top-level traversable 是 scriptWindow 的 关联Document的 node navigable 的 top-level traversable - 否则
-
将 scriptEntry Append 到 scripts。
-
-
返回 scripts。
2.2.
PerformanceScriptTiming
接口
enum {ScriptInvokerType ,"classic-script" ,"module-script" ,"event-listener" ,"user-callback" ,"resolve-promise" };"reject-promise" enum {ScriptWindowAttribution ,"self" ,"descendant" ,"ancestor" ,"same-page" }; ["other" Exposed =Window ]interface :PerformanceScriptTiming PerformanceEntry { /* Overloading PerformanceEntry */readonly attribute DOMHighResTimeStamp ;startTime readonly attribute DOMHighResTimeStamp ;duration readonly attribute DOMString ;name readonly attribute DOMString ;entryType readonly attribute ScriptInvokerType ;invokerType readonly attribute DOMString ;invoker readonly attribute DOMHighResTimeStamp ;executionStart readonly attribute DOMString ;sourceURL readonly attribute DOMString ;sourceFunctionName readonly attribute long long ;sourceCharPosition readonly attribute DOMHighResTimeStamp ;pauseDuration readonly attribute DOMHighResTimeStamp ;forcedStyleAndLayoutDuration readonly attribute Window ?;window readonly attribute ScriptWindowAttribution ; [windowAttribution Default ]object (); };toJSON
一个 PerformanceScriptTiming
具有关联的 script
timing info timing info。
一个 PerformanceScriptTiming
具有关联的 ScriptWindowAttribution
window attribution。
entryType
属性的 getter 步骤是返回 。
name
属性的 getter 步骤是返回 。
invokerType
属性的 getter 步骤是返回 this 的 timing info 的 invoker type。
invoker
属性的 getter 步骤是:
-
对 this 的
invokerType执行 switch:- "`classic-script`"
- "`module-script`"
-
返回 this 的 timing info 的 source url。
- "`event-listener`"
-
-
令 targetName 为 this 的 timing info 的 invoker name。
-
如果 this 的 timing info 的 event target element id 不是空字符串,则: 将 targetName 设为 « targetName, "#", this 的 timing info 的 event target element id » 的 concatenation。
-
否则,如果 this 的 timing info 的 event target element src attribute 不是空字符串,则: 将 targetName 设为 « targetName, "[src=", this 的 timing info 的 event target element src attribute, "]" » 的 concatenation。
-
返回 « targetName, ".on", this 的 timing info 的 event type » 的 concatenation。
-
- "`user-callback`"
-
返回 this 的 timing info 的 invoker name。
- "`resolve-promise`"
- "`reject-promise`"
-
-
如果 this 的 timing info 的 invoker name 是空 字符串, 则:
-
如果 this 的
invokerType是 "`resolve-promise`",则返回 "`Promise.resolve`"。 -
否则,返回 "`Promise.reject`"。
-
-
如果
invokerType是 "`resolve-promise`",则令 thenOrCatch 为 "`then`";否则为 "`reject-promise`"。 -
返回 « invoker name, ".", thenOrCatch » 的 concatenation。
-
- "`classic-script`"
startTime
属性的 getter 步骤是返回给定 relative high resolution time,其参数为 this 的 timing info 的 start time 以及 this 的 relevant global object。
duration
属性的 getter 步骤是返回 duration,该持续时间位于 this 的 startTime
与给定 relative high resolution time 之间,后者的参数为 this 的 timing info 的 end time 以及 this 的 relevant global object。
executionStart
属性的 getter 步骤是:如果 this 的 timing
info 的 execution start time 为 0,则返回 0;否则返回给定 relative high resolution time,其参数为 this 的 timing info 的 execution start time 以及 this 的 relevant global object。
forcedStyleAndLayoutDuration
属性的 getter 步骤是返回一个 implementation-defined 值,表示同步执行
样式和布局所花费的时间,例如调用 getComputedStyle()
或 getBoundingClientRect()。
找到一种方式使其 可互操作/规范化。也许在 WebIDL 中标记这些函数为需要同步样式/布局?并且 一旦解决,也将其移动到 timing info。
pauseDuration
属性的 getter 步骤是返回 this 的 timing info 的 pause
duration。
sourceURL
属性的 getter 步骤是返回 this 的 timing info 的 source url。
sourceFunctionName
属性的 getter 步骤是返回 this 的 timing info 的 source function name。
sourceCharPosition
属性的 getter 步骤是返回 this 的 timing info 的 source character position。
window
属性的 getter 步骤是:
-
令 window 为在 this 的 timing info 的 window 上调用 deref 的结果。
-
如果 window 为 undefined,则返回 null;否则返回 window。
windowAttribution
属性的 getter 步骤是返回 this 的 window attribution。
3. 处理模型
注: 实现 Long Animation Frame
API 的用户代理需要分别为 Window
上下文,在 supportedEntryTypes
中包含 。
3.1. 帧计时信息
frame timing info 是一个 struct, 被长动画帧算法用作记账细节。 它具有以下 items:- start time
- current task start time
- update the rendering start time
- style and layout start time
- first ui event timestamp
- end time
- current task start time
-
一个
DOMHighResTimeStamp, 初始值为 0。 注:以上所有时间都是 unsafe 的, 并且在通过 API 暴露时应当被 coarsened。 - task durations
-
一个由
DOMHighResTimeStamp组成的 list, 初始为空。 - scripts
-
一个由 script timing info 组成的 list, 初始为空。
- pending script
-
Null 或一个 script timing info,初始为 null。
script timing info 是 一个 struct。它 具有以下 items:
- invoker type
- start time
- end time
- execution start time
- end time
-
一个 unsafe
DOMHighResTimeStamp, 初始值为 0。 - pause duration
-
一个
DOMHighResTimeStamp, 表示毫秒数,初始值为 0。 - invoker name
- source url
- source function name
- event type
- event target element id
- event target element src attribute
- source url
-
一个字符串,初始为空字符串。
- source character position
-
一个数字,初始值为 -1。
- window
一个 Document
具有 null 或 frame timing
info current
frame timing info,初始为 null。
3.2. 报告长动画帧
3.2.1. 长动画帧监控 {#loaf-monitoring}
Document
document 的 nearest
same-origin root:
-
令 ancestors 为 document 的 ancestor navigables。
-
对 ancestors 中的每个 ancestorNavigable 执行: 如果 ancestorNavigable 的 active document 的 origin 与 document 的 origin 是 same origin, 并且 ancestorNavigable 的 active document 的 relevant agent 是 document 的 relevant agent, 则返回 ancestorNavigable 的 active document。
-
返回 document。
Document
document 的 relevant
frame timing info 是其 nearest same-origin root 的 current frame timing
info。
DOMHighResTimeStamp
unsafeTaskStartTime 和一个 Document
document 的情况下,record task start
time:
-
令 root 为 document 的 nearest same-origin root。
-
如果 root 的 current frame timing info 为 null, 则将 root 的 current frame timing info 设为一个新的 frame timing info,其 start time 为 unsafeTaskStartTime。
-
将 root 的 current frame timing info 的 current task start time 设为 unsafeTaskStartTime。
-
如果 root 的 current frame timing info 的 's start time 为 0,则将 root 的 current frame timing info 的 start time 设为 unsafeTaskStartTime。
DOMHighResTimeStamp
unsafeTaskEndTime 和一个 Document
document 的情况下,record task end
time:
-
令 timingInfo 为 document 的 relevant frame timing info。
-
如果 timingInfo 为 null,则返回。
注: 如果浏览器在该序列期间 变为隐藏状态,则可能发生这种情况。
-
令 safeTaskEndTime 为给定 unsafeTaskEndTime 和 document 的 relevant global object 的 relative high resolution time。
-
令 safeTaskStartTime 为给定 timingInfo 的 current task start time 和 document 的 relevant global object 的 relative high resolution time。
-
将 safeTaskStartTime 与 safeTaskEndTime 之间的 duration Append 到 timingInfo 的 task durations。
-
如果用户代理认为更新 document 的 node navigable 的渲染不会产生可见效果,则:
-
将 document 的 nearest same-origin root 的 current frame timing info 设为 null。
-
令 frameDuration 为给定 timingInfo 的 start time 和 global 的 relative high resolution time 与给定 unsafeTaskEndTime 和 global 的 relative high resolution time 之间的 duration。
-
如果 frameDuration 大于或等于 50 毫秒,则在给定 document、timingInfo 和一个新的 [/=paint timing info=] 的情况下,queue a long animation frame entry。
注: 尽管没有实际的视觉 更新,我们仍在这里标记一个 long animation frame,因为在它与无关的视觉更新同时发生的场景中, 它会造成阻塞。
-
Document
document 和一个 DOMHighResTimeStamp
unsafeStyleAndLayoutStart 的情况下,record rendering
time:
-
令 timingInfo 为 document 的 relevant frame timing info。
-
如果 timingInfo 为 null,则返回。
注: 如果浏览器在该序列期间 变为隐藏状态,则可能发生这种情况。
-
令 frameDuration 为给定 timingInfo 的 start time 和 global 的 relative high resolution time 与给定 global 的 current high resolution time 之间的 duration。
-
如果 frameDuration 小于 50 毫秒,则将 document 的 nearest same-origin root 的 current frame timing info 设为 null 并返回。
-
将 timingInfo 的 update the rendering start time 设为 timingInfo 的 current task start time。
-
将 timingInfo 的 style and layout start time 设为 unsafeStyleAndLayoutStart。
Document
document、一个 frame timing info timingInfo 和一个 paint timing
info paintTimingInfo 的情况下,queue a
long animation frame entry,
在 document 的 relevant realm 中
queue 一个新的 PerformanceLongAnimationFrameTiming,
其 timing info 为 timingInfo,
且其 paint timing info 为 paintTimingInfo。
3.2.2. 长脚本监控
-
将 scriptTimingInfo 的 invoker name 设为 callback 的 identifier。
-
给定 callback,为 scriptTimingInfo Apply source location。
Function
handler、一个 environment settings object settings 和一个
布尔值 repeat 的情况下,record
timing info for timer handler:
给定 settings、"`user-callback`"
以及以下步骤,Create
script entry point,该步骤给定一个 script timing info scriptTimingInfo:
-
如果 repeat 为 true,则令 setTimeoutOrInterval 为 "setInterval", 否则为 "setTimeout"。
-
将 scriptTimingInfo 的 invoker name 设为 « TimerHandler:", setTimeoutOrInterval » 的 concatenation。
-
如果 handler 是一个
Function, 则给定 handler,为 scriptTimingInfo apply source location。
Event
event 和一个 EventListener
listener 的情况下,record timing info for event listener:
给定 listener 的 relevant settings object、"`event-listener`"
以及以下步骤,Create
script entry point,该步骤给定一个 script timing info scriptTimingInfo 和一个 frame timing info
frameTimingInfo:
-
将 scriptTimingInfo 的 event type 设为 event 的
type。 -
令 target 为 event 的
currentTarget。 -
如果 target 是一个
Node, 则:-
将 scriptTimingInfo 的 invoker name 设为 target 的
nodeName。 -
如果 target 是一个
Element, 则:-
将 scriptTimingInfo 的 event target element id 设为 target 的 id。
-
将 scriptTimingInfo 的 event target element src attribute 设为给定 "`src`" 和 target getting an attribute value by name 的结果。
-
-
-
否则,将 scriptTimingInfo 的 invoker name 设为 target 的 interface name。
-
给定 listener 的 callback,为 scriptTimingInfo Apply source location。
-
如果 event 是一个
UIEvent, 并且 frameTimingInfo 的 first ui event timestamp 为 0, 则将 frameTimingInfo 的 first ui event timestamp 设为 event 的timeStamp。
Promise
promise 和一个 "`resolve-promise`" 或 "`reject-promise`" type 的情况下,record
timing info for promise resolver:
-
给定 promise 的 relevant realm 的 settings object、type 和以下步骤,Create script entry point,该步骤给定一个 script timing info scriptTimingInfo:
-
将 scriptTimingInfo 的 invoker name 设为 promise 的 invoker name when created。
-
将 scriptTimingInfo 的 source url 设为 promise 的 script url when created。
-
-
如果 url 为 null,则返回。
-
如果 url 的 scheme 是 "`http`" 或 "`https`",则将 scriptTimingInfo 的 source url 设为 script 的 base URL。
-
否则,如果 url 的 scheme 是 "`blob`" 或 "`data`",则将 scriptTimingInfo 的 source url 设为 « url 的 scheme, ":"" » 的 concatenation。
-
用 script 的 settings object、"`classic-script`" 和以下步骤 Create script entry point,该步骤给定一个 script timing info scriptTimingInfo: 给定 scriptTimingInfo、script 和 originalSourceURL, Set source url for script block。
-
令 settings 为 script 的 settings object。
-
如果 script 的 muted errors 为 true,则返回。
-
如果 settings 不是一个
Window, 则返回。 -
令 document 为 settings 的
document。 -
令 frameTimingInfo 为 document 的 relevant frame timing info。
-
如果 frameTimingInfo 为 null,或如果 frameTimingInfo 的 pending script 不是 null,则返回。
-
断言:frameTimingInfo 的 pending script 的 invoker type 是 "`classic-script`"。
-
将 frameTimingInfo 的 pending script 的 execution start time 设为 unsafe shared current time。
-
将 scriptTimingInfo 的 execution start time 设为 script 的 scriptTimingInfo 的 start time。
-
给定 scriptTimingInfo、script 和 script 的 base URL, Set source url for script block。
ScriptInvokerType
invokerType 和 steps 的情况下,create script
entry point,
其中 steps 是一个接收 script timing info 和可选的 frame timing info 的算法:
-
如果 settings 不是一个
Window, 则返回。 -
令 document 为 settings 的
document。 -
如果 document 不是 fully active,或
, 则返回。 -
令 frameTimingInfo 为 document 的 relevant frame timing info。
-
如果 frameTimingInfo 为 null,则返回。
-
如果 frameTimingInfo 的 pending script 不是 null,则返回。
-
令 scriptTimingInfo 为一个新的 script timing info, 其 start time 为 unsafe shared current time, 并且其 invoker type 为 invokerType。
-
给定 scriptTimingInfo 和 frameTimingInfo,运行 steps。
-
将 scriptTimingInfo 的 window 设为 settings。
-
将 frameTimingInfo 的 pending script 设为 scriptTimingInfo。
-
令 script 为 running script。
-
令 settings 为 script 的 settings object。
-
令 document 为 settings 的
document。 -
如果 document 不是 fully active,或
, 则返回。 -
令 frameTimingInfo 为 document 的 relevant frame timing info。
-
令 scriptTimingInfo 为 frameTimingInfo 的 pending script。
-
将 frameTimingInfo 的 pending script 设为 null。
-
如果 scriptTimingInfo 为 null,则返回。
-
将 scriptTimingInfo 的 end time 设为 unsafe shared current time。
-
如果 script 是一个 classic script,且其 muted errors 为 true,则:
-
将 scriptTimingInfo 的 source url 设为空字符串。
-
将 scriptTimingInfo 的 source character position 设为 -1。
-
将 scriptTimingInfo 的 source function name 设为空字符串。
-
-
如果 scriptTimingInfo 的 start time 与 scriptTimingInfo 的 end time 之间的 duration 大于 5 毫秒,则将 scriptTimingInfo append 到 frameTimingInfo 的 scripts。
Function
callback 的情况下,将 apply source
location 应用于 scriptTimingInfo:
-
用户代理可以将 scriptTimingInfo 的 source url 设为定义 callback 的脚本的源 URL。
-
用户代理可以将 scriptTimingInfo 的 source function name 设为 callback 的函数名。
-
用户代理可以将 scriptTimingInfo 的 source character position 设为 定义 callback 的字符位置。
-
令 script 为 running script。
-
令 settings 为 script 的 settings object。
-
如果 settings 不是一个
Window, 则返回。 -
令 document 为 settings 的
document。 -
如果 document 不是 fully active,或
, 则返回。 -
令 frameTimingInfo 为 document 的 relevant frame timing info。
-
如果 frameTimingInfo 为 null,则返回。
-
如果 frameTimingInfo 的 pending script 为 null,则返回。
-
将 frameTimingInfo 的 pending script 的 pause duration 按 duration 的 毫秒值递增。
4. 对既有标准的补充
4.1. 对 WebIDL 标准的猴子补丁
Promise
接口具有一个关联字符串 invoker name when created,初始值为
"`Promise`"。
Promise
接口具有一个关联字符串 script url when created,初始为空
字符串。
将以下步骤追加到创建新
promise中,在返回 Promise
之前:
-
令 interfaceName 为一个字符串,表示负责创建此 promise 的 interface。
-
令 attributeName 为一个字符串,表示该接口中负责创建此 promise 的 attribute。
-
将创建的
Promise的 script url when created 设为 running script 的 base URL。 -
用户代理可以将创建的
Promise的 invoker name when created 设为 « interfaceName, ".", attributeName » 最后已知的 concatenation这相当 含糊,因为这很难以规范方式做到。需要看看 是否可以改进,或者 promise 处理程序的源位置是否仍将有点 implementation-defined。
将以下步骤前置到给定 Promise
p 的resolve a promise:
给定 p 和 "`resolve-promise`",
Record timing info for promise resolver。
将以下步骤前置到给定
Promise
p 的reject a promise:
给定 p 和 "`reject-promise`",
Record timing info for promise resolver。
5. 安全和隐私考虑
Long Animation Frames API 通过包含关于 长任务来源的源安全归因信息来遵循同源策略。长任务有 50ms 阈值。持续时间仅以 1 ms 粒度提供。结合起来,这为防止跨源泄漏提供了足够保护。
Long Animation Frames API 提供关于用户执行的任务的持续时间和类型的计时信息, 以及导致函数调用的浏览上下文等归因。这可能使 攻击者能够执行侧信道计时攻击,以猜测用户的动作,或识别用户。例如, 长脚本后跟长渲染的模式可以组合起来猜测用户与社交 小部件的交互。详细的函数调用归因会被用于确定用户的动作。
5.1. 向观察者暴露了什么?
顶层页面内的所有观察者(即页面中的所有 iframe 和主框架)都会收到 关于长动画帧存在的通知。我们公开任务的开始时间、其持续时间 (粒度为 1 ms),以及指向责任框架的指针。这些信息今天已经可以通过 setTimeout 观察到,并且具有更高分辨率。攻击者可以通过清空页面上的所有其他内容, 并添加易受攻击的跨源资源来确保 setTimeout 的延迟由该资源造成。 其他不同页面(标签页或窗口)中的观察者不应接收通知,无论用户代理的 架构如何。
5.2. 考虑过的攻击场景
以下是所考虑的计时攻击:
-
传统计时攻击:利用外部资源加载时间揭示 私有数据的大小。例如相册中隐藏图片的数量、用户名是否 有效等。见一个示例。
-
侧信道计时攻击:利用视频解析、脚本解析、App Cache 读取 或 Cache API(service workers)使用所花的时间来唯一识别用户,或创建 用户年龄、性别、位置和兴趣等画像。例如, 某个实例中, 来自社交网络的状态更新可能仅限于某些人口统计群体(例如 20-30 岁女性), 永久链接页面的文件大小可用于判断用户是否属于目标群体。
这些场景通过 50ms 阈值以及尊重跨源边界来处理,即 不向不可信的跨源观察者显示任务类型或额外归因。
5.3. Long Animation Frames API 暴露的额外信息
由于多个跨源文档可以共享同一个事件循环,它们也可以作为 同一帧序列的一部分进行渲染,并相互影响彼此的渲染时间。这使得这些计时 在跨源情况下已经有些可观察,例如通过请求一个动画帧并观察它是否 被延迟, 不过长动画帧会以更高保真度暴露它们。为缓解这一点,长动画帧只报告给“参与的本地根”:只有 与对该序列作出贡献的工作任务相关联的文档,或作为该帧一部分被渲染的文档, 才有资格观察该长动画帧,并且该长动画帧只会在 它们最近的祖先中可用,该祖先要么是最顶层的,要么具有跨源父级。
5.4. PerformanceScriptTiming
与不透明脚本
由于 PerformanceScriptTiming
会暴露关于脚本执行的信息,我们需要确保它
不会暴露过多关于 CORS
cross-origin 脚本的信息,而这些信息无法通过其他方式轻易推断出来。
为此,我们使用现有的 muted errors
布尔值,并在此类情况下报告一个空的 sourceURL。