容器计时 API

社区组报告草案,

此版本:
https://WICG.github.io/container-timing/
测试套件:
https://github.com/web-platform-tests/wpt/tree/master/container-timing
问题跟踪:
GitHub
编辑:
Jason Williams (Bloomberg)
(Igalia)

摘要

本规范定义了一个 API,用于监测 DOM 中带注解的区段何时显示在 屏幕上并已完成其初始绘制。

本文档状态

本规范由 Web 平台孵化器 社区组发布。 它不是 W3C 标准,也不在 W3C 标准流程中。 请注意,根据 W3C 社区贡献者许可协议 (CLA), 适用有限的退出机制和其他条件。 了解更多关于 W3C 社区组和商业组的信息。

1. 简介

容器计时 API 能够监测 DOM 中带注解的区段何时显示在屏幕上并 完成其初始绘制。开发者可以使用 containertiming 属性(类似于 Element Timing API 的 elementtiming)来标记 DOM 的子区段,并在该区段 第一次被绘制时接收性能条目。

此 API 允许开发者测量页面中各类组件的计时。随着开发者 越来越多地将应用组织成组件,对应用或网页的 子区段进行性能测量的需求也在增长。

与 Element Timing 不同,渲染器无法知道 DOM 的某个区段何时已经完成 绘制(可能还会有未来的变化、用于新图像的异步请求、加载缓慢的按钮等), 因此此 API 会在发生更新时,以 PerformanceEntry 对象的形式发出候选项。

2. 动机

开发者希望测量 DOM 子区段何时被绘制,例如表格、小部件或其他 组件,以便跟踪绘制时间并将其提交给分析系统。当前的 Web API 无法充分 支持这一点:

Web 作者比任何人都更了解自己的领域,并希望以其用户或组织能够理解的方式 传达自身内容块的性能(例如 “首次推文时间”)。

2.1. 生命周期

在此示例生命周期中,一个组件会在不同时间绘制多块内容,其中每一次 都会生成一个新的 PerformanceContainerTiming 条目,并包含更新后的信息。

不过,一旦某个区域被绘制,该同一区域的后续绘制将不会生成新条目。 容器计时生命周期图

3. 用法示例

下面的示例演示如何注册一个容器根并观察其绘制计时。

使用 containertiming 属性按元素进行注册:
<div containertiming="foobar">
  <main>...</main>
  <aside>...</aside>
</div>

<script>
  const observer = new PerformanceObserver((list) => {
    let perfEntries = list.getEntries();
    for (const entry of perfEntries) {
      console.log('Container painted:', entry.identifier,
                  'at', entry.startTime,
                  'size:', entry.size);
    }
  });
  observer.observe({ entryTypes: ["container"] });
</script>

该属性应在元素被添加到文档之前设置(在 HTML 中设置,或者如果在 JavaScript 中设置,则应在将其添加到文档之前设置)。事后设置该属性只会捕获 后续事件和未来绘制。

3.1. 忽略子树

可以使用 <containertiming-ignore> 属性忽略 DOM 树的部分内容:
<div containertiming="foobar">
  <main>...</main>
  <!-- Aside updates won't trigger container timing events -->
  <aside containertiming-ignore>...</aside>
</div>

4. 术语

容器根是一个 具有 Element 且带有 containertiming 属性的元素。

被忽略的子树是一个 以带有 Elementcontainertiming-ignore> 属性的元素为根的子树。

已绘制区域是一个 区域(矩形集合),表示某个 容器根自首次 被观察以来所有已绘制部分的累积。

容器计时 API 提供关于容器根何时被绘制到屏幕上的计时信息。

5. PerformanceContainerTiming 接口

[Exposed=Window]
interface PerformanceContainerTiming : PerformanceEntry {
    readonly attribute DOMString identifier;
    readonly attribute DOMRectReadOnly intersectionRect;
    readonly attribute unsigned long long size;
    readonly attribute DOMHighResTimeStamp firstRenderTime;
    readonly attribute Element? lastPaintedElement;
    readonly attribute Element? rootElement;
};

PerformanceContainerTiming includes PaintTimingMixin;
每个 PerformanceContainerTiming 对象都有这些关联概念:

entryType 属性的 getter 必须返回 DOMString "container"

name 属性的 getter 必须返回空字符串。

duration 属性必须返回 0。

startTime 属性的 getter 必须返回 thisrenderTime 的值。

identifier 属性 必须返回 thisidentifier 的值。

intersectionRect 属性必须返回 thisintersectionRect 的值。

size 属性必须返回 thissize 的值。

firstRenderTime 属性必须返回 thisfirstRenderTime 的值。

lastPaintedElement 属性必须返回 thislastPaintedElement 的值。

rootElement 属性 必须返回 thisrootElement 的值。

注: 用户代理需要维护 容器根记录映射,以确保已移除的内容不会 引入内存泄漏。特别是,它可以将条目的生命周期绑定到指向这些 Element 的弱指针,从而在这些 Element 被移除之后的某个时候对其进行清理。由于该映射不会暴露给 Web 开发者,因此这不会暴露垃圾收集计时。

6. 处理模型

注: 实现容器计时 API 的用户代理 需要在 supportedEntryTypes 中为 Window 上下文包含 "container"。 这允许开发者检测对容器计时的支持。

6.1. 每文档状态

对于每个 Document, 用户代理必须维护一个 容器根记录映射,该映射将 容器根 Element 映射到 Container Timing Record 对象。

6.2. Element 接口的扩展

一旦 [DOM] 规范被修改,本节将被移除。

我们按如下方式扩展 Element 接口:

partial interface Element {
    [CEReactions] attribute DOMString containertiming;
    [CEReactions] attribute DOMString? containertimingIgnore;
};
containertiming 属性是一个 DOMString, 用于将元素标识为一个 容器根。该值会成为对应 identifier 在相应 PerformanceContainerTiming 条目中的值。

containertiming-ignore 属性在 存在时,会将该元素及其后代标记为一个 被忽略的子树,该子树不应对祖先 容器根 的容器计时 测量作出贡献。

6.3. Container Timing Record

本规范定义了处理模型使用的内部数据结构:

一个 Container Timing Record 具有这些关联 概念:
要在给定 paint timing info paintTimingInfo 和一个 DOMString identifier 的情况下创建一个 Container Timing Record,执行以下步骤:
  1. record 为一个新的 Container Timing Record

  2. recordpaintTimingInfo 设置为 paintTimingInfo

  3. recordidentifier 设置为 identifier

  4. 返回 record

6.4. 注册容器根

当一个带有 [^containertiming^] 内容属性的 Element 被连接到文档时:

  1. 用户代理必须将该元素注册为一个 容器根

  2. 用户代理必须为该 容器根 初始化一个空的 已绘制区域

  3. 用户代理必须跟踪 容器根子树内的所有绘制操作,但排除任何 被忽略的子树

6.5. 处理用于容器计时的元素已绘制

当一个元素被绘制且用户代理需要处理容器计时更新时,给定一个 Document document、一个 paint timing info paintTimingInfo、一个 Element element 和一个 DOMRectReadOnly intersectionRect,执行以下步骤:
  1. 如果 element对容器计时有贡献,则返回。

  2. containerRoot 为在给定 element 的情况下执行获取 容器根元素的结果。

  3. 如果 containerRoot 为 null,则返回。

  4. recorddocument容器根 记录映射中针对 containerRoot 的条目。如果不存在这样的条目,则将 record 设置为在给定 paintTimingInfocontainerRoot 的 [^containertiming^] 内容属性值的情况下执行创建一个 Container Timing Record的结果; 然后将 (containerRootrecord) 添加到 document容器根记录映射中。

  5. enclosingRectintersectionRect 的最小包围矩形。

  6. 对于 record,在给定 documentcontainerRootelementenclosingRectpaintTimingInfo 的情况下, 可能更新最后的新绘制 区域

  7. 将该 Document 标记为具有待处理的容器计时更改。

注: 此算法会针对每个发生绘制的图像或文本 节点调用。交叉矩形应使用 交叉矩形算法计算,以元素作为目标、 视口作为根,并与视觉视口求交。对于文本节点,交叉矩形是包含 所属文本节点集合中所有文本节点边框盒的 最小矩形,并与视觉视口求交。

6.6. 发出容器计时条目

当被要求为一个 Document document 发出容器计时条目时,执行以下步骤。此操作应在所有绘制 操作已处理之后每帧调用一次:
  1. 如果 document 没有待处理的容器计时更改,则返回。

  2. document容器根记录映射中的 每个 containerRootrecord依次执行

    1. 如果 recordhasPendingChanges 为 false,则继续。

    2. 在给定 recordcontainerRoot 的情况下,创建一个 Container Timing 条目

    3. recordhasPendingChanges 设置为 false。

  3. 将该 Document 标记为不再具有待处理的容器计时更改。

注: 与一些其他可能为每个已绘制元素发出 多个条目的绘制计时 API 不同,容器计时会为每个 容器根累积已绘制区域,并且每帧每个容器根最多发出 一个 PerformanceContainerTiming 条目。这种批处理方式更高效,并能提供对容器绘制状态的整体视图。

6.7. 获取父容器根 Element

要在给定一个 Element element 的情况下获取 父容器根元素,执行以下步骤:
  1. parentelement 的 parentElement。

  2. 如果 parent 为 null,则返回 null。

  3. 返回在给定 parent 的情况下执行获取容器根元素的结果。

6.8. 对容器计时有贡献

如果以下条件均为 true,则一个 Element 对某个容器根的容器计时有贡献

要确定一个 Element element 是否对一个 容器根 containerRoot 的容器计时有贡献:
  1. 如果 element 为 null,则返回 false。

  2. 如果 element 在影子树中,则返回 false。

  3. 如果 element 不是 containerRoot 的后代,则返回 false。

  4. 如果 element 位于某个 被忽略的子树内,则返回 false。

  5. 返回 true。

6.9. 获取容器根 Element

要在给定一个 Element element 的情况下获取 容器根元素,执行以下步骤:
  1. 如果 element 为 null,则返回 null。

  2. 如果 element 的 [^containertiming^] 内容属性存在,则返回 element

  3. 如果 element 的 parentElement 不为 null,则返回在给定 element 的 parentElement 的情况下执行获取 容器根元素的结果。

  4. 返回 null。

6.10. 可能更新最后的新绘制区域

要在给定一个 Container Timing Record record、一个 Document document、一个 容器根 Element containerRoot、一个 Element element、一个 DOMRectReadOnly enclosingRect 和一个 paint timing info paintTimingInfo 的情况下,为 record 可能更新最后的新绘制区域, 执行以下步骤:
  1. paintedRegionrecordpaintedRegion

  2. 如果 paintedRegion 完全包含 enclosingRect,则返回。

  3. recordpaintedRegion 设置为 paintedRegionenclosingRect 的并集。

  4. recordlastNewPaintedAreaPaintTimingInfo 设置为 paintTimingInfo

  5. recordlastNewPaintedAreaElement 设置为 element

  6. recordhasPendingChanges 设置为 true。

  7. 如果 containerRoot 的 [^containertiming-ignore^] 内容属性存在,则返回。

  8. parentContainerRoot 为在给定 containerRoot 的情况下执行获取父容器根元素的结果。

  9. 如果 parentContainerRoot 为 null,则返回。

  10. parentRecorddocument容器根 记录映射中针对 parentContainerRoot 的条目。如果不存在这样的条目,则将 parentRecord 设置为在给定 paintTimingInfoparentContainerRoot 的 [^containertiming^] 内容属性值的情况下执行创建一个 Container Timing Record的结果; 然后将 (parentContainerRootparentRecord) 添加到 document容器根记录映射中。

  11. 对于 parentRecord,在给定 documentparentContainerRootelementenclosingRectpaintTimingInfo 的情况下,可能更新最后的新绘制 区域

注: 此算法会报告对已绘制 区域的任何更改,而不考虑大小。即使已绘制区域发生 1 像素的变化,也会导致一个新的 PerformanceContainerTiming 条目被排入队列。希望过滤掉小变化的开发者可以通过比较各条目之间的 size 值来实现。

6.11. 创建容器计时条目

为了在给定一个 Container Timing Record record 和一个 容器根 Element containerRoot 的情况下创建一个 Container Timing 条目, 用户代理必须执行以下步骤:
  1. entry 为一个新的 PerformanceContainerTiming 条目,并将其:

  2. entry 排入 PerformanceEntry 队列

7. 安全与隐私考量

7.1. 跨源限制

该 API 遵守跨源边界:

7.2. 信息暴露

此 API 提供的大多数信息都已经可以通过现有 API 进行估算:

该 API 不会暴露:

7.3. 计时攻击

该 API 使用 DOMHighResTimeStamp, 出于安全目的,它可能受到分辨率限制,这与其他 Performance API 一致。

7.4. 隐私考量

该 API 不会:

8. 致谢

非常感谢以下人士提供的宝贵反馈和建议:

一致性

文档 约定

一致性要求通过描述性断言 与 RFC 2119 术语的组合来表达。 在本文档规范性部分中,关键词 “MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、 “MAY” 和 “OPTIONAL” 应按 RFC 2119 中的描述进行解释。 不过,为了可读性, 这些词在本规范中并不全都以大写字母出现。

本规范的所有文本都是规范性的, 明确标记为非规范性的章节、示例和注释除外。[RFC2119]

本规范中的示例会用 “for example” 一词引入, 或者通过 class="example" 与规范性文本分隔开, 如下所示:

这是一个资料性示例的例子。

资料性注释以 “Note” 一词开头, 并通过 class="note" 与规范性文本分隔开, 如下所示:

Note,这是一个资料性注释。

索引

本规范定义的 术语

由引用定义的 术语

参考文献

规范性参考文献

[DOM]
Anne van Kesteren。DOM Standard。现行标准。 URL:https://dom.spec.whatwg.org/
[GEOMETRY-1]
Sebastian Zartner; Yehonatan Daniv。Geometry Interfaces Module Level 1。URL:https://drafts.csswg.org/geometry/
[HR-TIME-3]
Yoav Weiss。High Resolution Time。URL:https://w3c.github.io/hr-time/
[HTML]
Anne van Kesteren; et al。HTML Standard。 现行标准。URL:https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola。Infra Standard。现行标准。URL:https://infra.spec.whatwg.org/
[PAINT-TIMING]
Ian Clelland; Noam Rosenthal。Paint Timing。 URL:https://w3c.github.io/paint-timing/
[PERFORMANCE-TIMELINE]
Nicolas Pena Moreno。Performance Timeline。URL:https://w3c.github.io/performance-timeline/
[RFC2119]
S. Bradner。Key words for use in RFCs to Indicate Requirement Levels。1997 年 3 月。最佳当前实践。URL:https://datatracker.ietf.org/doc/html/rfc2119
[WEBIDL]
Edgar Chen; Timothy Gu。Web IDL Standard。现行 标准。URL:https://webidl.spec.whatwg.org/

资料性参考文献

[INTERSECTION-OBSERVER]
Stefan Zager; Emilio Cobos Álvarez; Traian Captan。Intersection Observer。URL:https://w3c.github.io/IntersectionObserver/

IDL 索引

[Exposed=Window]
interface PerformanceContainerTiming : PerformanceEntry {
    readonly attribute DOMString identifier;
    readonly attribute DOMRectReadOnly intersectionRect;
    readonly attribute unsigned long long size;
    readonly attribute DOMHighResTimeStamp firstRenderTime;
    readonly attribute Element? lastPaintedElement;
    readonly attribute Element? rootElement;
};

PerformanceContainerTiming includes PaintTimingMixin;

partial interface Element {
    [CEReactions] attribute DOMString containertiming;
    [CEReactions] attribute DOMString? containertimingIgnore;
};