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
属性が上記課題を解決し、ミリ秒未満分解能で単調増加する時刻値を提供します。
サブミリ秒分解能の提供はこの仕様の必須要素ではありません。 実装はプライバシーやセキュリティの理由で 露出するタイマー分解能を制限し、 サブミリ秒タイマーを公開しないこともできます。 サブミリ秒分解能に依存するユースケースは、 その場合満たされないことがあります。
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 やその差分が [§ 9.1 クロック分解能] の懸念を緩和します。 duration は ミリ秒・秒などで測定されます。すべての clock が同じ速度で刻むことを目指すので、 duration にはクロックの関連付けがなく、 一つのクロック上の moment から計算した duration を 別の moment(別の clock)に加えることで、 その二つ目の moment を得ることができます。
duration from a から b まで は次のアルゴリズムの結果です:
- Assert: a が 同じクロック により生成されたことを確認する。
- Assert: a と b の両方が coarsened moment(粗化瞬間) であることを確認する。
- 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) を呼び出した結果を返す。
単調クロックからの 瞬間(moment) は、JavaScript や HTTP で直接表現できません。代わりに、そのような 瞬間同士の間の 持続時間(duration) を公開してください。
複数の UA 実行をまたぐ時間測定には、瞬間(moment) を 現在の粗化 wall time や(クロスオリジン隔離コンテキストで より高精度が必要な場合は)環境設定オブジェクトの 現在の wall time で作成する。現在の粗化 wall time は 粗化時間(coarsen time) を wall clock の unsafe current time で呼び出す結果である。
環境設定オブジェクト settingsObject の 現在の wall time は次の手順の結果である:
- unsafeWallTime を wall clock の unsafe current time とする。
- unsafeWallTime と settingsObject の クロスオリジン隔離機能 を使って 粗化時間(coarsen time) を呼び出した結果を返す。
wall clock からの 瞬間(moment) を使用する場合は、 ユーザーが時計を前後に調整した場合に備えて設計してください。
wall clock からの
瞬間(moment) は
Unix epoch からその 瞬間 までの
ミリ秒数を Date
のコンストラクタに、または
Unix epoch からその 瞬間 までの
ナノ秒数を Temporal.Instant
のコンストラクタに渡して JavaScript で表現できる。[Temporal]
類似した表現をコンピュータ間で送信するのは避けてください。そうすると ユーザーの時計のずれが露見し、これは トラッキングベクトルとなります。 代わりに、 単調クロックの 瞬間 のように、二つの 瞬間間の持続時間を送信する手法を用いてください。
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 epoch(UNIX エポック) は、 瞬間(moment) であり、 wall clock(壁時計)の上の 1970年1月1日 00:00:00 UTC に対応するものです。
どのような方法でも通信可能な 環境設定オブジェクト(environment settings objects)のグループごとに、 Unix epoch の推定単調時間 が存在します。 これは 単調クロック(monotonic clock) 上の 瞬間(moment) であり、 次の手順で初期化されます:
- wall time を wall clock の unsafe current time とする。
- monotonic time を monotonic clock の unsafe current time とする。
- epoch time を
monotonic time - (wall time - Unix epoch)とする。 - Unix epoch の推定単調時間 を epoch time でもって coarsen time(粗化時間) を呼び出した結果で初期化する。
パフォーマンス測定は、 関連する 環境設定オブジェクト の初期化時の 初期の 瞬間(moment) からの 持続時間(duration) を報告します。 その 瞬間(moment) はその settings object の time origin(タイムオリジン) に格納されます。
タイムオリジン・タイムスタンプを取得するために、 グローバルオブジェクト global が与えられたら、次の手順を実行し 持続時間(duration) を返す:
-
timeOrigin を global の 関連する settings object の タイムオリジン(time origin) とする。
Windowコンテキストでは、この値は ナビゲーション開始時刻 を表す。WorkerやServiceWorkerのコンテンツでは、 worker 実行時刻を表す。[SERVICE-WORKERS] - Unix epoch の推定単調時間 から timeOrigin までの duration from(持続時間) を返す。
タイムオリジン・タイムスタンプ取得で返る値は、 global の タイムオリジンが発生した Unix epoch以降のおおよその時刻である。 これはその時点で Date.now() の値とは異なる場合がある。 前者は 単調クロック(monotonic clock)を基準に記録されるため、 システムやユーザーの時計調整、クロックスキューなどの影響を受けない。
- time resolution(時間分解能)を 100 マイクロ秒、 またはそれ以上の 実装定義(implementation-defined)値とする。
- もし crossOriginIsolatedCapability が true なら time resolution を 5 マイクロ秒、 またはそれ以上の 実装定義値とする。
- 実装定義の方法で timestamp を粗化し、場合によってはジッターを加え、 その分解能が time resolution を超えないようにする。
- timestamp を 瞬間(moment)として返す。
- coarse time を 粗化時間(coarsen time)を time および global の 関連する settings object の クロスオリジン隔離機能 で呼び出した結果とする。
- coarse time と global に対して 相対高分解能粗化時刻を返す。
現在の高分解能時刻(current high resolution time)は、 グローバルオブジェクト current global が指定された場合、 unsafe shared current time(共通危険時刻) と current global を使って 相対高分解能時刻(relative high resolution time)を返さなければならない。
粗化後の共通危険時刻(coarsened shared current time)は、 オプションの boolean crossOriginIsolatedCapability(初期値 false)が指定された場合、 unsafe shared current time(共通危険時刻) と crossOriginIsolatedCapability を使って coarsen time(粗化時間)を呼び出した結果を返さなければならない。
unsafe shared current time(共通危険時刻)は、 単調クロックの unsafe current time(危険な現在時刻) を返さなければならない。
5. DOMHighResTimeStamp 型定義
DOMHighResTimeStamp
型は、持続時間(duration)
をミリ秒単位で保存するために使われます。コンテキストによっては、瞬間(moment) を表すことがあります。
この持続時間だけ基準となる
瞬間(例えば タイムオリジン や 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 属性は、 持続時間(duration) のミリ秒数を返さなければならない。 その値は タイムオリジン・タイムスタンプ取得 により、 関連グローバルオブジェクト( this )に対して得られたものである。
Performance.timeOrigin
を取得する時に返される時刻値は、
単調クロック(monotonic
clock)
であり、
複数の タイムオリジン(time origins) によって共有され、
その基準点は [ECMA-262]
の time 定義である。
(詳しくは [§ 9 セキュリティの考慮事項] を参照)。
7.3.
toJSON() メソッド
toJSON() の呼び出し時には、[WEBIDL]の default toJSON steps を実行します。
8.
WindowOrWorkerGlobalScope mixin への拡張
8.1.
performance 属性
インターフェースミックスイン
WindowOrWorkerGlobalScope
上の performance
属性は、グローバルオブジェクト(global object)からのパフォーマンス関連属性とメソッドのアクセスを可能にします。
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 の各氏に感謝します。