1. はじめに
多数のWebアプリ(およびタブ)が動作していると、メモリ・CPU・バッテリー・ネットワークなどの重要なリソースがすぐに使い過ぎとなり、ユーザー体験が悪化します。アプリケーションのライフサイクルは、現代のOSがリソースを管理する主要な手法です。プラットフォームがアプリケーションライフサイクルをサポートするには、以下が必要です:
-
ライフサイクル状態間の遷移について、開発者へ通知(シグナル)を提供すること
-
アプリがバックグラウンドや停止状態でも主要機能が動作できる、ライフサイクル互換APIを提供すること
この提案は、Webページのライフサイクルが何かを定義し、Webアプリケーションがユーザーエージェントによってよく行われる2つの重要なライフサイクルイベントに応答できる拡張を追加することを目指しています:
-
タブの破棄(メモリ節約のため)
-
CPUの一時停止(バッテリー、データ、CPU節約のため)
2. ページライフサイクル状態
この仕様はWebページのライフサイクルが何かを定義し、Webアプリケーションがユーザーエージェントによってよく行われる2つの重要なライフサイクルイベントに応答できる拡張を追加します:
-
CPUの一時停止(バッテリー・データ・CPUの節約)
-
タブの破棄(メモリ節約のため)
この仕様では、上記をサポートするために2つの新しいライフサイクル状態を正式化します:
-
Frozen(凍結): CPU一時停止のためのライフサイクル状態。これは、freeze steps アルゴリズムが
Documentの 閲覧コンテキストに対して呼び出されたことを意味します。通常、HIDDENページはリソース節約のため凍結状態になります。 -
Discarded(破棄): discard アルゴリズムが
Documentの 閲覧コンテキストに対して呼び出されたことを意味します。通常、凍結されたフレームはリソース節約のため破棄状態へ移行します。
TODO(panicker): 図を挿入予定
3. API
ページライフサイクルには以下の追加が含まれます:
partial interface Document {attribute EventHandler onfreeze ;attribute EventHandler onresume ;readonly attribute boolean wasDiscarded ; };
onfreeze と onresume 属性は、それぞれ freeze および
resume イベントの
イベントハンドラーIDL属性です。
wasDiscarded 属性のgetterは、この Document
の
discarded ブール値を返します。
注: これらのAPIは Document
に追加されています。
Window
ではなく、
Page Visibility APIとの一貫性のためです。これらのAPIは既存APIと組み合わせて使われることを想定しています。[PAGE-VISIBILITY]
注: 加えて clientId および
discardedClientId が Window
に追加され、破棄後にページを再訪してリロードする際のビュー状態復元をサポートします。これらは本イベントに反応するコードで利用される見込みです。
3.1. 使用例
freezeとresumeのハンドリング例:
const prepareForFreeze= () => { // 開いているIndexedDB接続を閉じる。 // Webロックを解放する。 // タイマーやポーリングを停止する。 }; const reInitializeApp= () => { // IndexedDB接続を復元する。 // 必要なWebロックを再取得する。 // タイマーやポーリングを再開する。 }; document. addEventListener( 'freeze' , prepareForFreeze); document. addEventListener( 'resume' , reInitializeApp);
破棄後にビュー状態を復元する例: ユーザーが同じアプリ・URLで複数タブを開いている場合、両方がバックグラウンドかつ破棄されたとき、アプリは2つのタブを区別して正しい状態を復元する必要があります。この目的でWindowのclientIdとlastClientIdが使えます。
// IndexedDBに状態を保存する際、self.clientIdの現在値をレコードにセットしておくことで // getPersistedState()で後から取得可能(タブが破棄後リロードされた場合など)。 const persistState= async ( state) => { const record= {... state, cliendId: self. clientId}; // レコードをIndexedDBやSessionStorageに保存... } // 渡されたクライアントIDに基づき、IndexedDBから状態レコードを取得。 const getPersistedState= async ( clientId) => { // IndexedDBでレコードを検索... }; // タブが以前破棄されていれば、self.lastClientId経由でそのクライアントIDの状態を取得。 if ( document. wasDiscarded) { getPersistedState( self. lastClientId); }
4. 機能ポリシー
ネストされた閲覧コンテキスト の実行状態を制御することは、閲覧コンテキストからユーザー体験を管理する上で望ましいです。たとえばアプリケーションは、ドキュメントが 描画中でない、または ビューポートに重なっていない場合、 ネストされた閲覧コンテキスト内(再生中の動画や音声含む)のすべての実行を停止したいかもしれません。すべての実行を停止することでCPU利用効率が向上します。こうしたニーズに対応するため、2つの機能ポリシーを定義します:-
"
execution-while-not-rendered"(デフォルト許可リストは*) -
"
execution-while-out-of-viewport"(デフォルト許可リストは*)
"execution-while-not-rendered" ポリシーは、
ネストされた閲覧コンテキストの 閲覧コンテキストコンテナが 描画中でない場合に、タスクの実行可否を制御します。
"execution-while-out-of-viewport" ポリシーは、
ネストされた閲覧コンテキストの 閲覧コンテキストコンテナが ビューポートに重なっていない場合(ターゲット要素とルートの交差領域計算アルゴリズムによる)に、タスクの実行可否を制御します。
§ 5.2 HTML標準の修正では、以下条件を満たした際にドキュメント(およびその子孫)を凍結状態に移行することでこれを実現します:
-
iframe loadイベントの手順が実行済み
-
当該ドキュメントのポリシーが無効である
-
該当するポリシー条件が成立(描画されていない・スクロールで視野外)
これら条件が成立しない場合、ドキュメントは未凍結状態となります。
<!-- iframeはロード後すぐに凍結されます。 --> < iframe allow = "execution-while-not-rendered 'none'" src = "subframe.html" style = "display:none" ></ iframe >
Document
document の ドキュメント凍結状態更新手順は以下の通り:
-
document の 閲覧コンテキストが ネストされた閲覧コンテキストでない場合はreturn。
-
document の readiness が "
complete" でない場合はreturn。 -
element を document の 閲覧コンテキストの 閲覧コンテキストコンテナとする。
-
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. ワークレット・ワーカーの挙動変更
単一の ワークレットエージェント の realm の グローバルオブジェクトの 所有ドキュメントが 凍結状態の場合、進行停止となる必要があります。 単一の 専用ワーカーエージェント の realm の グローバルオブジェクトの 所有ドキュメントが非nullかつ 凍結状態の場合も 進行停止となる必要があります。
DedicatedWorkerGlobalScope
workerGlobalScope の 所有ドキュメント判定手順:
-
workerGlobalScope の 所有セットが1つの
Documentdocument なら、document を返す。 -
workerGlobalScope の 所有セットが1つの
DedicatedWorkerGlobalScopeparentWorkerGlobalScope なら、parentWorkerGlobalScope の 所有ドキュメント を返す。 -
nullを返す。
注: DedicatedWorkerGlobalScope
の
所有セットは常に1つだけ要素を持ちます。
5.2. HTML標準の修正
5.2.1. ドキュメントのアンロードと履歴トラバーサル
ドキュメントが bfcache(back forward cache) へ出入りする際、凍結状態がtrue/falseに遷移します。
-
ドキュメントアンロードアルゴリズムで、Step #5後に
persisted属性がtrue(bfcacheへ移動時)、ドキュメントの凍結状態変更をdocument・trueで実行。 -
履歴トラバーサルアルゴリズムで、Step #4.6.4前に
persisted属性がtrue(bfcacheから出る時)、ドキュメントの凍結状態変更をdocument・falseで実行。
5.2.2. イベントループ: 定義
置き換え: タスクは、その document がnullまたは 完全にアクティブな場合、実行可能。
修正後: タスクは、その document がnullまたは 完全にアクティブかつ 未凍結の場合、実行可能。
5.2.3. イベントループ: 処理モデル
レンダリングの更新中、Step #11後に以下のステップを追加:
完全にアクティブな Document
doc それぞれについて、ドキュメント凍結状態更新手順をdocで実行。
5.2.4. 閲覧コンテキストの破棄
閲覧コンテキストやドキュメントの「discard」概念を「destroy」にリネームします。これにより、ユーザー向け属性 wasDiscarded
に「discarded」という用語を使えます。
5.2.5. ドキュメントの初期化
Step #3の前に以下を追加:
閲覧コンテキストが以前破棄状態だった場合、Documentの
discardedブール値をtrueにセット。
5.2.6. iframe loadイベントの手順
Step #5後に以下を追加:
child document で ドキュメント凍結状態更新手順を実行。
5.2.7. HTMLMediaElement
各 HTMLMediaElement
は resume frozen flag(初期値false)を持ちます。
5.2.8. メッセージの送信
注記追加:
注: 凍結状態の Window
にメッセージ送信すると、posted message task sourceにキューされたイベントは
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)
Step #4 の変数名を変更します。
-
matchedClientData を新しい リストとする。
Step #2.5.1 の前に挿入
-
lifecycleState を Get Client Lifecycle State に client を渡して実行した結果とする。
Step #5.3.1 でリストに lifecycleState を追加
-
windowData を «[ "client" → client, "ancestorOriginsList" → 新しい リスト, "lifecycleState" → lifecycleState ]»とする。
Step #5.4 で matchedClientData に lifecycleState を追加
-
«[ "client" → client, "lifecycleState" → lifecycleState ]» を matchedClientData に追加する。
Step #6.2 で windowData の lifecycleState を Create Window Clientアルゴリズムに渡す
-
windowClient を Create Window Clientアルゴリズムに windowData["
client"], windowData["frameType"], windowData["visibilityState"], windowData["focusState"], windowData["ancestorOriginsList"], windowData["lifecycleState"] を引数として渡して実行した結果とする。
Step #6.3 の調整
-
各 clientData を matchedClientData で反復:
-
clientObject を Create Clientアルゴリズムに clientData["
client"], clientData["lifecycleState"] を引数として渡して実行した結果とする。
-
5.3.1.3.
openWindow(url)
Step #7.5 の前に挿入
-
lifecycleState を Get Client Lifecycle State に this の関連 service worker client を渡して実行した結果とする。
Step #7.8.2 の調整(lifecycleState を渡す)
-
client を Create Window Clientに newContext の
Windowオブジェクトの environment settings object, frameType, visibilityState, focusState, ancestorOriginsList, lifecycleState を引数として実行した結果とする。
5.3.2. アルゴリズム
5.3.2.1. クライアントのライフサイクル状態取得
次のアルゴリズムを追加:
- 入力
-
client : service worker client
- 出力
-
state : 文字列
-
state を
"active"にする。 -
もし client の global object の 所有ドキュメント が 凍結状態なら state を
"frozen"にする。 -
state を返す。
5.3.2.2. Create Client
入力に lifecycleState(文字列)を追加
出力の Step #2 の後に次を追加
-
clientObject の ライフサイクル状態 を lifecycleState にセットする。
5.3.2.3. Create Window Client
入力に lifecycleState(文字列)を追加
出力の Step #5 の後に次を追加
-
windowClient の ライフサイクル状態 を lifecycleState にセットする。
5.4. ページライフサイクル処理モデル
5.4.1. 凍結状態(FROZENNESS state)
ドキュメントは以下の 凍結状態(FROZENNESS) のいずれかになります:-
true: ドキュメントが 凍結状態、関連するタスクは実行されない
-
false: ドキュメントが 未凍結状態、関連するタスクは通常通り実行される
注: トップレベルドキュメントの凍結状態変更アルゴリズムによれば、トップレベル閲覧コンテキストのドキュメントが凍結状態を変更すると、子孫閲覧コンテキストのすべてのドキュメントも同じ値に変更され(トップレベルと一致する)、一貫性が保たれます。
UAは状況に応じ トップレベルドキュメントの凍結状態変更アルゴリズムをtrueで実行することがあります。 例えば、トップレベル閲覧コンテキストがバックグラウンドまたは非表示であり、猶予期間が経過した場合、UAはリソース節約とフォアグラウンドのユーザー体験品質維持のためtrueで実行できます。 具体例:
-
モバイルChromeでは、バックグラウンドに5分以上あるタブは 凍結され、バッテリーやデータを節約します。
-
デスクトップChromeでは、ユーザーに重要でない(しばらく利用されていない)バックグラウンドタブは 破棄され、メモリを節約します。
注: ユーザーのために積極的に動作している(例:音声再生中)バックグラウンドタブは通常 凍結や 破棄されません。
注: Chromeが用いる詳しいヒューリスティック・除外条件は こちらの文書を参照してください。
UAは通常、ユーザーがその閲覧コンテキストを再訪した際 トップレベルドキュメントの凍結状態変更をfalseで実行します。また、リソースが豊富にある場合はバックグラウンドでも定期的にfalseで実行することがあります。
5.4.2. ドキュメントの凍結状態変更
Document
topLevelDoc、boolean frozenness状態frozenness)の手順:
-
Assert: doc の 閲覧コンテキストが トップレベル閲覧コンテキストである。
-
ドキュメントの凍結状態変更を topLevelDoc, frozenness, false で実行。
-
descendants を 子孫閲覧コンテキストのリスト(doc)とする。
-
各 閲覧コンテキスト b を descendants で反復:
-
descendantDocument を アクティブドキュメント(b)とする。
-
ドキュメントの凍結状態変更を descendantDocument, frozenness, false で実行。
-
Document
doc, boolean frozenness状態frozenness, boolean auto resume frozen
media)の手順:
-
もし frozenness が true なら freeze steps を doc・auto resume frozen media で実行。
-
そうでなければ resume steps を doc で実行。
Document
doc, boolean auto resume frozen media)の手順:
-
doc の 凍結状態 を true にセットする。
-
freezeイベントを doc で発火する。
-
elements を メディア要素のうち shadow-including descendants で doc をルートとし、shadow-including tree orderで並べたものとする。
-
各 element を elements で反復:
-
もし element の
pausedが false なら:-
element の resume frozen flag を auto resume frozen media にセットする。
-
media pause を element に実行。
-
注: frozenness 状態の代入がイベント発火より先になる順序は意図的です。
-
Document
doc)の手順:
-
elements を メディア要素のうち shadow-including descendants で doc をルートとし、shadow-including tree orderで並べたものとする。
-
各 element を elements で反復:
-
もし elements の resume frozen flag が true なら:
-
elements の resume frozen flag を false にセット。
-
media play を element に実行。
-
-
-
-
resumeイベントを doc で発火する。
-
doc の 凍結状態 を false にセットする。
注: frozenness 状態の代入がイベント発火の後になる順序は意図的です。
5.4.3. 破棄(Discarding)
各Documentは discarded ブール値(初期値false)を持つ。discard で閲覧コンテキストを破棄するには、閲覧コンテキストをdestroyし、その理由がdiscardingによるものであることを記録する(子孫も同様)。
注: discardは通常、システムメモリ等のリソース不足時に解放目的で行う。一方destroyはユーザーがページ離脱等で通常の後処理として行う。
閲覧コンテキスト ― バックグラウンドかつ、そのドキュメントが VisibilityState hidden の場合、リソース圧迫時(例:メモリ不足)には discarded され得る。具体例:
-
デスクトップChromeでは、ユーザーに重要でない(しばらく利用されていない)バックグラウンドタブは 破棄され、メモリを節約します。
注: ユーザーのために積極的に動作している(例:音声再生中)バックグラウンドタブは通常 破棄されません。
注: Chromeが用いる詳しいヒューリスティック・除外条件は こちらの文書を参照してください。
トップレベル閲覧コンテキスト(ブラウザのタブ)がリソース圧迫や予期せぬ事象(プロセスクラッシュ等)で 破棄された後、ユーザーが再度タブを訪れた場合、Document
の discarded ブール値が § 5.2.5 ドキュメントの初期化により true となります。
6. 謝辞
この仕様の改善につながる技術的な助言と提案をいただいた Dave Tapuska、 Fadi Meawad、 Ojan Vafai、 Olli Pettay、 Philip Walton、 そして Todd Reifsteck に特別な感謝を捧げます。