长任务 API

W3C 工作草案,

关于本文档的更多详情
此版本:
https://www.w3.org/TR/2024/WD-longtasks-1-20240524/
最新发布版本:
https://www.w3.org/TR/longtasks-1/
编辑草案:
https://w3c.github.io/longtasks/
之前版本:
历史记录:
https://www.w3.org/standards/history/longtasks-1/
测试套件:
http://w3c-test.org/longtask-timing/
反馈:
GitHub
规范内联
编辑:
(Google)
前编辑:
(Google)
(Google)
(Google)

摘要

本文档定义了一个 API,网页作者可以使用该 API 检测“长任务”的存在,这些任务会长时间占用 UI 线程并阻止其他关键任务的执行,例如响应用户输入。

本文档的状态

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

本文档由 Web 性能工作组 作为 工作草案 使用 推荐轨道 发布。 作为工作草案发布 并不意味着 W3C 及其成员的认可。

本文档是一个草稿, 可能会随时更新、替换 或被其他文档废弃。 将本文档引用为除工作进展外的其他内容是不合适的。

GitHub 问题 是讨论本规范的首选方式。

本文档受 2023年11月3日 W3C 流程文档 管辖。

本文档由在 W3C 专利政策 下运作的小组制作。 W3C 维护了与该小组的可交付成果相关的 公开专利披露列表; 该页面还包括披露专利的说明。 任何实际知晓某项专利并认为该专利包含 必要权利要求 的个人, 必须根据 W3C 专利政策第 6 节 披露该信息。

如果您希望对本文档发表评论,请将其发送至 public-web-perf@w3.org (订阅, 存档),并在电子邮件主题的开头添加 [LongTasks]

1. 简介

在页面加载时以及用户随后与页面交互时,应用程序和浏览器都会排队各种事件,然后由浏览器执行这些事件——例如,用户代理根据用户活动安排输入事件,应用程序安排 requestAnimationFrame 的回调以及其他回调等。一旦进入队列,浏览器会逐个出队并执行这些事件。

然而,有些任务可能需要很长时间(多个帧),如果/当这种情况发生时,UI 线程可能会被阻塞,并且也会阻塞所有其他任务。对于用户来说,这通常表现为页面“卡住”,浏览器无法响应用户输入;这是当今网页上用户体验不佳的主要原因之一:

延迟“可交互时间”:

在页面加载时,甚至完全视觉渲染后,长任务通常会占用主线程并阻止用户与页面交互。设计不良的第三方内容通常是罪魁祸首。

高/不稳定的输入延迟:

关键用户交互事件(例如点击、滚动、滑动等)排在长任务之后,导致用户体验卡顿且不可预测。

高/不稳定的事件处理延迟:

与输入类似,处理事件回调(例如 onload 事件等)会延迟应用程序更新。

卡顿的动画和滚动:

某些动画和滚动交互需要合成器线程与主线程之间的协调;如果长任务阻塞了主线程,则会影响动画和滚动的响应性。

一些应用程序(以及 RUM 供应商)已经尝试识别和跟踪发生“长任务”的情况。例如,一个已知的模式是安装一个短周期定时器并检查连续到期之间的时间间隔:如果时间间隔大于定时器周期,则很可能一个或多个长任务延迟了事件循环的执行。这种方法大多有效,但有几个性能上的负面影响:通过轮询检测长任务,应用程序阻止了静止状态和长时间的空闲块(参见 requestIdleCallback);这对电池寿命不利;无法知道导致延迟的原因(例如第一方或第三方代码)。

RAIL 性能模型建议应用程序应在不到 100 毫秒内响应用户输入(对于触摸移动和滚动,阈值为 16 毫秒)。此 API 的目标是提供有关可能阻止应用程序达到这些目标的任务的通知。此 API 提供了耗时 50 毫秒或更长时间的任务通知。没有这些任务的网站应该能够在 100 毫秒内响应用户输入:完成用户输入时正在执行的任务需要不到 50 毫秒,响应用户输入的任务需要不到 50 毫秒。

1.1. 使用示例

const observer = new PerformanceObserver(function(list) {
    for (const entry of list.getEntries()) {
        // 处理长任务通知:
        // 回传分析和监控
        // ...
    }
});
// 为之前和未来的长任务通知注册观察者。
observer.observe({type: "longtask", buffered: true});
// 此后的长脚本执行将导致排队
// 并在观察者中接收“longtask”条目。

2. 术语

长任务是指持续时间超过50毫秒的以下任意情况:

浏览上下文容器对于浏览上下文 bcbc活动文档节点可导航容器

注:此术语已过时,在修订时应重用新术语。

罪魁祸首浏览上下文容器是指整体上被认定为长任务浏览上下文容器iframeobject等)。

归因是指识别对长任务产生重大影响的工作类型(如脚本、布局等),以及识别负责该工作的罪魁祸首浏览上下文容器

3. 长任务计时

长任务计时涉及以下新接口:

3.1. PerformanceLongTaskTiming 接口

[Exposed=Window]
interface PerformanceLongTaskTiming : PerformanceEntry {
    /* 重载PerformanceEntry */
    readonly attribute DOMHighResTimeStamp startTime;
    readonly attribute DOMHighResTimeStamp duration;
    readonly attribute DOMString name;
    readonly attribute DOMString entryType;

    readonly attribute FrozenArray<TaskAttributionTiming> attribution;
    [Default] object toJSON();
};

PerformanceLongTaskTiming 属性的值在§ 4.1 报告长任务的处理模型中设置。以下提供了如何设置它们的信息性摘要。

name 属性的getter将返回以下字符串之一:

"unknown"

长任务源于用户代理在事件循环之外执行的工作。

"self"

长任务源于此浏览上下文内的事件循环任务

"same-origin-ancestor"

长任务源于同源祖先可导航内的事件循环任务

"same-origin-descendant"

长任务源于同源后代浏览上下文内的事件循环任务

"same-origin"

长任务源于既不是祖先也不是后代的同源浏览上下文内的事件循环任务

"cross-origin-ancestor"

长任务源于跨域祖先可导航内的事件循环任务

"cross-origin-descendant"

长任务源于跨域后代浏览上下文内的事件循环任务

"cross-origin-unreachable"

长任务源于既不是祖先也不是后代的跨域浏览上下文内的事件循环任务

"multiple-contexts"

长任务源于涉及多个浏览上下文的事件循环任务

注:这些名称存在一些不一致性,例如"-unreachable"和"-contexts"后缀。保留这些名称是出于向后兼容性的原因。

entryType 属性的getter步骤是返回"longtask"

startTime 属性的getter步骤是返回任务开始时的DOMHighResTimeStamp

duration 属性的getter步骤是返回等于任务开始和结束之间经过时间的DOMHighResTimeStamp,精度为1毫秒。

attribution属性的getter将返回TaskAttributionTiming 条目的冻结数组。

3.2. TaskAttributionTiming 接口

[Exposed=Window]
        interface TaskAttributionTiming : PerformanceEntry {
            /* 重载PerformanceEntry */
            readonly attribute DOMHighResTimeStamp startTime;
            readonly attribute DOMHighResTimeStamp duration;
            readonly attribute DOMString name;
            readonly attribute DOMString entryType;
        
            readonly attribute DOMString containerType;
            readonly attribute DOMString containerSrc;
            readonly attribute DOMString containerId;
            readonly attribute DOMString containerName;
            [Default] object toJSON();
        };
        

TaskAttributionTiming 属性的值在§ 4.1 报告长任务的处理模型中设置。以下提供了如何设置它们的信息性摘要。

name 属性的getter始终返回"unknown"。

entryType 属性的getter始终返回"taskattribution"。

startTime 属性的getter始终返回0。

duration 属性的getter始终返回0。

containerType属性的getter将返回罪魁祸首浏览上下文容器的类型,如"iframe"、"embed"或"object"。如果未找到单一罪魁祸首浏览上下文容器,则返回"window"。

containerName属性的getter将返回容器name内容属性值。如果未找到单一罪魁祸首浏览上下文容器,则返回空字符串。

containerId 属性的getter将返回容器id内容属性值。如果未找到单一罪魁祸首浏览上下文容器,则返回空字符串。

containerSrc属性的getter将返回容器src内容属性值。如果未找到单一罪魁祸首浏览上下文容器,则返回空字符串。

3.3. 指向罪魁祸首

本节为非规范性内容。

一个长任务可能涉及不同类型的工作(如脚本、布局、样式等),并且可能在不同的浏览上下文中执行,或者它可能是全局性的,例如跨整个代理集群浏览上下文组集的长时间垃圾回收。

因此,归因有几个方面:

因此,name 字段和attribution 字段共同描述了长任务归因于何处。在传递这些信息时,必须遵守Web的同源策略。

这些字段不是独立的。下表概述了它们之间的关系:

name attribution 牵涉的 罪魁祸首浏览上下文容器(Culprit browsing context container)
"self"
"same-origin-ancestor" 同源罪魁
"same-origin-descendant" 同源罪魁
"same-origin" 同源罪魁
"cross-origin-ancestor"
"cross-origin-descendant"
"cross-origin-unreachable"
"multiple-contexts"
"unknown"

4. 处理模型

注:实现 Long Tasks API 的用户代理需要在 "longtask" 中包含 supportedEntryTypes, 针对 Window 上下文。

这允许开发者检测是否支持长任务。

4.1. 报告长任务

给定 start timeend timetop-level browsing contextstask,执行以下算法:
  1. 根据 end timetaskdocument记录任务结束时间

  2. 如果 end time 减去 start time 小于 50ms 的长任务阈值,则终止这些步骤。

  3. destinationRealms 为一个空集合。

  4. 确定将要报告的 JavaScript Realm 集合:

    对于 top-level browsing contexts 中的每个 顶级浏览上下文 topmostBC

    1. topmostBC活动文档相关 Realm 添加到 destinationRealms

    2. descendantBCstopmostBC活动文档后代浏览上下文列表

    3. documentdescendantBC活动文档

    4. 对于 descendantBCs 中的每个 descendantBC,将(document相关 Realmdocument相关设置对象跨域隔离能力)添加到 destinationRealms

  5. 用户代理可以从 destinationRealms 中移除一些 JavaScript Realm

注:这种移除可用于避免为用户代理在单独进程中处理的 JavaScript Realm 报告长任务。但该概念未被精确定义。

关于哪些 文档 能看到哪些长任务的范围仍在讨论中,因此该逻辑未来可能会变化。[Issue #75]

  1. 对于 destinationRealms 中的每个 (destinationRealm, crossOriginIsolatedCapability):

    1. name 为空字符串。该值用于下方报告 最小罪魁归因

    2. culpritSettingsnull

    3. 处理 task脚本执行环境设置对象集,以如下方式确定 nameculpritSettings

      1. 如果 task脚本执行环境设置对象集为空:将 name 设为 "unknown",culpritSettings 设为 null

      2. 否则,如果 task脚本执行环境设置对象集长度大于1:将 name 设为 "multiple-contexts",culpritSettings 设为 null

      3. 否则(即长度为1):

        1. culpritSettings 设为 task脚本执行环境设置对象集中的唯一项。

        2. destinationSettingsdestinationRealm相关设置对象

        3. destinationOrigindestinationSettingsorigin

        4. destinationBCdestinationSettings全局对象浏览上下文

        5. culpritBCculpritSettings全局对象浏览上下文

        6. 断言:culpritBC 不为 null

        7. 如果 culpritSettingsdestinationSettings 相同,则将 name 设为 "self"。

        8. 否则,如果 culpritSettingsorigindestinationOrigin 同源

          1. 如果 destinationBCnull,则 name 设为 "same-origin"。

          2. 否则,如果 culpritBCdestinationBC祖先,则 name 设为 "same-origin-ancestor"。

          3. 否则,如果 destinationBCculpritBC祖先,则 name 设为 "same-origin-descendant"。

          4. 否则,name 设为 "same-origin"。

        9. 否则:

          1. 如果 destinationBCnull,则 name 设为 "cross-origin-unreachable"。

          2. 否则,如果 culpritBCdestinationBC祖先,则 name 设为 "cross-origin-ancestor",并将 culpritSettings 设为 null

            注:出于安全原因不会报告此项,开发者应自行查找。

          3. 否则,如果 destinationBCculpritBC祖先,则 name 设为 "cross-origin-descendant"。

          4. 否则,name 设为 "cross-origin-unreachable"。

    4. attribution 为一个新的 TaskAttributionTiming 对象,使用 destinationRealm 并按如下设置其属性:

      1. attributionname 属性设为 "unknown"。

        注:未来 API 版本会为 TaskAttributionTiming 对象的 name 属性增加更多值,目前仅有一个值。

      2. attributionentryType 属性设为 "taskattribution"

      3. attributionstartTimeduration 设为 0。

      4. attributioncontainerType 属性设为 "window"

      5. attributioncontainerNamecontainerSrc 属性设为空字符串。

      6. 如果 culpritSettings 不为 null

        1. culpritBCculpritSettings全局对象浏览上下文

        2. 断言:culpritBC 不为 null

        3. containerculpritBC浏览上下文容器

        4. 断言:container 不为 null

        5. attributioncontainerId 属性设为 containerID 的值,若未设置则为空字符串。

        6. 如果 containeriframe 元素:

          1. attributioncontainerType 属性设为 "iframe"。

          2. attributioncontainerName 属性设为 containername 内容属性的值,若无则为空字符串。

          3. attributioncontainerSrc 属性设为 containersrc 内容属性的值,若无则为空字符串。

          注:此处记录的是 frame 的 src 属性,而不是其当前 URL,主要用于识别 frame,允许发现跨域 iframe 的当前 URL 存在安全问题。

        7. 如果 containerframe 元素:

          1. attributioncontainerType 属性设为 "frame"。

          2. attributioncontainerName 属性设为 containername 内容属性的值,若无则为空字符串。

          3. attributioncontainerSrc 属性设为 containersrc 内容属性的值,若无则为空字符串。

        8. 如果 containerobject 元素:

          1. attributioncontainerType 属性设为 "object"。

          2. attributioncontainerName 属性设为 containername 内容属性的值,若无则为空字符串。

          3. attributioncontainerSrc 属性设为 containerdata 内容属性的值,若无则为空字符串。

        9. 如果 containerembed 元素:

          1. attributioncontainerType 属性设为 "embed"。

          2. attributioncontainerName 属性设为空字符串。

          3. attributioncontainerSrc 属性设为 containersrc 内容属性的值,若无则为空字符串。

    5. 创建一个新的 PerformanceLongTaskTiming 对象 newEntry,使用 destinationRealm 并按如下设置其属性:

      1. newEntryname 属性设为 name

      2. newEntryentryType 属性设为 "longtask"。

      3. newEntrystartTime 属性设为 粗化 start time 的结果,参数为 crossOriginIsolatedCapability

      4. dur粗化 end time 的结果(参数同上),减去 newEntrystartTime

      5. newEntryduration 属性设为 dur 的整数部分。

      6. 如果 attribution 不为 null,则将 newEntryattribution 属性设为包含单个值 attribution 的新冻结数组。

        注:未来 API 版本会为 attribution 属性增加更多值,目前仅包含一个值。

    6. 将 PerformanceEntry newEntry 入队。

5. 安全与隐私注意事项

Long Tasks API 遵循同源策略,仅包含关于长任务来源的同源安全归因信息。长任务有 50ms 的阈值,持续时间仅以 1 毫秒为粒度提供。这些措施共同为跨域信息泄露提供了充分保护。

Long Tasks API 提供了关于用户执行任务的持续时间和类型的计时信息,以及归因信息(如导致函数调用的浏览上下文)。这可能使攻击者能够利用侧信道计时攻击来猜测用户行为或识别用户。例如,长脚本后跟长渲染的模式可能被用来推测用户与社交组件的交互。详细的函数调用归因可用于确定用户的具体操作。

虽然该 API 没有引入新的隐私攻击,但它可能加速现有的隐私攻击。可根据需要实施如下缓解措施:

5.1. 对观察者暴露了什么?

顶级页面内的所有观察者(即页面内所有 iframe 及主框架)都会收到长任务存在的通知。我们暴露了任务的开始时间、持续时间(1ms 粒度)以及指向罪魁 frame 的指针。这些信息目前已经可以通过 setTimeout 以更高分辨率观测到。攻击者可以通过清空页面其他内容,仅添加易受攻击的跨域资源,确保 setTimeout 的延迟由该资源引起。其他页面(标签页或窗口)中的观察者不应收到通知,无论用户代理的架构如何。

跨域暴露规则:

5.2. 考虑的攻击场景

以下是已考虑的计时攻击:

  1. 传统计时攻击:利用外部资源加载时间揭示私有数据的大小。例如,隐藏图片的数量、用户名是否有效等。参见示例

  2. 侧信道计时攻击:利用视频解析、脚本解析、App Cache 读取或 Cache API(service worker)使用的时间来唯一标识用户,或创建用户的年龄、性别、位置、兴趣等画像。例如,此文提到,社交网络的状态更新可仅限于某些人群(如 20-30 岁女性),而永久链接页面的文件大小可用于判断用户是否属于目标人群。

这些场景通过 50ms 阈值和遵守跨域边界(即不向不可信跨域观察者显示任务类型或额外归因信息)得到应对。

一致性

文档约定

一致性要求通过描述性断言和 RFC 2119 术语的组合表达。 本规范规范性部分中的关键词 “必须(MUST)”、“不得(MUST NOT)”、“要求(REQUIRED)”、“应(SHALL)”、“不应(SHALL NOT)”、“应该(SHOULD)”、“不应该(SHOULD NOT)”、“推荐(RECOMMENDED)”、“可以(MAY)” 和 “可选(OPTIONAL)” 应按照 RFC 2119 的说明进行解释。 但为便于阅读,本规范未将这些词全部大写。

除明确标记为非规范性、示例和注释的部分外,本规范的所有文本均为规范性内容。[RFC2119]

本规范中的示例以“例如”开头,或通过 class="example" 与规范性文本区分,如下所示:

这是一个信息性示例。

信息性注释以“注”开头,并通过 class="note" 与规范性文本区分,如下所示:

注:这是一个信息性注释。

一致性算法

作为算法一部分的命令式要求(如“去除所有前导空格字符”或“返回 false 并中止这些步骤”),应结合引入算法时所用关键词(如“必须”、“应该”、“可以”等)进行解释。

以算法或具体步骤表述的一致性要求可以以任何方式实现,只要最终结果等效即可。 本规范中定义的算法旨在易于理解,并不追求高性能。鼓励实现者进行优化。

索引

本规范定义的术语

引用文献定义的术语

参考文献

规范性引用

[DOM]
Anne van Kesteren. DOM 标准. Living Standard. URL: https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript 语言规范. URL: https://tc39.es/ecma262/multipage/
[HR-TIME-2]
Ilya Grigorik. High Resolution Time Level 2. 2019年11月21日. REC. URL: https://www.w3.org/TR/hr-time-2/
[HR-TIME-3]
Yoav Weiss. High Resolution Time. 2023年7月19日. WD. URL: https://www.w3.org/TR/hr-time-3/
[HTML]
Anne van Kesteren 等. HTML 标准. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[LONG-ANIMATION-FRAMES]
Long Animation Frames API. Editor's Draft. URL: https://w3c.github.io/long-animation-frames/
[PERFORMANCE-TIMELINE]
Nicolas Pena Moreno. Performance Timeline. 2024年2月16日. CR. URL: https://www.w3.org/TR/performance-timeline/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. 1997年3月. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL 标准. Living Standard. URL: https://webidl.spec.whatwg.org/

IDL 索引

[Exposed=Window]
interface PerformanceLongTaskTiming : PerformanceEntry {
    /* 重载 PerformanceEntry */
    readonly attribute DOMHighResTimeStamp startTime;
    readonly attribute DOMHighResTimeStamp duration;
    readonly attribute DOMString name;
    readonly attribute DOMString entryType;

    readonly attribute FrozenArray<TaskAttributionTiming> attribution;
    [Default] object toJSON();
};

[Exposed=Window]
interface TaskAttributionTiming : PerformanceEntry {
    /* 重载 PerformanceEntry */
    readonly attribute DOMHighResTimeStamp startTime;
    readonly attribute DOMHighResTimeStamp duration;
    readonly attribute DOMString name;
    readonly attribute DOMString entryType;

    readonly attribute DOMString containerType;
    readonly attribute DOMString containerSrc;
    readonly attribute DOMString containerId;
    readonly attribute DOMString containerName;
    [Default] object toJSON();
};

问题索引

关于哪些 文档 能看到哪些长任务的范围仍在讨论中,因此该逻辑未来可能会变化。[Issue #75]
MDN

PerformanceLongTaskTiming/attribution

In only one current engine.

FirefoxNoneSafariNoneChrome58+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

PerformanceLongTaskTiming/toJSON

In only one current engine.

FirefoxNoneSafariNoneChrome58+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

PerformanceLongTaskTiming

In only one current engine.

FirefoxNoneSafariNoneChrome58+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskAttributionTiming/containerId

In only one current engine.

FirefoxNoneSafariNoneChrome58+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskAttributionTiming/containerName

In only one current engine.

FirefoxNoneSafariNoneChrome58+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskAttributionTiming/containerSrc

In only one current engine.

FirefoxNoneSafariNoneChrome58+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskAttributionTiming/containerType

In only one current engine.

FirefoxNoneSafariNoneChrome58+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskAttributionTiming/toJSON

In only one current engine.

FirefoxNoneSafariNoneChrome58+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskAttributionTiming

In only one current engine.

FirefoxNoneSafariNoneChrome58+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?