1. 介绍
当有大量 Web 应用(以及标签页)运行时,关键资源如内存、CPU、电池、网络等很容易被过度占用,导致糟糕的终端用户体验。应用生命周期是现代操作系统管理资源的关键方式之一。一个平台如要支持应用生命周期,需要:
-
向开发者提供生命周期状态变更的信号
-
提供兼容生命周期的 API,使关键能力在应用被后台运行或停止时仍能工作。
本提案试图定义网页的生命周期,并增加必要的扩展,使 Web 应用能够响应用户代理常见的两个重要生命周期事件:
-
标签页丢弃(节省内存)
-
CPU 挂起(节省电池、数据、CPU)
2. 页面生命周期状态
本规范定义了网页生命周期,并增加扩展,使 Web 应用能够响应用户代理常见的两个重要生命周期事件:
-
CPU 挂起(节省电池、数据、CPU)
-
标签页丢弃(节省内存)
本规范正式定义了两个新的生命周期状态以支持上述需求:
-
冻结(Frozen):用于 CPU 挂起的生命周期状态。表示已在
Document
的 浏览上下文上调用了 freeze steps 算法。通常隐藏页面会被冻结以节约资源。 -
丢弃(Discarded):表示在
Document
的 浏览上下文上调用了 discard 算法。通常被冻结的 frame 会被移到丢弃状态以节约资源。
TODO(panicker):插入生命周期状态图
3. API
页面生命周期涉及以下补充:
partial interface Document {attribute EventHandler onfreeze ;attribute EventHandler onresume ;readonly attribute boolean wasDiscarded ; };
onfreeze
和 onresume
属性是 事件处理 IDL 属性,分别用于 freeze
和 resume
事件。
wasDiscarded
属性的 getter 应返回该 Document
的 discarded 布尔值。
注意: 这些 API 添加在 Document
上,而不是 Window
,是为了与
Page Visibility API 保持一致;我们预计这些 API 会与现有的 Page Visibility API 一起使用。[PAGE-VISIBILITY]
注意: 此外 clientId
和
discardedClientId
将被添加到 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 在加载后会立即被冻结。 --> < iframe allow = "execution-while-not-rendered 'none'" src = "subframe.html" style = "display:none" ></ iframe >
Document
document 运行 更新文档冻结性步骤:
-
如果 document 的 readiness 不为 "
complete
",返回。 -
令 frozenness 为 false。
-
令 auto resume media 为 false。
-
如果 document 不被允许使用 "
execution-while-not-rendered
" 特性:-
如果 element 未被 渲染,则设 frozenness 为 true。
-
-
否则如果 document 不被允许使用 "
execution-while-out-of-viewport
" 特性:-
如果 element 未与 视口相交(参见 计算目标元素与根的交集),则设 frozenness 为 true,设 auto resume media 为 true。
-
-
如果 frozenness 不等于 document 的 冻结性状态,更改文档冻结性,参数为 document、frozenness 和 auto resume media。
5. 处理模型
5.1. 对 worklet 和 worker 行为的修改
任何 worklet agent,如果其唯一 realm 的 全局对象的 所属文档被冻结,则该 agent 必须变为 阻塞状态。 任何 dedicated worker agent,如果其唯一 realm 的 全局对象的 所属文档非空且被冻结,则该 agent 必须变为 阻塞状态。
DedicatedWorkerGlobalScope
workerGlobalScope 的 所属文档:
-
如果 workerGlobalScope 的 owner set 仅包含一个
Document
document,则返回 document。 -
如果 workerGlobalScope 的 owner set 仅包含一个
DedicatedWorkerGlobalScope
parentWorkerGlobalScope,则返回 parentWorkerGlobalScope 的 所属文档。 -
返回 null。
注意: DedicatedWorkerGlobalScope
的 owner set 始终只有一个条目。
5.2. 对 HTML 标准的修改
5.2.1. 卸载文档和历史遍历
当文档进入或离开 bfcache(前进后退缓存) 时,其 冻结性状态会分别变为 true 和 false。
-
在 卸载文档算法中,Step #5 后,如果
persisted
属性为 true(即进入 bfcache),则 更改文档冻结性,参数为 document 和 true。 -
在 历史遍历算法中,Step #4.6.4 前,如果
persisted
属性为 true(即离开 bfcache),则 更改文档冻结性,参数为 document 和 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 前添加:
如果浏览上下文之前已丢弃,则将 Document
的 discarded 布尔值设为
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步中的变量。
-
令 matchedClientData 为一个新的 列表。
在第2.5.1步之前插入
-
令 lifecycleState 为用 client 运行 获取 Client 生命周期状态 的结果。
在第5.3.1步将 lifecycleState 添加到列表
-
令 windowData 为 «[ "client" → client, "ancestorOriginsList" → 新的 列表, "lifecycleState" → lifecycleState ]»。
在第5.4步将 lifecycleState 添加到 matchedClientData
-
将 «[ "client" → client, "lifecycleState" → lifecycleState ]» 添加到 matchedClientData。
在第6.2步将 windowData 的 lifecycleState 传入 Create Window Client 算法
-
令 windowClient 为用 windowData["
client
"]、windowData["frameType
"]、windowData["visibilityState
"]、windowData["focusState
"]、windowData["ancestorOriginsList
"] 和 windowData["lifecycleState
"] 作为参数运行 Create Window Client 算法的结果。
调整第6.3步
-
遍历 matchedClientData 中的每个 clientData:
-
令 clientObject 为用 clientData["
client
"] 和 clientData["lifecycleState
"] 作为参数运行 Create Client 算法的结果。 -
追加 clientObject 到 clientObjects。
-
5.3.1.3.
openWindow(url)
在第7.5步之前插入
-
令 lifecycleState 为用 this 的关联 service worker client 运行 获取 Client 生命周期状态 的结果。
调整第7.8.2步以提供 lifecycleState
-
令 client 为用 newContext 的
Window
对象的 环境设置对象、frameType、visibilityState、focusState、ancestorOriginsList 和 lifecycleState 作为参数运行 Create Window Client 的结果。
5.3.2. 算法
5.3.2.1. 获取 Client 生命周期状态
附加如下算法:
- 输入
-
client,一个 service worker client
- 输出
-
state,一个字符串
5.3.2.2. 创建 Client
在输入参数后追加 lifecycleState,一个字符串
在输出第2步后追加:
-
将 clientObject 的 生命周期状态设为 lifecycleState。
5.3.2.3. 创建 Window Client
在输入参数后追加 lifecycleState,一个字符串
在输出第5步后追加:
-
将 windowClient 的 生命周期状态设为 lifecycleState。
5.4. 页面生命周期处理模型
5.4.1. 冻结状态(FROZENNESS)
一个文档可以处于以下 冻结性(FROZENNESS) 状态之一:-
true:文档处于 冻结 状态,与该文档关联的所有任务都不会运行
-
false:文档处于 未冻结 状态,与该文档关联的任务会正常运行
注意: 根据 更改顶级文档冻结性算法,当顶级浏览上下文的 Document 更改其 冻结性状态时,其所有子浏览上下文的文档也会更改冻结性(与顶级一致)。
UA 在某些情况下可能会用 true 执行 更改顶级文档冻结性算法。例如,当顶级浏览上下文处于后台或隐藏状态且宽限期已过时,UA 可用 true 执行该算法以节省资源并保证前台体验质量。具体例子:
-
在移动端 Chrome 中,后台至少5分钟的标签页可能会被 冻结,以节约电池和数据。
-
在桌面端 Chrome 中,长时间未使用且对用户不重要的后台标签页可能会被 丢弃,以节省内存
注意: 用户正在使用的后台标签页(如正在播放音频)通常不会被 冻结或 丢弃。
注意: Chrome 使用的具体启发式与排除列表详见 此文档。
当用户重新访问该浏览上下文时,UA 通常会用 false 执行 更改顶级文档冻结性。此外,当资源充足时,UA 还可在后台周期性地用 false 执行该算法。
5.4.2. 文档冻结性的变更
Document
doc 执行 冻结步骤,布尔型参数 auto resume frozen media:
-
设 doc 的 冻结性状态为 true。
-
触发名为
freeze
的事件,目标为 doc。 -
令 elements 为 doc 的所有 媒体元素,这些元素为 包含 shadow 的所有后代,顺序为 包含 shadow 的树顺序。
-
遍历 elements 中每个 element:
-
若 element 的
paused
属性为 false,则:-
设 element 的 resume frozen flag 为 auto resume frozen media。
-
对 element 执行 媒体暂停。
-
注意: 这里特意先赋值冻结性状态再触发事件。
-
Document
doc 执行 恢复步骤:
-
令 elements 为 doc 的所有 媒体元素,这些元素为 包含 shadow 的所有后代,顺序为 包含 shadow 的树顺序。
-
遍历 elements 中每个 element:
-
若 elements 的 resume frozen flag 为 true:
-
设 elements 的 resume frozen flag 为 false。
-
对 element 执行 媒体播放。
-
-
-
-
触发名为
resume
的事件,目标为 doc。 -
设 doc 的 冻结性状态为 false。
注意: 这里特意在触发事件后才赋值冻结性状态。
5.4.3. 丢弃(Discarding)
每个 Document 都有一个 discarded 布尔值,初始为 false。要 丢弃 浏览上下文,销毁浏览上下文,并记录其及所有后代被销毁的原因是丢弃。
注意: 丢弃通常用来回收系统内存,当内存等资源不足时使用。销毁浏览上下文则是用户离开页面等正常流程。
浏览上下文在后台且文档处于 VisibilityState hidden 状态时,可在资源压力(如内存不足)下被 丢弃,具体例子:
-
在桌面端 Chrome 中,长时间未使用且对用户不重要的后台标签页可能被 丢弃,以节省内存
注意: 正在为用户执行任务的后台标签页(如播放音频)通常不会被 丢弃。
注意: Chrome 使用的具体启发式与排除列表详见 此文档。
当 顶级浏览上下文(浏览器标签页)由于资源压力(或如进程崩溃等异常)被 丢弃后,用户再次访问该标签页时,则该 Document
的 discarded 布尔值会为
true(见 § 5.2.5 初始化文档)。
6. 致谢
特别感谢 Dave Tapuska、 Fadi Meawad、 Ojan Vafai、 Olli Pettay、 Philip Walton 以及 Todd Reifsteck 提供了技术建议和意见,促进了本规范的改进。