1. はじめに
Container Timing API を使用すると、DOM の注釈付きセクションが画面に表示され、最初の描画が
完了した時点を監視できます。開発者は、[^containertiming^] 属性(Element Timing API の
elementtiming と同様)を使用して DOM のサブセクションをマークし、そのセクションが初めて描画されたときに
パフォーマンスエントリーを受け取ることができます。
この API により、開発者はページ内のさまざまなコンポーネントのタイミングを測定できます。開発者が アプリケーションをコンポーネントとして構成することが増えるにつれ、アプリケーションまたは Web ページの サブセクションにおけるパフォーマンスを測定したいという需要が高まっています。
Element Timing とは異なり、DOM のセクションが描画を完了した時点をレンダラーが知ることはできません
(将来の変更、新しい画像に対する非同期リクエスト、読み込みの遅いボタンなどが存在し得るため)。
そのため、この API は、更新があったときに PerformanceEntry
オブジェクトの形式で候補を発行します。
2. 動機
開発者は、テーブル、ウィジェット、その他のコンポーネントなど、DOM のサブセクションがいつ描画されたかを 測定し、描画時間を追跡して分析に送信できるようにしたいと考えています。現在の Web API はこれを十分には サポートしていません:
-
Element Timing はサポートできる対象に制限があり、セクション全体には使用できません。
-
Largest Contentful Paint (LCP) は、ページの特定部分が読み込まれた時点を測定するには 十分に有用ではありません。
-
ユーザー空間の polyfill には、次のような大きな欠点があります:
-
描画前に要素をマークする必要がある(サーバー側の変更またはレンダリングのブロックが必要)
-
新たに挿入された要素を捕捉するために MutationObserver が必要
-
document head 内で実行する必要があり、初回描画までの時間が増加する
-
ブラウザー組み込みの 2D エンジンと比較して、矩形追跡の効率が低い場合がある
-
Web 作者は自分のドメインを誰よりもよく理解しており、自分たちのコンテンツブロックのパフォーマンスを、 ユーザーや組織が理解できる方法で伝えたいと考えています(例: "time to first tweet")。
2.1. ライフサイクル
このライフサイクルの例では、コンポーネントは異なる時点で複数のコンテンツ片を描画し、それぞれの時点で 更新された情報を含む新しいPerformanceContainerTiming
entry が生成されます。
ただし、いったん領域が描画されると、その同じ領域の後続の描画によって新しい entry は生成されません。
3. 使用例
次の例は、コンテナールートを登録し、その描画タイミングを監視する方法を示します。
< div containertiming = "foobar" > < main > ...</ main > < aside > ...</ aside > </ div > < script > const observer= new PerformanceObserver(( list) => { let perfEntries= list. getEntries(); for ( const entryof perfEntries) { console. log( 'コンテナーが描画されました:' , entry. identifier, '時刻' , entry. startTime, 'サイズ:' , entry. size); } }); observer. observe({ entryTypes: [ "container" ] }); </ script >
この属性は、要素が文書に追加される前に設定する必要があります(HTML 内で設定するか、 JavaScript で設定する場合は、文書に追加する前に設定します)。この属性を遡及的に設定した場合は、 それ以降のイベントと将来の描画のみが取得されます。
3.1. サブツリーの無視
< div containertiming = "foobar" > < main > ...</ main > <!-- aside の更新はコンテナータイミングイベントを発生させません --> < aside containertimingignore > ...</ aside > </ div >
4. 用語
コンテナルートとは、
[^containertiming^] 属性を持つ HTMLElement
である。
無視されるサブツリーとは、
[^containertimingignore^] 属性を持つ HTMLElement
を根とするサブツリーである。
ペイント済み領域とは、
コンテナルートが最初に観測されて以降に蓄積された、ペイントされたすべての部分を表す領域(矩形の集合)であり、
CSS ピクセルで表される。ペイント済み領域はビューポート座標空間で維持される。コンテナルート要素が
移動した場合(例:レイアウトの変更または moveBefore()
による場合)、
以前に蓄積された矩形は調整されない — 領域はビューポート座標内で拡大し続ける。
コンテナタイミング 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 HTMLElement ?lastPaintedElement ;readonly attribute HTMLElement ?rootElement ; };PerformanceContainerTiming includes PaintTimingMixin ;
注記: intersectionRectの
座標およびsizeは
CSS ピクセル(sizeの場合は平方
CSS ピクセル)で表現され、
ペイント済み
領域の座標空間および交差矩形アルゴリズムによって生成される単位と一致する。
PerformanceContainerTiming
オブジェクトには、次の関連付けられた概念がある。
-
identifier。初期値は空文字列に設定される。
-
intersectionRect。初期値は、すべての値が 0 に設定された
DOMRectReadOnlyに設定される。 -
size。初期値は 0 に設定される。
-
firstRenderTime。初期値は 0 に設定される。
-
renderTime。初期値は 0 に設定される。
-
関連付けられた
HTMLElementを含む lastPaintedElement。初期値はnullに設定される。 -
コンテナルートである、関連付けられた
HTMLElementを含む rootElement。初期値はnullに設定される。
entryType
属性のゲッターは、DOMString
"container" を返さなければならない。
name
属性のゲッターは、空文字列を返さなければならない。
duration
属性は 0 を返さなければならない。
startTime
属性のゲッターは、this
の renderTime の値を返さなければならない。
identifier 属性は、
this の identifier の値を返さなければならない。
intersectionRect
属性は、this の intersectionRect の値を返さなければならない。
size 属性は、
this の size
の値を返さなければならない。
firstRenderTime
属性は、this の firstRenderTime の値を返さなければならない。
lastPaintedElement
属性は、this の lastPaintedElement の値を返さなければならない。
rootElement 属性は、
this の rootElement の値を返さなければならない。
注記: ユーザーエージェントは、削除されたコンテンツによって
メモリリークが発生しないように、コンテナルートレコードマップを維持する必要がある。特に、エントリーの存続期間を HTMLElement
への弱いポインターに
結び付けることで、HTMLElement
が
削除された後のいずれかの時点で、それらをクリーンアップできる。このマップはウェブ開発者に公開されないため、
ガベージコレクションのタイミングが公開されることはない。
6. 処理モデル
注: Container Timing API を実装するユーザーエージェントは、
"container" を supportedEntryTypes
に含める必要があります。これは Window
コンテキスト向けです。
これにより、開発者は container timing のサポートを検出できます。
6.1. 文書ごとの状態
各 Document
について、ユーザーエージェントは、コンテナルートである
HTMLElementを
Container Timing Record
オブジェクトに対応付ける、コンテナルートレコードマップを維持しなければならない。
6.2. HTMLElement
インターフェイスの拡張
この節は、[DOM] 仕様が修正され次第、削除される。
HTMLElement
インターフェイスを次のように拡張する。
partial interface HTMLElement { [CEReactions ,Reflect ]attribute DOMString containerTiming ; [CEReactions ,Reflect ]attribute boolean containerTimingIgnore ; };
containerTiming 属性は、要素をコンテナルートとして識別する DOMString
である。その値は、対応する PerformanceContainerTiming
エントリーの identifier
となる。
containerTimingIgnore 属性は、存在する場合、
要素とその子孫を、祖先のコンテナルートに対するコンテナタイミング測定に寄与すべきではない無視されるサブツリーとしてマークする。
6.3. Container Timing Record
この仕様は、処理モデルで使用される内部データ構造を定義します:
-
paintTimingInfo。これは ペイントタイミング情報である。
-
identifier。これは
DOMStringである。 -
paintedRegion。これはペイント済み領域であり、 初期状態では空である。
-
lastNewPaintedAreaPaintTimingInfo。 これはペイントタイミング情報であり、初期状態では未設定である。
-
lastNewPaintedAreaElement。これは
HTMLElementまたは null であり、初期状態では null である。 -
lastNewPaintedAreaSize。これは 数値であり、初期状態では 0 である。
-
hasPendingChanges。これは ブール値であり、初期状態では false である。
DOMString
identifier が与えられたときに、コンテナー
タイミングレコードを作成するには、次の手順を実行します:
-
record を新しい コンテナータイミングレコードとします。
-
record の paintTimingInfo を paintTimingInfo に設定します。
-
record の identifier を identifier に設定します。
-
record を返します。
6.4. コンテナールートの登録
[^containertiming^] コンテンツ属性を持つ HTMLElement
が文書に接続されたとき:
-
ユーザーエージェントは、その要素を コンテナールートとして登録しなければなりません。
-
ユーザーエージェントは、無視されるサブツリーを除き、コンテナールートのサブツリー内のすべての 描画操作を追跡しなければなりません。
6.5. containertiming 属性の削除
HTMLElement
element から削除されたとき、次の手順を実行する。
-
document を element のノード文書とする。
-
document のコンテナルートレコードマップに element のエントリーが 含まれている場合、そのエントリーを削除する。
注: [^containertiming^] 属性が後で同じ要素に 再度追加された場合、次の描画時に新しい コンテナータイミングレコードが 作成されます。描画領域は新たに開始され、以前の描画データは保持されません。
6.6. コンテナールートの切断
HTMLElement
element が文書から切断されたとき、次の手順を実行する。
-
document を element のノード文書とする。
-
document のコンテナルートレコードマップに element のエントリーが 含まれている場合、そのエントリーを削除する。
注: 要素が [^containertiming^] 属性を引き続き 持ったまま文書に再接続された場合、新しい コンテナールートの登録として扱われます。次の描画時に、新しい コンテナータイミングレコードが、新しい 描画領域とともに作成されます。
6.7. コンテナータイミングのために描画された要素を処理する
Document
document、ペイントタイミング情報 paintTimingInfo、
HTMLElement
element、および DOMRectReadOnly
intersectionRect が与えられた場合、次の手順を実行する。
-
element がコンテナタイミングに寄与しない場合、返る。
-
containerRoot を、element を与えてコンテナ ルート要素を取得する結果とする。
-
containerRoot が null の場合、返る。
-
record を、document のコンテナルート レコードマップにある containerRoot のエントリーとする。そのようなエントリーが存在しない場合、record を、paintTimingInfo と containerRoot の [^containertiming^] コンテンツ属性の値を与えてコンテナタイミングレコードを作成する結果に設定し、 その後、(containerRoot → record) を document のコンテナルートレコードマップに追加する。
-
enclosingRect を intersectionRect の最小外接矩形とする。
-
document、containerRoot、 element、enclosingRect、および paintTimingInfo を与えて、 record の最後の新しいペイント済み領域を必要に応じて更新する。
-
Documentに保留中のコンテナタイミング変更があるものとしてマークする。
注記: このアルゴリズムは、ペイントを行う各画像ノードまたはテキスト ノードに対して呼び出される。交差矩形は、要素を ターゲット、ビューポートをルートとして交差矩形アルゴリズムを使用して計算し、 ビジュアルビューポートとの交差を取る必要がある。テキストノードの場合、交差 矩形は、所有されるテキストノードの集合内のすべてのテキストノードのボーダーボックスを含む最小の矩形であり、ビジュアルビューポートとの交差を取ったものである。
6.8. コンテナータイミングエントリーを発行する
Document
document の container timing エントリーを発行するよう求められた場合、次の手順を実行する。これは、すべてのペイント
操作が処理された後、フレームごとに 1 回呼び出されるべきである。
-
document に保留中の container timing の変更がない場合、返る。
-
document の container root records map の containerRoot → record それぞれについて:
-
record の hasPendingChanges が false の場合、継続する。
-
record と containerRoot を指定して、Container Timing エントリーを作成する。
-
record の hasPendingChanges を false に設定する。
-
record の lastNewPaintedAreaElement を null に設定する。
-
record の lastNewPaintedAreaSize を 0 に設定する。
-
-
Documentを、保留中の container timing の変更がなくなったものとしてマークする。
注: 描画された要素ごとに複数のエントリーを発行することがある他の一部の描画タイミング API とは異なり、
Container Timing は各 コンテナールートの描画領域を蓄積し、
フレームごと、コンテナールートごとに最大で 1 つの PerformanceContainerTiming
エントリーを発行します。このバッチ処理方式はより効率的であり、コンテナーの描画状態を包括的に把握できます。
6.9. 親コンテナールート Element の取得
HTMLElement
element が与えられたときに、親
コンテナルート要素を取得するには、次の手順を実行する。
-
parent を element の parentElement とする。
-
parent が null の場合、null を返す。
-
parent を与えてコンテナルート要素を取得する結果を返す。
6.10. コンテナータイミングへの寄与
HTMLElement
は、以下のすべてが真である場合、コンテナルートのコンテナタイミングに寄与する。
-
それがコンテナルートの子孫である。
-
それが無視されるサブツリー内にない。
-
それがシャドウツリー内にない。
HTMLElement
element がコンテナルート
containerRoot のコンテナタイミングに寄与するかどうかを判定するには、次の手順を実行する。
-
element が null の場合、false を返す。
-
element がシャドウツリー内にある場合、false を返す。
-
element が containerRoot の子孫でない場合、false を返す。
-
element が無視されるサブツリー内にある場合、false を返す。
-
true を返す。
6.11. コンテナールート Element の取得
HTMLElement
element が与えられたときに、コンテナ
ルート要素を取得するには、次の手順を実行する。
-
element が null の場合、null を返す。
-
element の [^containertiming^] コンテンツ属性が存在する場合、 element を返す。
-
element の parentElement が null でない場合、element の parentElement を与えてコンテナ ルート要素を取得する結果を返す。
-
null を返す。
6.12. 最後の新規描画領域を必要に応じて更新する
Document
document、container
root である HTMLElement
containerRoot、HTMLElement
element、DOMRectReadOnly
enclosingRect、および paint timing info paintTimingInfo
が与えられたとき、
最後の新しいペイント領域を更新する可能性があるには、次の手順を実行する。
-
paintedRegion を record の paintedRegion とする。
-
paintedRegion が enclosingRect を完全に含む場合、返る。
-
newPaintedArea を、paintedRegion にまだ 含まれていない enclosingRect の領域とする。
-
record の paintedRegion を、 paintedRegion と enclosingRect の和集合に設定する。
-
record の lastNewPaintedAreaPaintTimingInfo を paintTimingInfo に設定する。
-
newPaintedArea が record の lastNewPaintedAreaSize より大きい場合:
-
record の lastNewPaintedAreaElement を element に設定する。
-
record の lastNewPaintedAreaSize を newPaintedArea に設定する。
-
-
record の hasPendingChanges を true に設定する。
-
containerRoot の [^containertimingignore^] content 属性が存在する場合、返る。
-
parentContainerRoot を、containerRoot を指定して 親 container root 要素を取得する 結果とする。
-
parentContainerRoot が null の場合、返る。
-
parentRecord を、parentContainerRoot に対応する document の container root records map 内のエントリーとする。そのようなエントリーが存在しない場合、 parentRecord を、paintTimingInfo と parentContainerRoot の [^containertiming^] content 属性の値を指定して Container Timing Record を作成する 結果に設定し、 その後 (parentContainerRoot → parentRecord) を document の container root records map に追加する。
-
document、 parentContainerRoot、element、enclosingRect、および paintTimingInfo が与えられた parentRecord について、最後の新しいペイント領域を更新する可能性があるを実行する。
注: lastPaintedElement
は、1 回のレンダリングフレーム内で新たに描画された最大の領域に寄与した要素です。これにより、
実装固有の描画順序への依存を回避できます。lastPaintedElement
は、それ自体を独立したパフォーマンス指標として使用するのではなく、大規模または複雑な コンテナールート(例えば、
テーブル)の更新を引き起こしているものを調査する開発者向けのデバッグ支援として意図されています。lastNewPaintedAreaSize
は、各フレームの比較を新たに開始できるように、
エントリーが発行された後にリセットされます。
注: このアルゴリズムは、サイズにかかわらず描画
領域に対するすべての変更を報告します。描画領域が 1 ピクセルだけ変更された場合でも、新しい PerformanceContainerTiming
エントリーがキューに追加されます。小さな変更を除外したい開発者は、エントリー間の size
の値を比較することで除外できます。
6.13. コンテナータイミングエントリーの作成
HTMLElement
containerRoot が与えられたときに、コンテナタイミングエントリーを作成するには、ユーザーエージェントは次の手順を実行しなければならない。
-
entryを、次のように設定された新しい
PerformanceContainerTimingエントリとする:-
entryTypeを「container」に設定する -
nameを空文字列に設定する -
startTimeをrecordのlastNewPaintedAreaPaintTimingInfoのpaintTimeに設定する -
durationを0に設定する -
identifierをrecordのidentifierに設定する -
firstRenderTimeをrecordのpaintTimingInfoのpaintTimeに設定する -
intersectionRectをrecordのpaintedRegionの境界矩形に設定する -
sizeを、recordのpaintedRegionの平方CSSピクセル単位の総面積に設定する -
lastPaintedElementをrecordのlastNewPaintedAreaElement に設定する -
rootElementをcontainerRootに設定する
-
-
entryをPerformanceEntryのキューに追加する。
7. セキュリティとプライバシーに関する考慮事項
7.1. クロスオリジン制限
この API はクロスオリジン境界を尊重します:
-
クロスオリジン iframe に属する要素は、親フレームに公開されません。
-
開発者が
postMessageによって明示的に渡さない限り、タイミング情報は フレーム境界を越えません。
7.2. 情報の露出
この API によって提供される情報の大部分は、既存の API によってすでに推定できます:
-
Element Timing は、画像およびテキストの初回レンダリング時刻を返します。
-
Paint Timing API は関連するタイムスタンプを提供します。
-
これらの API の組み合わせにより、効率は劣るものの、container timing 情報を近似できます。
この API は次のものを公開しません:
-
レンダリングエンジンの内部実装の詳細
-
開発者がまだアクセス権を持っていない要素に関する情報
-
既存の Performance API を通じてすでに利用可能なものよりも細かいタイミング情報
7.3. タイミング攻撃
この API は DOMHighResTimeStamp
を使用します。これは他の Performance API と同様に、セキュリティ目的で分解能の制限を受ける場合があります。
7.4. プライバシーに関する考慮事項
この API は次のことを行いません:
-
サイトをまたいでユーザーを追跡できるようにすること
-
閲覧履歴を公開すること
-
スクリプト実行を通じてサイトがすでにアクセスできるものを超える、ユーザー行動に関する情報を提供すること
8. 謝辞
貴重なフィードバックと助言をくださった以下の方々に深く感謝します:
-
Barry Pollard
-
Michael Mocny
-
Scott Haseley
-
Sergey Chernyshev
-
Bas Schouten