1. はじめに
Container Timing API は、DOM の注釈付けされたセクションが画面に表示され、その初回描画が
完了した時点を監視できるようにします。開発者は、containertiming
属性(Element Timing API の elementtiming と同様)で DOM のサブセクションをマークし、そのセクションが
初めて描画されたときに performance entry を受け取ることができます。
この 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. 使用例
次の例は、コンテナールートを登録し、その描画タイミングを監視する方法を示します。
containertiming
属性を使用して要素ごとに行われます:
< div containertiming = "foobar" > < main > ...</ main > < aside > ...</ aside > </ div > < script > const observer= new PerformanceObserver(( list) => { let perfEntries= list. getEntries(); for ( const entryof perfEntries) { console. log( 'Container painted:' , entry. identifier, 'at' , entry. startTime, 'size:' , entry. size); } }); observer. observe({ entryTypes: [ "container" ] }); </ script >
この属性は、要素が文書に追加される前に設定するべきです(HTML 内で設定するか、JavaScript で設定する場合は 文書に追加する前に設定します)。属性を遡及的に設定しても、後続のイベントと将来の描画のみが捕捉されます。
3.1. サブツリーの無視
containertiming-ignore>
属性を使用して無視できます:
< div containertiming = "foobar" > < main > ...</ main > <!-- Aside updates won't trigger container timing events --> < aside containertiming-ignore > ...</ aside > </ div >
4. 用語
コンテナールートとは、
Element
のうち、containertiming
属性を持つものです。
無視されるサブツリーとは、
Element
をルートとするサブツリーで、その要素が containertiming-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
オブジェクトには、次の関連概念があります:
-
identifier。初期値は空文字列です。
-
intersectionRect。初期値は、すべての値が 0 に設定された
DOMRectReadOnlyです。 -
size。初期値は 0 です。
-
firstRenderTime。初期値は 0 です。
-
renderTime。初期値は 0 です。
-
lastPaintedElement。関連する
Elementを含み、初期値はnullです。 -
rootElement。コンテナールートである、関連する
Elementを含み、初期値はnullです。
entryType
属性の getter は、DOMString
"container" を返さなければなりません。
name
属性の getter は空文字列を返さなければなりません。
duration
属性は 0 を返さなければなりません。
startTime
属性の getter は、this の renderTime の値を返さなければなりません。
identifier 属性は、
this の identifier の値を返さなければなりません。
intersectionRect
属性は、this の intersectionRect の値を返さなければなりません。
size 属性は、
this の size
の値を返さなければなりません。
firstRenderTime
属性は、this の firstRenderTime の値を返さなければなりません。
lastPaintedElement
属性は、this の lastPaintedElement の値を返さなければなりません。
rootElement 属性は、
this の rootElement の値を返さなければなりません。
注: ユーザーエージェントは、削除されたコンテンツが
メモリリークを引き起こさないように、container root records map を維持する必要があります。
特に、entry の寿命を Element
への
弱ポインターに結び付けることができ、Element
が
削除された後のある時点でそれらをクリーンアップできます。この map は Web 開発者に公開されないため、
これはガベージコレクションのタイミングを公開しません。
6. 処理モデル
注: Container Timing API を実装するユーザーエージェントは、
"container" を supportedEntryTypes
に含める必要があります。これは Window
コンテキスト向けです。
これにより、開発者は container timing のサポートを検出できます。
6.1. 文書ごとの状態
各 Document
について、ユーザーエージェントは container root records map を維持しなければなりません。これは
コンテナールート Element
から
Container Timing Record オブジェクトへの対応付けです。
6.2. Element
インターフェイスへの拡張
このセクションは、[DOM] 仕様が変更された後に削除されます。
ここでは、Element
インターフェイスを次のように拡張します:
partial interface Element { [CEReactions ]attribute DOMString containertiming ; [CEReactions ]attribute DOMString ?; };containertimingIgnore
containertiming 属性は、その要素を
コンテナールートとして識別する DOMString
です。その値は、対応する PerformanceContainerTiming
entry 内の identifier
になります。
containertiming-ignore 属性は、存在する場合、
その要素とその子孫を、祖先の コンテナールート
の container timing 測定に寄与すべきでない
無視されるサブツリーとしてマークします。
6.3. Container Timing Record
この仕様は、処理モデルで使用される内部データ構造を定義します:
-
paintTimingInfo。 これは paint timing info です。
-
identifier。 これは
DOMStringです。 -
paintedRegion。 これは 描画済み領域で、 初期状態では空です。
-
lastNewPaintedAreaPaintTimingInfo。 これは paint timing info で、初期状態では未設定です。
-
lastNewPaintedAreaElement。 これは
Elementまたは null で、初期値は null です。 -
hasPendingChanges。 これは boolean で、初期値は false です。
DOMString
identifier が与えられたとき、Container Timing Record を
作成するには、次の手順を実行します:
-
record を新しい Container Timing Record とします。
-
record の paintTimingInfo を paintTimingInfo に設定します。
-
record の identifier を identifier に設定します。
-
record を返します。
6.4. コンテナールートの登録
[^containertiming^] content attribute を持つ Element
が文書に接続されたとき:
-
ユーザーエージェントは、その要素を コンテナールートとして登録しなければなりません。
-
ユーザーエージェントは、コンテナールートのサブツリー内の すべての paint operation を追跡しなければなりません。ただし、無視されるサブツリーは除外します。
6.5. Container Timing のための要素描画の処理
Document
document、paint timing info paintTimingInfo、
Element
element、および DOMRectReadOnly
intersectionRect が与えられた場合、次の手順を実行します:
-
element が container timing に寄与しない場合、return します。
-
containerRoot を、element が与えられたときの コンテナールート要素を 取得する結果とします。
-
containerRoot が null の場合、return します。
-
record を、document の container root records map 内の containerRoot に対する entry とします。そのような entry が存在しない場合、 paintTimingInfo と containerRoot の [^containertiming^] content attribute の値が与えられたときの Container Timing Record を作成する結果を record に設定し、(containerRoot → record) を document の container root records map に追加します。
-
enclosingRect を intersectionRect の最小包含矩形とします。
-
document、containerRoot、 element、enclosingRect、および paintTimingInfo が与えられたとき、 record について 最後の新規描画領域を更新する可能性を実行します。
-
Documentを、保留中の container timing 変更を持つものとしてマークします。
注: このアルゴリズムは、描画される各画像またはテキスト ノードに対して呼び出されます。intersection rectangle は、要素を target、viewport を root として intersection rect algorithm を使用し、visual viewport と交差させて 計算するべきです。テキストノードの場合、intersection rectangle は、所有されるテキストノードの集合内のすべてのテキストノードの border box を含む最小矩形を、visual viewport と交差させたものです。
6.6. Container Timing Entry の発行
Document
document について container timing entry を発行するよう求められたとき、次の手順を実行します。
これは、すべての paint operation が処理された後、フレームごとに 1 回呼び出されるべきです:
-
document が保留中の container timing 変更を持たない場合、return します。
-
document の container root records map における 各 containerRoot → record について、反復します:
-
record の hasPendingChanges が false の場合、continue します。
-
record と containerRoot が与えられたとき、 Container Timing entry を作成します。
-
record の hasPendingChanges を false に設定します。
-
-
Documentを、保留中の container timing 変更を持たなくなったものとしてマークします。
注: 描画された要素ごとに複数の entry を発行する場合がある
他の一部の paint timing API とは異なり、Container Timing は各
コンテナールートについて
描画済み領域を蓄積し、コンテナールートごと、フレームごとに最大 1 つの PerformanceContainerTiming
entry を発行します。このバッチ化のアプローチはより効率的であり、コンテナーの描画状態を全体的に把握できます。
6.7. 親コンテナールート Element の取得
Element
element が与えられたとき、親コンテナールート要素を
取得するには、次の手順を実行します:
-
parent を element の parentElement とします。
-
parent が null の場合、null を返します。
-
parent が与えられたときの コンテナールート要素を取得する結果を返します。
6.8. Container Timing への寄与
Element
は、次のすべてが true である場合、コンテナールートの
container timing に寄与します:
-
それが コンテナールートの子孫である。
-
それが 無視されるサブツリー内にない。
-
それが shadow tree 内にない。
Element
element が、コンテナールート containerRoot の container timing に寄与するかどうかを
判定するには:
-
element が null の場合、false を返します。
-
element が shadow tree 内にある場合、false を返します。
-
element が containerRoot の子孫でない場合、false を返します。
-
element が 無視されるサブツリー内にある場合、false を返します。
-
true を返します。
6.9. コンテナールート Element の取得
Element
element が与えられたとき、コンテナールート要素を
取得するには、次の手順を実行します:
-
element が null の場合、null を返します。
-
element の [^containertiming^] content attribute が存在する場合、 element を返します。
-
element の parentElement が null でない場合、element の parentElement が 与えられたときの コンテナールート要素を 取得する結果を返します。
-
null を返します。
6.10. 最後の新規描画領域を更新する可能性
Document
document、コンテナールート Element
containerRoot、Element
element、DOMRectReadOnly
enclosingRect、および paint timing info paintTimingInfo が
与えられたとき、最後の新規描画領域を更新する可能性を実行するには、
次の手順を実行します:
-
paintedRegion を record の paintedRegion とします。
-
paintedRegion が enclosingRect を完全に含む場合、return します。
-
record の paintedRegion を paintedRegion と enclosingRect の union に設定します。
-
record の lastNewPaintedAreaPaintTimingInfo を paintTimingInfo に設定します。
-
record の lastNewPaintedAreaElement を element に設定します。
-
record の hasPendingChanges を true に設定します。
-
containerRoot の [^containertiming-ignore^] content attribute が存在する場合、return します。
-
parentContainerRoot を、containerRoot が与えられたときの 親コンテナールート要素を取得する結果とします。
-
parentContainerRoot が null の場合、return します。
-
parentRecord を、document の container root records map 内の parentContainerRoot に対する entry とします。そのような entry が 存在しない場合、paintTimingInfo と parentContainerRoot の [^containertiming^] content attribute の値が与えられたときの Container Timing Record を作成する結果を parentRecord に設定し、(parentContainerRoot → parentRecord) を document の container root records map に追加します。
-
document、parentContainerRoot、element、 enclosingRect、および paintTimingInfo が与えられたとき、 parentRecord について 最後の新規描画領域を更新する可能性を実行します。
注: このアルゴリズムは、サイズにかかわらず、描画済み領域への
あらゆる変更を報告します。描画領域の 1 ピクセルの変更であっても、新しい PerformanceContainerTiming
entry がキューに入れられます。小さな変更を除外したい開発者は、entry 間の size
値を比較することで、それを行うことができます。
6.11. Container Timing Entry の作成
Element
containerRoot が与えられたとき、Container Timing entry を作成するために、
ユーザーエージェントは次の手順を実行しなければなりません:
-
entry を新しい
PerformanceContainerTimingentry とし、次の値を持たせます:-
entryTypeは "container" に設定 -
nameは空文字列に設定 -
startTimeは record の lastNewPaintedAreaPaintTimingInfo のpaintTimeに設定 -
durationは 0 に設定 -
identifierは record の identifier に設定 -
firstRenderTimeは record の paintTimingInfo のpaintTimeに設定 -
intersectionRectは record の paintedRegion の bounding rectangle に設定 -
sizeは record の paintedRegion の合計面積に設定 -
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