1. はじめに
このセクションは非規範的です。
ECMAScript言語仕様書 [ECMA-262] では、
Date オブジェクトを、
1970年1月1日UTCからのミリ秒単位の時刻値を表すものとして定義しています。
ほとんどの場合、この時刻の定義は十分であり、これらの値によって
1970年1月1日UTCから約285,616年以内の任意の瞬間の時刻をミリ秒精度で表現できます。
実際には、こうした時刻の定義はクロックスキューや システムクロックの調整の影響を受けます。時刻値は 常に単調増加するとは限らず、後続値が減少したり 同じ値となることもあります。
例えば、以下のスクリプトは計算した duration に
正の数・負の数・ゼロを記録する場合があります。
var mark_start= Date. now(); doTask(); // Some task var duration= Date. now() - mark_start;
特定のタスクにおいては、この時刻定義では不十分なことがあり、それは次の理由によります:
- 安定した単調クロックを持たず、結果として システムクロックのズレの影響を受ける。
- ミリ秒未満の時刻分解能が提供されない。
この仕様では
Date.now() [ECMA-262]
の挙動を変更することは提案していません。
Date.now() はカレンダー時刻の現在値の取得に便利であり、
歴史的にも広く利用されています。
DOMHighResTimeStamp
型、
Performance.now()
メソッド、
そして
Performance.timeOrigin
属性(これらのメソッドと属性は Performance インターフェイスに属します)が上記課題を解決し、ミリ秒未満分解能で単調増加する時刻値を提供します。
サブミリ秒分解能の提供はこの仕様の必須要素ではありません。 実装はプライバシーやセキュリティの理由で 露出するタイマー分解能を制限し、 サブミリ秒タイマーを公開しないこともできます。 サブミリ秒分解能に依存するユースケースは、 その場合満たされないことがあります。
1.1. ユースケース
このセクションは非規範的です。本仕様は以下のような能力を定義します:安定した単調クロックに基づき、コンテキスト間で比較可能で、 サブミリ秒分解能も選択的に持つタイムスタンプを提供します。
パフォーマンス測定に安定した単調クロックが必要な理由は、 無関係なクロックのズレによって測定値が歪み、意味を失うためです。 たとえばドキュメントへのナビゲーション、リソースのフェッチ、スクリプト実行の経過時間を正確に計測するには、 サブミリ秒分解能で単調増加するクロックが求められます。
コンテキスト間でのタイムスタンプ比較は、例えば
Worker
とメインスレッド間で作業を同期する際や、
タイムライン全体の統一ビューを作るべくイベントを計測する際に必要不可欠です。
最後に、サブミリ秒タイマーが必要になるユースケースは次の通りです:
- サブミリ秒単位で作業をスケジューリングできること。 これはメインスレッド上で特に重要であり、 処理が短く規則的な間隔でフレーム描画と干渉しない必要があり、 ユーザーに目に見えるジャンクを避けたい場合に重要です。
- スクリプトベースのアニメーションのフレームレートを計算する際、 アニメーションが60FPSで描画されているかを判定するにはサブミリ秒分解能が必要です。 これがない場合、開発者は58.8FPS(1000ms / 16)か62.5FPS(1000ms / 17)かしか判定できません。
- 野生環境下でJSコード(例:User-Timing)の実測値を収集する際、 開発者は関数のサブミリ秒計測値を取得し、早期にパフォーマンス退化を検知したい事があります。
- アニメーションの特定のタイミングでオーディオを鳴らしたり、 オーディオとアニメーションが完全に同期するよう調整する際、 経過時間を正確に測る必要があります。
1.2. 例
このセクションは非規範的です。
開発者は、Worker
や SharedWorker
等異なる time origins のイベントを含む
アプリ全体のタイムラインを構築したい場合があります。同じタイムラインに描画するには、
DOMHighResTimeStamp
を
Performance.timeOrigin
属性で変換できます。
// ---- worker.js ----------------------------- // Shared worker script onconnect= function ( e) { var port= e. ports[ 0 ]; port. onmessage= function ( e) { // Time execution in worker var task_start= performance. now(); result= runSomeWorkerTask(); var task_end= performance. now(); } // Send results and epoch-relative timestamps to another context port. postMessage({ 'task' : 'Some worker task' , 'start_time' : task_start+ performance. timeOrigin, 'end_time' : task_end+ performance. timeOrigin, 'result' : result}); } // ---- application.js ------------------------ // Timing tasks in the document var task_start= performance. now(); runSomeApplicationTask(); var task_end= performance. now(); // developer provided method to upload runtime performance data reportEventToAnalytics({ 'task' : 'Some document task' , 'start_time' : task_start, 'duration' : task_end- task_start}); // Translating worker timestamps into document’s time origin var worker= new SharedWorker( 'worker.js' ); worker. port. onmessage= function ( event) { var msg= event. data; // translate epoch-relative timestamps into document’s time origin msg. start_time= msg. start_time- performance. timeOrigin; msg. end_time= msg. end_time- performance. timeOrigin; reportEventToAnalytics( msg); }
2. 時間の概念
2.1. クロック
クロック(clock) は時の経過を追跡し、 アルゴリズムの手順実行中の 非安全な現在時刻(unsafe current time) を報告できます。 様々な種類のクロックがあります。ウェブプラットフォーム上での全てのクロックは 実時間1ミリ秒につき1ミリ秒を数えようとしますが、正確に一致しない場合の扱いは異なります。
- The wall clock’s unsafe current time
-
は常にユーザーの時間の認識に可能な限り近い。コンピューターは時に遅く または進んだり時刻を見失ったりするため、その wall clock は時々調整する必要があり、これは unsafe current time が減少することがあり、これにより パフォーマンス測定や出来事の順序の記録には信頼できなくなる。Web プラットフォームは wall clock を [ECMA-262] の time を共有する。
- The monotonic clock’s unsafe current time
-
決して減少しないため、システムクロックの調整で変更されることはない。 monotonic clock は単一の実行の user agent 内にのみ存在するため、 異なる実行で発生する可能性のある出来事を比較するためには使用できない。
単調クロックの monotonic clock はユーザーの時間の認識に合わせて調整できないため、ユーザーに表示される時刻ではなく測定に使用するべきである。ユーザーとの時間のやり取りには wall clock を使用する。
ユーザーエージェントは、ブラウザーの再起動時、隔離された閲覧セッション(例:
シークレットモードやそれに類する閲覧モード)を開始したとき、あるいは既存の設定オブジェクトと通信できない environment settings object を作成したときに、新しい estimated monotonic time of the Unix
epoch
を選ぶことがある。その結果、開発者は共有タイムスタンプを過去・現在・未来のすべてのコンテキストにまたがって単調性を保持する絶対時刻として使用すべきではない。実際には、単調性は提供されたメッセージング機構のいずれか(例:
postMessage(message, options)、
BroadcastChannel
など)を介してメッセージを交換することで互いに到達できるコンテキストにのみ適用される。
特定の状況(例: タブがバックグラウンドになった場合)では、ユーザーエージェントはそのコンテキストで実行されるタイマーや定期的なコールバックの実行を制限したり完全に停止したりすることを選択する場合がある。そのような制限があっても、単調クロックが返す時刻の分解能や精度には影響を与えるべきではない。
2.2. モーメントと持続時間
それぞれの クロックの unsafe current time は unsafe moment(危険な瞬間) を返します。Coarsen time(粗化時間) はこれらの unsafe moment(危険な瞬間) を coarsened moment(粗化瞬間)、または単に moment(瞬間) に変換します。unsafe moment や moment は異なるクロック間では比較できません。
moment や unsafe moment は時刻の一点を表すため、直接数値として保存することはできません。実装では 通常 moment を、他の時刻からの duration(持続時間) で表しますが、仕様は moment 自身を扱うべきです。
duration(持続時間) は、ある moment から、同じ clock(クロック) 上の別の時点までの距離です。どちらの端点も unsafe moment であってはならないので、 duration やduration 同士の差分が [§ 9.1 クロック分解能] の懸念を緩和します。 duration は ミリ秒・秒などで測定されます。すべての clock が同じ速度で刻むことを目指すので、 duration にはクロックの関連付けがなく、 一つのクロック上の二つの moment から計算した duration を 別の moment(別の clock)に加えることで、 その二つ目のクロック上の別の moment を得ることができます。
duration from a から b まで は次のアルゴリズムの結果です:
- 断言: a は b と同じ clock によって作成された。
- 断言: a と b の両方は 粗視化されたモーメント である。
- a から b までの時間量を duration として返す。もし b が a より前であった場合、これは負の duration になる。
duration は暗黙的に
DOMHighResTimeStamp
として利用できます。
duration を暗黙的にタイムスタンプへ変換するには、
duration d を与えられたとき、
d のミリ秒数を返します。
3. 仕様書著者のためのツール
単一ページ(単一の
環境設定オブジェクトのコンテキスト)内で時間を測定するには、
settingsObject の 現在の相対タイムスタンプを使用する。これは
settingsObject の タイムオリジン から settingsObject の 現在の単調時刻
までの duration from
として定義される。この値は
持続時間(duration) の
暗黙的変換によって DOMHighResTimeStamp
として JavaScript から直接参照できる。
単一の UA 実行内で時間を測定したい場合で、環境設定オブジェクトの タイムオリジン が適切な比較基準でない場合は、 瞬間(moment) を、環境設定オブジェクトの 現在の単調時刻 を使って作成する。 環境設定オブジェクト settingsObject の 現在の単調時刻 は、次の手順の結果である:
- unsafeMonotonicTime を 単調クロックの unsafe current time とする。
- unsafeMonotonicTime と settingsObject の cross-origin isloated capability(クロスオリジン隔離機能) を使って 粗化時間(coarsen time) を呼び出した結果を返す。
モーメント(単調クロック由来) はJavaScriptやHTTPで直接表現できません。 代わりに、そのような2つのモーメント間の 持続時間を公開します。
複数のUA実行をまたいで時間を計測するには、モーメントを 現在の粗視化ウォールタイムや (cross-origin-isolated contextsでより高い精度が必要なら) environment settings objectの current wall time を使って作成してください。 現在の粗視化ウォールタイム とは、ウォールクロックの 非安全な現在時刻を coarsen time で処理した結果です。
環境設定オブジェクト settingsObject の 現在の wall time は次の手順の結果である:
- unsafeWallTime を wall clock の unsafe current time とする。
- unsafeWallTime と settingsObject の クロスオリジン隔離機能 を使って 粗化時間(coarsen time) を呼び出した結果を返す。
ウォールクロックから得たモーメントを利用する際は、 ユーザーが自分の時計を進めたり戻したりした場合にも設計が対応できていることを確認してください。
ウォールクロックからのモーメントは、
Unixエポックからそのモーメントまでのミリ秒数を
Date
コンストラクタに渡すことでJavaScript上で表現できます。または、
Unixエポックからそのモーメントまでのナノ秒数を
Temporal.Instant
コンストラクタに渡すこともできます。[Temporal]
このような表現をコンピュータ間でやりとりするのは避けてください。 そうするとユーザーの時計のずれが漏れ、トラッキングベクトルとなってしまいます。 代わりに、単調クロックの モーメントと同様、 2つのモーメント間の継続時間を送る方式を使ってください。
3.1. 例
エラー報告の経過時間は次のように計算できる:
後で:
-
data を次のキー/値ペアを持つマップとする:
- age
-
report の 生成時刻 と
context の 関連する settings object の 現在の単調時刻 の差(ミリ秒)、最も近い整数に丸めた値。
- ...
複数日をまたぐアトリビューションレポートの有効期限処理は次のようになる:
-
source を新しいアトリビューションソース構造体とし、そのアイテム群は:
-
...
- source time
- context の 現在のwall time
- expiry
-
duration
文字列の解析(
value["expiry"])
-
...
数日後:
- もし context の 現在のwall time が source の source time + source の expiry より小さい場合、 レポートを送信する。
4. タイムオリジン(Time Origin)
Unixエポックは、 ウォールクロック上の 1970年1月1日 00:00:00 UTCに対応する モーメントです。
いずれかの方法で通信の可能性があるenvironment settings objectの各グループには、 Unixエポックの推定単調時刻があり、 それは単調クロック上の モーメントであり、 次の手順で初期化されます:
- wall timeをウォールクロックの非安全な現在時刻とする。
- monotonic timeを 単調クロックの 非安全な現在時刻とする。
- epoch timeを
monotonic time - (wall time - Unixエポック)とする。 - Unixエポックの推定単調時刻 を、epoch timeでcoarsen timeを呼び出した結果で初期化する。
Worker
も含みます。
パフォーマンス計測は、該当するenvironment settings objectの初期化の早い段階での モーメント からの持続時間を報告します。 そのモーメントは、そのsettings objectの time originに格納されます。
time origin タイムスタンプの取得は、グローバルオブジェクト globalを受け取り、次の手順を実行して 持続時間を返します:
-
timeOrigin を globalの 関連settings objectのtime originとする。
Windowコンテキストでは、この値は ナビゲーションが開始 された時刻を表します。WorkerやServiceWorkerコンテンツでは、この値は workerが実行 された時刻を表します。[SERVICE-WORKERS] - Unixエポックの推定単調時刻から timeOriginまでの 持続時間 を返す。
time origin タイムスタンプの取得で返された値は、 おおよそglobalのUnixエポック 以降のtime origin発生時刻です。 これはその時点で Date.now() を実行したときに返される値と異なる場合があります。 前者は単調クロック基準で記録され、 システムやユーザーの時計調整や、クロックスキュー等の影響を受けません。
- coarse time を coarsen time(timeとglobalの関連settings objectの cross-origin isolated capability)の結果とする。
- coarse timeとglobalに対する relative high resolution coarse timeを返す。
current high resolution timeは、 グローバルオブジェクト current global を受け取り、 unsafe shared current time と current global で relative high resolution timeを呼び出した結果を返さなければならない。
coarsened shared current timeは、 オプションの真偽値crossOriginIsolatedCapability(デフォルトはfalse)を受け取り、 coarsen timeを unsafe shared current timeと crossOriginIsolatedCapabilityで呼び出した結果を返す必要がある。
unsafe shared current timeは、 単調クロック の 非安全な現在時刻を返さなければならない。
5. DOMHighResTimeStamp 型定義
DOMHighResTimeStamp
型は、ミリ秒単位の期間を保存するために使われます。文脈によっては、基準となるモーメント(たとえば time origin や Unixエポック)からこの期間だけ経過した
モーメントを表すこともあります。
typedef double ;DOMHighResTimeStamp
DOMHighResTimeStamp
は、計測のために十分な精度を持ちつつ、タイミング攻撃を防げる程度のミリ秒単位の時刻を表現すべきです。詳細な考慮事項は § 9.1
クロックの分解能を参照してください。
DOMHighResTimeStamp
は double
なので、
Unixエポックから
モーメントまでの
ミリ秒数という形でしか有限の分解能で時刻を表わせません。2023年時点のモーメントでは、
この分解能はおよそ0.2マイクロ秒です。
6. EpochTimeStamp 型定義
typedef unsigned long long ;EpochTimeStamp
EpochTimeStamp
は、UNIXエポックから
壁時計(wall clock)上の
特定の 瞬間(moment) までの
うるう秒を除いた整数ミリ秒数を表します。この型を使う仕様は、ミリ秒数の解釈方法を定義します。
7. Performance インターフェース
[Exposed =(Window ,Worker )]interface :Performance EventTarget {DOMHighResTimeStamp ();now readonly attribute DOMHighResTimeStamp timeOrigin ; [Default ]object (); };toJSON
7.1.
now() メソッド
now() メソッドは、 現在の高分解能時刻を this の 関連グローバルオブジェクト(duration(持続時間))で与えられたものとして、 ミリ秒数を返さなければならない。
Performance
オブジェクト上で now()
メソッドを呼び出したときに返る時刻値は、
同じ
タイムオリジン(time origin) を持つ場合、
必ず同じ
単調クロック(monotonic
clock)
を用いなければならない。
now()
メソッドから時系列順に記録された任意の2つの時刻値の差は、
もし2つの時刻値が同じ
タイムオリジン を持つならば、
決して負の値になってはならない。
7.2.
timeOrigin 属性
timeOrigin 属性は、期間の
ミリ秒数を返さなければならない。この期間は、時刻起点タイムスタンプを取得するによって、関連するグローバルオブジェクト、すなわち this のものに対して返される。
Performance.timeOrigin
を取得するときに返される時刻値は、
単調増加クロックと同じものを
使用しなければならない。このクロックは時刻起点によって共有され、その基準点は [ECMA-262]
の時刻
の定義である。— [§ 9 セキュリティに関する考慮事項] を参照。
7.3.
toJSON() メソッド
toJSON() の呼び出し時には、[WEBIDL]の default toJSON steps を実行します。
8.
WindowOrWorkerGlobalScope mixin への拡張
8.1.
performance 属性
performance 属性は、インターフェイスミックスイン
WindowOrWorkerGlobalScope
上で、グローバルオブジェクトからパフォーマンス関連の
属性およびメソッドへのアクセスを可能にする。
partial interface mixin WindowOrWorkerGlobalScope { [Replaceable ]readonly attribute Performance performance ; };
9. セキュリティの考慮事項
このセクションは規範的ではありません。9.1. クロックの分解能
測定やスケジューリング目的で高精度なタイミング情報へアクセスすることは、多くのアプリケーションで一般的な要件です。 たとえば、ページ上でアニメーションやサウンド、他の動きを協調させるには高分解能の時間へのアクセスが求められ、良好なユーザー体験のために必要です。 同様に、測定は開発者が重要なコードコンポーネントのパフォーマンスを追跡したり、リグレッション(性能後退)を検出したりなどを可能にします。
しかし、同じ高精度なタイミング情報は、攻撃者が他では可視化・アクセスできないデータを推測・解読するために悪用されることもあります。 たとえば、キャッシュ攻撃、統計的フィンガープリンティング、マイクロアーキテクチャ攻撃は、様々なブラウザやアプリケーション主導の操作における高分解能タイミングデータを悪用してユーザの識別や未知の同一プロセスデータの特定に用いられる恐れがあり、プライバシーやセキュリティ問題となります(詳細は [CACHE-ATTACKS] および [SPECTRE] を参照)。
本仕様はミリ秒より細かい分解能(サブミリ秒)の時刻情報APIを定義します。これは従来の EpochTimeStamp
で公開されていたミリ秒分解能よりも高精度です。
しかし、このAPIがなくても攻撃者は実行の繰り返しや統計解析により高分解能の推定値を得られる場合があります。
この新たなAPIが攻撃の精度や速度を有意に高めないようにするため、DOMHighResTimeStamp
型の最小分解能は攻撃を回避できる程度に不正確であるべきです。
必要に応じて、ユーザーエージェントはアーキテクチャやソフトウェアの制約、その他の考慮事項によるプライバシーやセキュリティ上の懸念に対応するため、coarsen time の処理モデルにおける time resolution の値をより大きな分解能に設定するべきである。
このような攻撃を緩和するために、ユーザーエージェントは必要と判断したあらゆる緩和手法を講じることができます。こうした対策の運用は、ブラウザの構成やユーザー端末、コンテンツの性質やクロスオリジンデータへのアクセス可否などによって変動します。
その技術は以下を含みうる:
- 分解能の低下。
- ジッター(わざと揺らぎを加える)付加。
- 悪用検知またはAPI呼出しのスロットリング。
このようなタイミングチャネル攻撃を完全に防止することは事実上不可能です。すべての操作を機密情報の値によらず一定時間で必ず実行するか、アプリケーションを時計・タイマー・カウンタなどあらゆる時間関連プリミティブから隔離しなければなりません。しかし、どちらもブラウザ・アプリ開発者の負担や、アプリケーションのパフォーマンスや応答性への悪影響のため実現的ではありません。
9.2. クロックドリフト
この仕様では、タイムオリジンのゼロ時点についてサブミリ秒分解能の時間を提供する API も定義しており、これはアプリケーションに 単調クロック を必要とし、かつ公開します。 この単調クロックはすべてのブラウザコンテキスト間で共有されなければなりません。 単調クロックは物理時間に結び付ける必要はありませんが、 [ECMA-262] の time 定義に従うよう推奨されます。これはユーザーに関する新しいフィンガープリントエントロピーを公開しないためです ― すなわち、この時刻はすでにアプリケーションから容易に取得できるものであり、 新しい論理クロックを公開すると新たな情報になってしまうためです。
ただし上記の仕組みがあっても、単調クロックはさらに細かい クロックドリフト分解能を提供することがあります。
今日のアプリケーションは、同じコンテキスト内で
時刻と単調時刻(Date.now() と now())を
複数タイミングで記録し、
それらの間のドリフト(例: 自動またはユーザーによる時計調整)を観測できます。
timeOrigin
属性により、攻撃者は
タイムオリジン(単調クロックで報告されるもの)と、
タイムオリジン の現在の時刻推定値(つまり `performance.timeOrigin` と
`Date.now() - performance.now()` の差分)
を比較することで、長期的にクロックドリフトを観測できる可能性があります。
実際には、同じ時間ドリフトはアプリケーションが複数のナビゲーションをまたいで観測できます。 アプリケーションは各コンテキストで論理時刻を記録し、クライアントやサーバーの時刻同期機構を使って ユーザーの時計の変化を推測できます。同様に、TCPタイムスタンプのような下位層の仕組みも、 複数回アクセスせずともサーバーに同様の高分解能情報を明らかにする場合があります。 そのため、このAPIによって提供される情報は、ユーザーに関する重要または未公開だったエントロピーを 新たに公開するべきではありません。
10. プライバシーの考慮事項
このセクションは規範的ではありません。
Document
のタイムオリジンの現在の定義は、
当該ドキュメントのオリジンにリクエストが到達する前のクロスオリジンリダイレクト全体の所要時間を公開します。これはクロスオリジン情報の公開となりますが、これによりパフォーマンス指標へ大きな悪影響なしでどのように緩和するかはまだ決まっていません。
議論の経過は Navigation Timing Issue 160 を参照してください。
11. 謝辞
本仕様の作成に貢献したArvind Jain、Angelos D. Keromytis、Boris Zbarsky、Jason Weber、Karen Anderson、Nat Duca、Philippe Le Hegaret、Ryosuke Niwa、 Simha Sethumadhavan、Todd Reifsteck、Tony Gentilcore、Vasileios P. Kemerlis、Yoav Weiss、Yossef Oren の各氏に感謝します。