1. はじめに
ページの読み込み中やユーザーがページとやり取りしている間、アプリケーションやブラウザーは様々なイベントをキューに入れ、ブラウザーがそれらを実行します。例えば、ユーザーエージェントはユーザーの操作に基づいて入力イベントをスケジュールし、アプリケーションは requestAnimationFrame や他のコールバックのためにコールバックをスケジュールします。キューに入ったイベントは、ブラウザーによって一つずつ取り出されて実行されます。
しかし、いくつかのタスクは長時間(複数フレーム)かかる場合があり、その際UIスレッドがブロックされ、他のすべてのタスクもブロックされることがあります。ユーザーにとっては、これはしばしば「固まった」ページとして現れ、ブラウザーがユーザー入力に応答できなくなります。これは現在ウェブ上でのユーザー体験の大きな悪化要因です:
- 「操作可能になるまでの時間」の遅延:
-
ページの読み込み中や、完全に視覚的に表示されていても、ロングタスクがメインスレッドを占有し、ユーザーがページとやり取りするのを妨げます。設計の悪いサードパーティコンテンツが原因となることが多いです。
- 入力遅延の増加・ばらつき:
-
重要なユーザー操作イベント(例:タップ、クリック、スクロール、ホイールなど)がロングタスクの後ろに並べられるため、体験がぎこちなく予測困難になります。
- イベント処理遅延の増加・ばらつき:
-
入力イベントと同様、イベントコールバック(例:onloadイベントなど)の処理が遅れ、アプリケーションの更新が遅延します。
- アニメーションやスクロールのカクつき:
-
一部のアニメーションやスクロール操作は、コンポジタスレッドとメインスレッドの協調が必要ですが、メインスレッドがロングタスクでブロックされると、アニメーションやスクロールの応答性に影響します。
一部のアプリケーションやRUMベンダーは、すでに「ロングタスク」が発生したケースの特定や追跡を試みています。例えば、既知のパターンとして、短い周期的なタイマーを設置し、連続するタイマーの満了間の経過時間を調べる方法があります。経過時間がタイマー周期を超えていれば、ロングタスクによってイベントループの実行が遅延した可能性が高いです。この方法は一応機能しますが、パフォーマンス面で多くの問題があります:ロングタスク検出のためにポーリングすると、アイドル状態や長時間の休止が防げてしまい(requestIdleCallback参照)、バッテリー寿命に悪影響を及ぼします。また、遅延の原因(ファーストパーティかサードパーティのコードか)は分かりません。
RAILパフォーマンスモデルは、アプリケーションがユーザー入力に100ms以内で応答するべきだと提案しています(タッチ移動やスクロールの場合は16ms)。このAPIの目的は、アプリケーションがこれらの目標を達成できなくなる原因となるタスクについて通知を提供することです。本APIは50ms以上かかるタスクを検出します。これらのタスクがないウェブサイトは、ユーザー入力に100ms以内で応答できるはずです:ユーザー入力が発生したとき実行中のタスクを50ms未満で終了し、入力への反応タスクも50ms未満で実行できます。
1.1. 利用例
const observer= new PerformanceObserver( function ( list) { for ( const entryof list. getEntries()) { // ロングタスク通知の処理: // 分析や監視のための報告 // ... } }); // 過去および今後のロングタスク通知用にオブザーバーを登録 observer. observe({ type: "longtask" , buffered: true }); // この後に長いスクリプト実行があると、 // "longtask" エントリがオブザーバーにキューされ受け取れる
2. 用語
ロングタスクは、以下のいずれかの事象で50msを超えて継続するものを指します:
-
イベントループのタスクと、その直後に実行されるマイクロタスクチェックポイントの実行。これによりイベントループタスクの所要時間(関連するマイクロタスクを含む)が計測されます。
-
レンダリングの更新ステップ(イベントループ処理モデル内)。
-
最後のステップから次の最初のステップまでの間の一時停止(イベントループ処理モデル)。これにより、ユーザーエージェントがUIスレッドでイベントループ外で行う作業も含まれます。
ブラウジングコンテナは、ブラウジングコンテキストbcのアクティブドキュメントのノードナビゲータブルのコンテナです。
注: この用語は古くなっており、改訂時には新しい用語を再利用すべきです。
原因となるブラウジングコンテナは、ブラウジングコンテナ(iframe
,
object
など)が、ロングタスクの主な原因として特定される場合を指します。
アトリビューションは、ロングタスクに大きく寄与した作業(スクリプト、レイアウト等)の種類の特定と、その作業の責任を持つ原因となるブラウジングコンテナの特定を指します。
3. ロングタスクタイミング
ロングタスクタイミングには次の新しいインターフェースが含まれます:
3.1.
PerformanceLongTaskTiming
インターフェース
[Exposed =Window ]interface :
PerformanceLongTaskTiming PerformanceEntry { /* Overloading PerformanceEntry */readonly attribute DOMHighResTimeStamp ;
startTime readonly attribute DOMHighResTimeStamp ;
duration readonly attribute DOMString ;
name readonly attribute DOMString ;
entryType readonly attribute FrozenArray <TaskAttributionTiming >attribution ; [Default ]object (); };
toJSON
PerformanceLongTaskTiming
の属性値は、§ 4.1 ロングタスクの報告にある処理モデル内で設定されます。以下は、それらがどのように設定されるかの説明(参考)です。
name
属性のgetterは、次のいずれかの文字列を返します:
- "
unknown
" -
ロングタスクが、ユーザーエージェントがイベントループ外で行った作業から発生した場合。
- "
self
" -
ロングタスクが、このイベントループタスク内で、現在のブラウジングコンテキストで発生した場合。
- "
same
"- origin- ancestor -
ロングタスクが、イベントループタスク内で、同一オリジンの祖先ナビゲーブルで発生した場合。
- "
same
"- origin- descendant -
ロングタスクが、イベントループタスク内で、同一オリジンの子孫ブラウジングコンテキストで発生した場合。
- "
same
"- origin -
ロングタスクが、イベントループタスク内で、祖先でも子孫でもない同一オリジンのブラウジングコンテキストで発生した場合。
- "
cross
"- origin- ancestor -
ロングタスクが、イベントループタスク内で、クロスオリジンの祖先ナビゲーブルで発生した場合。
- "
cross
"- origin- descendant -
ロングタスクが、イベントループタスク内で、クロスオリジンの子孫ブラウジングコンテキストで発生した場合。
- "
cross
"- origin- unreachable -
ロングタスクが、イベントループタスク内で、祖先でも子孫でもないクロスオリジンのブラウジングコンテキストで発生した場合。
- "
multiple
"- contexts -
ロングタスクが、複数のブラウジングコンテキストをまたぐイベントループタスクから発生した場合。
注: これらの名前には「-unreachable」や「-contexts」など、いくつか一貫性がないものがあります。 互換性維持のため、これらの名前はそのままとされています。
entryType
属性のgetterは、
を返します。
startTime
属性のgetterは、タスクの開始時刻のDOMHighResTimeStamp
を返します。
duration
属性のgetterは、タスク開始から終了までの経過時間を、1msの粒度で表したDOMHighResTimeStamp
を返します。
attribution
属性のgetterは、TaskAttributionTiming
エントリの凍結された配列を返します。
3.2.
TaskAttributionTiming
インターフェース
[Exposed =Window ]interface :
TaskAttributionTiming PerformanceEntry { /* Overloading PerformanceEntry */readonly attribute DOMHighResTimeStamp ;
startTime readonly attribute DOMHighResTimeStamp ;
duration readonly attribute DOMString ;
name readonly attribute DOMString ;
entryType readonly attribute DOMString containerType ;readonly attribute DOMString containerSrc ;readonly attribute DOMString containerId ;readonly attribute DOMString containerName ; [Default ]object (); };
toJSON
TaskAttributionTiming
の属性値は、§ 4.1 ロングタスクの報告にある処理モデル内で設定されます。以下は、それらがどのように設定されるかの説明(参考)です。
name
属性のgetterは常に"unknown
"を返します。
entryType
属性のgetterは常に"taskattribution
"を返します。
startTime
属性のgetterは常に0を返します。
duration
属性のgetterは常に0を返します。
containerType
属性のgetterは、原因となるブラウジングコンテナの種類("iframe
"、"embed
"、"object
"など)を返します。単一の原因となるブラウジングコンテナが見つからない場合は、"window
"を返します。
containerName
属性のgetterは、コンテナの name
コンテンツ属性の値を返します。単一の原因となるブラウジングコンテナが見つからない場合は、空文字列を返します。
containerId
属性のgetterは、コンテナの id
コンテンツ属性の値を返します。単一の原因となるブラウジングコンテナが見つからない場合は、空文字列を返します。
containerSrc
属性のgetterは、コンテナの src
コンテンツ属性の値を返します。単一の原因となるブラウジングコンテナが見つからない場合は、空文字列を返します。
3.3. 原因の特定
このセクションは規定ではありません。
ロングタスクは、スクリプト・レイアウト・スタイルなど様々な種類の作業を含む場合があり、異なるブラウジングコンテキスト内で実行されたり、全体的(ガベージコレクションなど)なものもあり、 エージェントクラスタやブラウジングコンテキストグループセット全体にまたがる場合もあります。
このため、アトリビューションにはいくつかの側面があります:
-
ロングタスクの発生元や原因となるブラウジングコンテナの全体的な場所を指し示す:これは最小限の原因特定と呼ばれ、
name
フィールドに記録されます。 -
ロングタスクに関与した作業の種類や、その作業に関連する原因となるブラウジングコンテナの特定:これは
TaskAttributionTiming
オブジェクトとして、attribution
フィールドに記録されます(PerformanceLongTaskTiming
)。
したがって、name
フィールドと attribution
フィールド(PerformanceLongTaskTiming
)で、ロングタスクの責任箇所を示します。
この情報を提供する際はWebの同一オリジンポリシーを遵守する必要があります。
これらのフィールドは独立していません。以下はそれらの関係の概要です:
name
| 原因となるブラウジングコンテナ
(attribution で示される)
|
---|---|
"self "
| 空 |
"same "
| 同一オリジンの原因 |
"same "
| 同一オリジンの原因 |
"same "
| 同一オリジンの原因 |
"cross "
| 空 |
"cross "
| 空 |
"cross "
| 空 |
"multiple "
| 空 |
"unknown "
| 空 |
4. 処理モデル
注: Long Tasks API を実装するユーザーエージェントは、
を supportedEntryTypes
に Window
コンテキスト用として含める必要があります。
これにより、開発者はロングタスクのサポートを検出できます。
4.1. ロングタスクの報告
-
タスク終了時刻を記録する(end time と task の document を用いる)。
-
end time から start time を引いた値がロングタスクの閾値(50ms)未満なら、この手順を中止する。
-
destinationRealms を空の集合とする。
-
レポートを配信する JavaScript Realm の集合を決定する:
各 トップレベルブラウジングコンテキスト topmostBC(top-level browsing contexts の要素)について:
-
topmostBC の アクティブドキュメント の 関連Realm を destinationRealms に加える。
-
descendantBCs を topmostBC の アクティブドキュメント の 子孫ブラウジングコンテキストのリスト とする。
-
document を descendantBC の アクティブドキュメント とする。
-
各 descendantBC(descendantBCs の要素)について、(document の 関連Realm, document の 関連設定オブジェクト の クロスオリジン分離機能) を destinationRealms に加える。
-
-
ユーザーエージェントは、JavaScript Realm の一部を destinationRealms から除外できる。
注: この除外は、ユーザーエージェントが別プロセスで扱う JavaScript Realm のロングタスク報告を回避するために利用される可能性がある。しかしこの概念は厳密には規定されていない。
どの Document
がどのロングタスクに可視性を持つかについて、議論が続いているため、このロジックは将来的に変更される可能性がある。 [Issue #75]
-
各 (destinationRealm, crossOriginIsolatedCapability)(destinationRealms の要素)について:
-
name を空文字列とする。これは下記の最小限の原因特定用に使われる。
-
culpritSettings を
とする。null -
task の スクリプト評価環境設定オブジェクト集合 を処理して、以下の通り name と culpritSettings を決定する:
-
task の スクリプト評価環境設定オブジェクト集合 が空なら、name を "
unknown
" に、culpritSettings を
に設定する。null -
そうでなく、task の スクリプト評価環境設定オブジェクト集合 の長さが 1 より大きいなら:name を "
multiple
" に、culpritSettings を- contexts
に設定する。null -
そうでなく、すなわち task の スクリプト評価環境設定オブジェクト集合 の長さが 1 の場合:
-
culpritSettings を task の スクリプト評価環境設定オブジェクト集合 の唯一の項目に設定する。
-
destinationSettings を destinationRealm の 関連設定オブジェクト に設定する。
-
destinationOrigin を destinationSettings の オリジン に設定する。
-
destinationBC を destinationSettings の グローバルオブジェクト の ブラウジングコンテキスト に設定する。
-
culpritBC を culpritSettings の グローバルオブジェクト の ブラウジングコンテキスト に設定する。
-
Assert: culpritBC は
ではない。null -
culpritSettings が destinationSettings と同じなら、name を "
self
" に設定する。 -
そうでなく、culpritSettings の オリジン と destinationOrigin が 同一オリジン の場合:
-
destinationBC が
なら、name を "null same
" に設定する。- origin -
そうでなく、culpritBC が 祖先(destinationBC の)なら、name を "
same
" に設定する。- origin- ancestor -
そうでなく、destinationBC が 祖先(culpritBC の)なら、name を "
same
" に設定する。- origin- descendant -
それ以外の場合、name を "
same
" に設定する。- origin
-
-
それ以外の場合:
-
destinationBC が
なら、name を "null cross
" に設定する。- origin- unreachable -
そうでなく、culpritBC が 祖先(destinationBC の)なら、name を "
cross
" にし、culpritSettings を- origin- ancestor
に設定する。null 注: これはセキュリティ上報告されない。開発者は自分で調べるべき。
-
そうでなく、destinationBC が 祖先(culpritBC の)なら、name を "
cross
" に設定する。- origin- descendant -
それ以外の場合、name を "
cross
" に設定する。- origin- unreachable
-
-
-
-
attribution を新しい
TaskAttributionTiming
オブジェクトとして destinationRealm で生成し、属性を次のように設定する:-
attribution の
name
属性を "unknown
" に設定する。注: 今後このAPIのバージョンでは
name
属性に追加値が加わる予定だが、現時点では値は1つのみ。 -
attribution の
entryType
属性を
に設定する。"taskattribution" -
attribution の
containerType
属性を
に設定する。"window" -
attribution の
containerName
およびcontainerSrc
属性を空文字列に設定する。 -
culpritSettings が
でない場合:null -
culpritBC を culpritSettings の グローバルオブジェクト の ブラウジングコンテキスト に設定する。
-
Assert: culpritBC は
ではない。null -
container を culpritBC の ブラウジングコンテナ に設定する。
-
Assert: container は
ではない。null -
attribution の
containerId
属性を container の ID の値に設定する(IDが未設定なら空文字列)。 -
container が
iframe
要素の場合:-
attribution の
containerType
属性を "iframe
" に設定する。 -
attribution の
containerName
属性を container のname
コンテンツ属性の値に(属性がなければ空文字列)。 -
attribution の
containerSrc
属性を container のsrc
コンテンツ属性の値に(属性がなければ空文字列)。
注: ここで記録するのは frame の
src
属性の値であり、現在のURLではない。これは主にフレーム識別のためであり、クロスオリジン iframe の現在URLを発見可能にすることはセキュリティ上問題。 -
-
container が
frame
要素の場合:-
attribution の
containerType
属性を "frame
" に設定する。 -
attribution の
containerName
属性を container のname
コンテンツ属性の値に(なければ空文字列)。 -
attribution の
containerSrc
属性を container のsrc
コンテンツ属性の値に(なければ空文字列)。
-
-
container が
object
要素の場合:-
attribution の
containerType
属性を "object
" に設定する。 -
attribution の
containerName
属性を container の name コンテンツ属性の値に(なければ空文字列)。 -
attribution の
containerSrc
属性を container のdata
コンテンツ属性の値に(なければ空文字列)。
-
-
container が
embed
要素の場合:-
attribution の
containerType
属性を "embed
" に設定する。 -
attribution の
containerName
属性を空文字列に設定する。 -
attribution の
containerSrc
属性を container のsrc
コンテンツ属性の値に(なければ空文字列)。
-
-
-
-
新しい
PerformanceLongTaskTiming
オブジェクト newEntry を destinationRealm で生成し、属性を次のように設定する:-
newEntry の
name
属性を name に設定する。 -
newEntry の
entryType
属性を "longtask
" に設定する。 -
newEntry の
startTime
属性を coarsening start time(crossOriginIsolatedCapability 指定)で求めた値に設定する。 -
dur を coarsening end time(crossOriginIsolatedCapability 指定)で求めた値から newEntry の
startTime
を引いた値とする。 -
newEntry の
duration
属性を dur の整数部分に設定する。 -
attribution が
でない場合、newEntry のnull attribution
属性を attribution 1つのみを含む新しい凍結配列に設定する。注: 今後このAPIのバージョンでは
attribution
属性に追加値が加わる予定だが、現時点では値は1つのみ。
-
-
PerformanceEntry をキュー する(newEntry)。
-
5. セキュリティおよびプライバシーに関する考慮事項
Long Tasks APIは、ロングタスクの発生元についてオリジンが安全なアトリビューション情報を含めることで、同一オリジンポリシーに従っています。ロングタスクの閾値は50msです。継続時間は1msの粒度でのみ提供されます。これらを組み合わせることで、クロスオリジン漏洩に対して十分な保護を提供しています。
Long Tasks APIは、ユーザーによって実行されたタスクの継続時間や種類のタイミング情報、および関数呼び出しを引き起こしたブラウジングコンテキストなどのアトリビューションを提供します。これにより、攻撃者がユーザーの行動を推測したり特定したりするためのサイドチャネルタイミング攻撃を行える可能性があります。たとえば、長いスクリプトの後に長いレンダーが続くパターンから、ユーザーのソーシャルウィジェットへの操作を推測できます。詳細な関数呼び出しのアトリビューションは、ユーザーの行動を特定するために利用されます。
このAPIが新たなプライバシー攻撃を導入することはありませんが、既存のプライバシー攻撃をより迅速にする可能性があります。これに対する緩和策は必要に応じて実装できます:
-
APIが提供するロングタスクの継続時間にさらにクランプ(丸め)やランダムなジッターを加え、攻撃の実行を困難にする。
-
APIによって公開するlongtaskのオリジン数を制限し、その後のタスクのアトリビューションを曖昧化する。例えば、5つのiframeを持つページは、そのうち3つのiframeからのタスクのみアトリビューションを受け取り、他の2つについてはアトリビューションなし(
name
がunknown
)となる。 -
一定の閾値を超えた後、原因/アトリビューション情報を削除できるようにする。例えば、10回のロングタスクの後は、すべてのエントリーがアトリビューションなしとなり、
name
が"unknown
"となる。 -
APIによって公開されるタイミング情報に内蔵の遅延を追加し、ロングタスクの件数依存の攻撃の実行を困難にする。
5.1. 観測者に公開される内容
トップレベルページ内のすべての観測者(すなわちページ内のすべてのiframeとメインフレーム)は、ロングタスクの存在について通知を受け取ります。タスクの開始時刻、継続時間(1ms粒度)、および原因となるフレームへのポインタを公開します。これらの情報は、今日でもsetTimeoutを使えばより高い精度で観測可能です。攻撃者は、ページ上の他の要素をすべて除去し、脆弱なクロスオリジンリソースのみを追加することで、setTimeoutの遅延がそのリソースによるものであると確認できます。他ページ(タブやウィンドウ)の観測者は、ユーザーエージェントのアーキテクチャに関わらず、通知を受け取るべきではありません。
クロスオリジンで公開される内容のルール:
-
クロスオリジンの観測者は、原因の方向のみ確認できます。たとえば、原因が深くネストされたiframeなら、ホストページは自分と原因との間の最初のクロスオリジンだけを見ることができます。
-
逆に、原因がトップレベルページの場合、深く埋め込まれたiframeはクロスオリジン祖先でロングタスクが発生したことは分かりますが、それ以上の情報は受け取りません。
5.2. 考慮された攻撃シナリオ
考慮されたタイミング攻撃は以下の通りです:
-
従来のタイミング攻撃:外部リソースのロード時間を利用して、プライベートデータのサイズを明らかにする。例えば、ギャラリー内の隠し画像の数やユーザー名の有効性など。例参照。
-
サイドチャネルタイミング攻撃:動画解析、スクリプト解析、App Cacheの読み取りやCache API(Service Worker)利用の時間を使って、ユーザーの特定や年齢・性別・地域・興味などのプロファイルを作成する。例として、ソーシャルネットワークのステータス更新が特定の属性(例:20~30歳の女性)に限定される場合、パーマリンクページのファイルサイズによってユーザーがその属性かどうかが判別できる。
これらのシナリオは、50msの閾値とクロスオリジン境界の遵守、すなわちタスクタイプや追加のアトリビューションを信頼できないクロスオリジン観測者に公開しないことで対応しています。