页面生命周期

社区组报告草案

本版本:
https://wicg.github.io/page-lifecycle/
议题跟踪:
GitHub
编辑:
(Google)
前编辑:
(Google)
(Google)

摘要

本文档定义了一个支持浏览器管理网页生命周期的 API。

本文件状态

本规范由 Web Platform Incubator Community Group 发布。 它不是 W3C 标准,也不属于 W3C 标准轨道。 请注意,根据 W3C Community Contributor License Agreement (CLA) 有有限的选择退出权以及其它相关条件。 了解更多 W3C Community and Business Groups 信息。

1. 介绍

当有大量 Web 应用(以及标签页)运行时,关键资源如内存、CPU、电池、网络等很容易被过度占用,导致糟糕的终端用户体验。应用生命周期是现代操作系统管理资源的关键方式之一。

一个平台如要支持应用生命周期,需要:

本提案试图定义网页的生命周期,并增加必要的扩展,使 Web 应用能够响应用户代理常见的两个重要生命周期事件:

2. 页面生命周期状态

本规范定义了网页生命周期,并增加扩展,使 Web 应用能够响应用户代理常见的两个重要生命周期事件:

本规范正式定义了两个新的生命周期状态以支持上述需求:

TODO(panicker):插入生命周期状态图

3. API

页面生命周期涉及以下补充:

partial interface Document {
    attribute EventHandler onfreeze;
    attribute EventHandler onresume;
    readonly attribute boolean wasDiscarded;
};

onfreezeonresume 属性是 事件处理 IDL 属性,分别用于 freezeresume 事件。

wasDiscarded 属性的 getter 应返回该 Documentdiscarded 布尔值。

注意: 这些 API 添加在 Document 上,而不是 Window,是为了与 Page Visibility API 保持一致;我们预计这些 API 会与现有的 Page Visibility API 一起使用。[PAGE-VISIBILITY]

注意: 此外 clientIddiscardedClientId 将被添加到 Window, 用于支持用户重新访问丢弃页面导致页面重新加载时恢复视图状态。我们预计这些属性会用于响应这些事件的代码。

3.1. 用法示例

冻结和恢复的处理示例:

const prepareForFreeze = () => {
  // 关闭所有打开的 IndexedDB 连接。
  // 释放所有 Web 锁。
  // 停止定时器或轮询。
};

const reInitializeApp = () => {
  // 恢复 IndexedDB 连接。
  // 重新获取所需的 Web 锁。
  // 重新启动定时器或轮询。
};

document.addEventListener('freeze', prepareForFreeze);
document.addEventListener('resume', reInitializeApp);

丢弃后恢复视图状态的示例: 用户可能会为同一个应用和 URL 打开多个标签页。如果它们都在后台且都被丢弃,应用需要区分这两个标签页以恢复正确的状态。Window 上的 clientId 和 lastClientId 可用于此目的。

// 将状态持久化到 IndexedDB,确保在记录中设置当前 self.clientId,
// 这样如果标签页丢弃后需要重新加载,可以通过 getPersistedState() 检索。
const persistState = async (state) => {
  const record = {...state, cliendId: self.clientId};

  // 将 record 持久化到 IndexedDB 或 SessionStorage...
}

// 根据传入的 clientId 从 IndexedDB 检索状态记录。
const getPersistedState = async (clientId) => {
  // 在 IndexedDB 中查找记录...
};

// 如果标签页之前被丢弃,通过 self.lastClientId 获取丢弃标签页的状态。
if (document.wasDiscarded) {
  getPersistedState(self.lastClientId);
}

4. 特性策略

控制嵌套浏览上下文的执行状态对于浏览上下文来说有助于控制用户体验。应用可能希望当文档未被渲染或未与视口相交时,所有嵌套浏览上下文(包括正在播放的视频、音频)都停止执行。停止所有执行可提升 CPU 利用率。为满足这些需求,定义了两个特性策略:

"execution-while-not-rendered" 策略控制对于 嵌套浏览上下文,其 浏览上下文容器未被 渲染时是否应继续执行任务。

"execution-while-out-of-viewport" 策略控制对于 嵌套浏览上下文,其 浏览上下文容器未与 视口相交时是否应继续执行任务,根据 计算目标元素与根的交集

§ 5.2 对 HTML 标准的修改通过在满足如下条件时将文档(及其后代)置于 冻结 状态来实现上述功能:

如果不满足上述条件,则文档将处于 未冻结 状态。

<!-- iframe 在加载后会立即被冻结。 -->

<iframe allow="execution-while-not-rendered 'none'"
  src="subframe.html" style="display:none"></iframe>
要为 Document document 运行 更新文档冻结性步骤
  1. 如果 document浏览上下文不是 嵌套浏览上下文,返回。

  2. 如果 documentreadiness 不为 "complete",返回。

  3. elementdocument浏览上下文浏览上下文容器

  4. frozenness 为 false。

  5. auto resume media 为 false。

  6. 如果 document 不被允许使用 "execution-while-not-rendered" 特性:

    1. 如果 element 未被 渲染,则设 frozenness 为 true。

  7. 否则如果 document 不被允许使用 "execution-while-out-of-viewport" 特性:

    1. 如果 element 未与 视口相交(参见 计算目标元素与根的交集),则设 frozenness 为 true,设 auto resume media 为 true。

  8. 如果 frozenness 不等于 document冻结性状态,更改文档冻结性,参数为 documentfrozennessauto resume media

5. 处理模型

5.1. 对 worklet 和 worker 行为的修改

任何 worklet agent,如果其唯一 realm全局对象所属文档被冻结,则该 agent 必须变为 阻塞状态。 任何 dedicated worker agent,如果其唯一 realm全局对象所属文档非空且被冻结,则该 agent 必须变为 阻塞状态。

要确定 DedicatedWorkerGlobalScope workerGlobalScope所属文档
  1. 如果 workerGlobalScopeowner set 仅包含一个 Document document,则返回 document

  2. 如果 workerGlobalScopeowner set 仅包含一个 DedicatedWorkerGlobalScope parentWorkerGlobalScope,则返回 parentWorkerGlobalScope所属文档

  3. 返回 null。

注意: DedicatedWorkerGlobalScopeowner set 始终只有一个条目。

5.2. 对 HTML 标准的修改

5.2.1. 卸载文档历史遍历

当文档进入或离开 bfcache(前进后退缓存) 时,其 冻结性状态会分别变为 true 和 false。

5.2.2. 事件循环:定义

替换:任务(task)可运行条件为其文档为 null 或 完全激活

新条件:任务可运行条件为其文档为 null 或 完全激活,并且 未冻结

5.2.3. 事件循环:处理模型

更新渲染步骤 #11 后,添加如下步骤:

docs 中每个 完全激活Document doc,运行 更新文档冻结性步骤,参数为 doc

5.2.4. 丢弃浏览上下文

将“discard”(丢弃)概念(包括浏览上下文和文档)重命名为“destroy”(销毁)。这样可以将“discarded”(已丢弃)术语用于用户可见的 wasDiscarded 属性。

5.2.5. 初始化文档

在 Step #3 前添加:

如果浏览上下文之前已丢弃,则将 Documentdiscarded 布尔值设为 true。

5.2.6. iframe 加载事件步骤

在 Step #5 后添加:

child document 运行 更新文档冻结性步骤

5.2.7. HTMLMediaElement

每个 HTMLMediaElement 都有一个 resume frozen flag,初始值为 false。

5.2.8. 消息发送

添加备注:

注意: 当向被冻结的 Window 发送消息时,消息任务源上排队的事件不会运行,直到该 Window解冻。如果事件队列过多,用户代理可能会 丢弃Window浏览上下文(作为资源压力下允许丢弃的一部分)。

5.3. 对 Service Worker 标准的修改

5.3.1. Client

partial interface Client {
    readonly attribute ClientLifecycleState lifecycleState;
};

enum ClientLifecycleState {
    "active",
    "frozen"
};

Client 对象有一个关联的 生命周期状态,该状态是 ClientLifecycleState 枚举值之一。

5.3.1.1. lifecycleState

lifecycleState 的 getter 步骤为返回 this生命周期状态

5.3.1.2. matchAll(options)

重命名第4步中的变量。

  1. matchedClientData 为一个新的 列表

在第2.5.1步之前插入

  1. lifecycleState 为用 client 运行 获取 Client 生命周期状态 的结果。

在第5.3.1步将 lifecycleState 添加到列表

  1. windowData 为 «[ "client" → client, "ancestorOriginsList" → 新的 列表, "lifecycleState" → lifecycleState ]»。

在第5.4步将 lifecycleState 添加到 matchedClientData

  1. 将 «[ "client" → client, "lifecycleState" → lifecycleState ]» 添加到 matchedClientData

在第6.2步将 windowData 的 lifecycleState 传入 Create Window Client 算法

  1. windowClient 为用 windowData["client"]、windowData["frameType"]、windowData["visibilityState"]、windowData["focusState"]、windowData["ancestorOriginsList"] 和 windowData["lifecycleState"] 作为参数运行 Create Window Client 算法的结果。

调整第6.3步

  1. 遍历 matchedClientData 中的每个 clientData

    1. clientObject 为用 clientData["client"] 和 clientData["lifecycleState"] 作为参数运行 Create Client 算法的结果。

    2. 追加 clientObjectclientObjects

5.3.1.3. openWindow(url)

在第7.5步之前插入

  1. lifecycleState 为用 this 的关联 service worker client 运行 获取 Client 生命周期状态 的结果。

调整第7.8.2步以提供 lifecycleState

  1. client 为用 newContextWindow 对象的 环境设置对象frameTypevisibilityStatefocusStateancestorOriginsListlifecycleState 作为参数运行 Create Window Client 的结果。

5.3.2. 算法

5.3.2.1. 获取 Client 生命周期状态

附加如下算法:

输入

client,一个 service worker client

输出

state,一个字符串

  1. state"active"

  2. 如果 client全局对象所属文档被冻结,则将 state 设为 "frozen"

  3. 返回 state

5.3.2.2. 创建 Client

在输入参数后追加 lifecycleState,一个字符串

在输出第2步后追加:

  1. clientObject生命周期状态设为 lifecycleState

5.3.2.3. 创建 Window Client

在输入参数后追加 lifecycleState,一个字符串

在输出第5步后追加:

  1. windowClient生命周期状态设为 lifecycleState

5.4. 页面生命周期处理模型

5.4.1. 冻结状态(FROZENNESS)

一个文档可以处于以下 冻结性(FROZENNESS) 状态之一:

UA 在某些情况下可能会用 true 执行 更改顶级文档冻结性算法。例如,当顶级浏览上下文处于后台或隐藏状态且宽限期已过时,UA 可用 true 执行该算法以节省资源并保证前台体验质量。具体例子:

当用户重新访问该浏览上下文时,UA 通常会用 false 执行 更改顶级文档冻结性。此外,当资源充足时,UA 还可在后台周期性地用 false 执行该算法。

5.4.2. 文档冻结性的变更

更改顶级文档冻结性,给定 Document topLevelDoc 和布尔型 冻结性状态 frozenness
  1. 断言:doc浏览上下文顶级浏览上下文

  2. topLevelDocfrozenness 和 false 执行 更改文档冻结性

  3. descendantsdoc所有子浏览上下文列表

  4. 遍历 descendants 中每个 浏览上下文 b

    1. descendantDocumentb活动文档

    2. descendantDocumentfrozenness 和 false 执行 更改文档冻结性

更改文档冻结性,给定 Document doc,布尔型 冻结性状态 frozenness,以及布尔型 auto resume frozen media
  1. frozenness 为 true,则用 docauto resume frozen media 执行 冻结步骤

  2. 否则,执行 恢复步骤,参数为 doc

要为 Document doc 执行 冻结步骤,布尔型参数 auto resume frozen media
  1. doc冻结性状态为 true。

  2. 触发名为 freeze 的事件,目标为 doc

  3. elementsdoc 的所有 媒体元素,这些元素为 包含 shadow 的所有后代,顺序为 包含 shadow 的树顺序

  4. 遍历 elements 中每个 element

    1. elementpaused 属性为 false,则:

      1. elementresume frozen flagauto resume frozen media

      2. element 执行 媒体暂停

    注意: 这里特意先赋值冻结性状态再触发事件。

要为 Document doc 执行 恢复步骤
  1. elementsdoc 的所有 媒体元素,这些元素为 包含 shadow 的所有后代,顺序为 包含 shadow 的树顺序

    1. 遍历 elements 中每个 element

      1. elementsresume frozen flag 为 true:

        1. elementsresume frozen flag 为 false。

        2. element 执行 媒体播放

  2. 触发名为 resume 的事件,目标为 doc

  3. doc冻结性状态为 false。

    注意: 这里特意在触发事件后才赋值冻结性状态。

5.4.3. 丢弃(Discarding)

每个 Document 都有一个 discarded 布尔值,初始为 false。

丢弃 浏览上下文,销毁浏览上下文,并记录其及所有后代被销毁的原因是丢弃。

注意: 丢弃通常用来回收系统内存,当内存等资源不足时使用。销毁浏览上下文则是用户离开页面等正常流程。

浏览上下文在后台且文档处于 VisibilityState hidden 状态时,可在资源压力(如内存不足)下被 丢弃,具体例子:

顶级浏览上下文(浏览器标签页)由于资源压力(或如进程崩溃等异常)被 丢弃后,用户再次访问该标签页时,则该 Documentdiscarded 布尔值会为 true(见 § 5.2.5 初始化文档)。

6. 致谢

特别感谢 Dave Tapuska、 Fadi Meawad、 Ojan Vafai、 Olli Pettay、 Philip Walton 以及 Todd Reifsteck 提供了技术建议和意见,促进了本规范的改进。

一致性

文档约定

一致性要求通过描述性断言和 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" 与规范性文本区分,如下所示:

注意,这是一个说明性备注。

一致性算法

以祈使句表达的算法中的要求(如“去除所有前导空格字符”或“返回 false 并中止这些步骤”) 应按引入算法时所用关键词(如“must”、“should”、“may”等)解释其含义。

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

索引

本规范定义的术语

引用定义的术语

参考文献

规范性引用

[CSS21]
Bert Bos 等. Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification. 链接: https://drafts.csswg.org/css2/
[DOM]
Anne van Kesteren. DOM 标准. Living Standard. 链接: https://dom.spec.whatwg.org/
[HTML]
Anne van Kesteren 等. HTML 标准. Living Standard. 链接: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 标准. Living Standard. 链接: https://infra.spec.whatwg.org/
[PERMISSIONS-POLICY-1]
Ian Clelland. Permissions Policy. 链接: https://w3c.github.io/webappsec-permissions-policy/
[RFC2119]
S. Bradner. 用于 RFC 中指示需求等级的关键词. 1997年3月. 最佳当前实践. 链接: https://datatracker.ietf.org/doc/html/rfc2119
[SERVICE-WORKERS-1]
Alex Russell 等. Service Workers 1. 链接: https://w3c.github.io/ServiceWorker/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL 标准. Living Standard. 链接: https://webidl.spec.whatwg.org/

说明性引用

[PAGE-VISIBILITY]
Jatinder Mann; Arvind Jain. 页面可见性(第二版). 2013年10月29日. REC. 链接: https://www.w3.org/TR/page-visibility/

IDL 索引

partial interface Document {
    attribute EventHandler onfreeze;
    attribute EventHandler onresume;
    readonly attribute boolean wasDiscarded;
};

partial interface Client {
    readonly attribute ClientLifecycleState lifecycleState;
};

enum ClientLifecycleState {
    "active",
    "frozen"
};