1. はじめに
performance.measureUserAgentSpecificMemory()
APIを定義します。
新しいAPIは、本番環境でのメモリ使用量データの集計を目的としています。主な利用ケースは以下の通りです:
-
ウェブアプリケーションの新バージョンのロールアウト時に新たなメモリリークを検出するためのリグレッション検出。
-
新機能のA/Bテストでそのメモリへの影響を評価すること。
-
ユーザーメトリクスとの相関により、メモリ使用量が全体的に与える影響を理解すること。
1.1. 例
performance.measureUserAgentSpecificMemory()
の呼び出しはPromise
を返し、
ページによって割り当てられたメモリの非同期測定を開始します。
async function run() { const result= await performance. measureUserAgentSpecificMemory(); console. log( result); } run();
iframeやworkerを含まないシンプルなページの場合、結果は次のようになります:
ここでは、すべてのメモリがメインページに帰属しています。{ bytes: 1000000 , breakdown: [ { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], types: [ "JS" , "DOM" ], }, { bytes: 0 , attribution: [], types: [], }, ], }
bytes: 0のエントリは、特定のエントリをハードコーディングせず、ジェネリックな方法で結果を処理することを推奨するためにbreakdownリストに存在します。
リストが空でなければ、このようなエントリがランダムな位置に挿入されます。
他の有効な結果の例:
ここでは、実装は総メモリ使用量のみを提供しています。{ bytes: 1000000 , breakdown: [], }
ここでは実装がメモリタイプ別に分解を行っていません。{ bytes: 1000000 , breakdown: [ { bytes: 0 , attribution: [], types: [], }, { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], types: [], }, ], }
同一オリジンiframeを埋め込むページの場合、そのiframeに一部のメモリが帰属され、 iframeを識別するための診断情報が提供される場合があります:
< html > < body > < iframe id = "example-id" src = "redirect.html?target=iframe.html" ></ iframe > </ body > </ html >
{ bytes: 1500000 , breakdown: [ { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], types: [ "DOM" , "JS" ], }, { bytes: 0 , attribution: [], types: [], }, { bytes: 500000 , attribution: [ { url: "https://example.com/iframe.html" container: { id: "example-id" , src: "redirect.html?target=iframe.html" , }, scope: "Window" , } ], types: [ "JS" , "DOM" ], }, ], }
iframeのurlとcontainer.srcフィールドがどのように異なるかに注目してください。
前者はiframeの現在のlocation.hrefを反映し、
後者はsrc属性値です。
iframeのメモリをページのメモリから意味のある形で分割できるとは限りません。 実装によっては、iframeとページのメモリをまとめて扱う場合も許容されています:
{ bytes: 1500000 , breakdown: [ { bytes: 1500000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, { url: "https://example.com/iframe.html" , container: { id: "example-id" , src: "redirect.html?target=iframe.html" , }, scope: "Window" , }, ], types: [ "JS" , "DOM" ], }, { bytes: 0 , attribution: [], types: [], }, ], };
実装によってはworkerとページのメモリをまとめて計上する場合があります。iframeがworkerを生成した場合、workerのattributionエントリにはiframe要素に対応する{ bytes: 1800000 , breakdown: [ { bytes: 0 , attribution: [], types: [], }, { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], types: [ "JS" , "DOM" ], }, { bytes: 800000 , attribution: [ { url: "https://example.com/worker.js" , scope: "DedicatedWorkerGlobalScope" , }, ], types: [ "JS" ], }, ], };
containerフィールドが入ります。
共有およびサービスワーカーのメモリは結果に含まれません。
以下の構成のページを考えます:
exampleクロスオリジンiframeが他のiframeやworkerを内包しています。 これら全リソースのメモリは、最初のiframeに帰属されます。. com( 1000000 bytes) | *-- foo. com/ iframe1( 500000 bytes) | *-- foo. com/ iframe2( 200000 bytes) | *-- bar. com/ iframe2( 300000 bytes) | *-- foo. com/ worker. js( 400000 bytes)
< html > < body > < iframe id = "example-id" src = "https://foo.com/iframe1" ></ iframe > </ body > </ html >
クロスオリジンiframeの{ bytes: 2400000 , breakdown: [ { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], types: [ "JS" , "DOM" ], }, { bytes: 0 , attribution: [], types: [], }, { bytes: 1400000 , attribution: [ { url: "cross-origin-url" , container: { id: "example-id" , src: "https://foo.com/iframe1" , }, scope: "cross-origin-aggregated" , }, ], types: [ "DOM" , "JS" ], }, ], }
urlおよびscopeフィールドは、情報が利用できないことを示す特別な値となることに注意してください。
実装がクロスオリジンiframeを異なるアドレス空間でロードする場合、そのメモリ使用量は測定されません。
そのようなiframeのbreakdownエントリはbytes: 0となり、対象外であることを示します:
{ bytes: 100000 , breakdown: [ { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], types: [ "JS" , "DOM" ], }, { bytes: 0 , attribution: [ { url: "cross-origin-url" , container: { id: "example-id" , src: "https://foo.com/iframe1" , }, scope: "cross-origin-aggregated" , }, ], types: [ "JS" , "DOM" ], }, { bytes: 0 , attribution: [], types: [], }, ], }
location.hrefにアクセスできるため、情報漏洩はありません。
example. com( 1000000 bytes) | *-- foo. com/ iframe1( 500000 bytes) | *-- example. com/ iframe2( 200000 bytes)
< html > < body > < iframe id = "example-id" src = "https://foo.com/iframe1" ></ iframe > </ body > </ html >
{ bytes: 1700000 , breakdown: [ { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], types: [ "DOM" , "JS" ], }, { bytes: 0 , attribution: [], types: [], }, { bytes: 500000 , attribution: [ { url: "cross-origin-url" , container: { id: "example-id" , src: "https://foo.com/iframe1" , }, scope: "cross-origin-aggregated" , }, ], types: [ "DOM" , "JS" ], }, { bytes: 200000 , attribution: [ { url: "https://example.com/iframe2" , container: { id: "example-id" , src: "https://foo.com/iframe1" , }, scope: "Window" , }, ], types: [ "JS" , "DOM" ], }, ], }
実装がクロスオリジンiframeのメモリ測定を省略した場合、結果は次のようになる場合もあります:
{ bytes: 1200000 , breakdown: [ { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], types: [ "JS" , "DOM" ], }, { bytes: 0 , attribution: [ { url: "cross-origin-url" , container: { id: "example-id" , src: "https://foo.com/iframe1" , }, scope: "cross-origin-aggregated" , }, ], types: [ "JS" , "DOM" ], }, { bytes: 200000 , attribution: [ { url: "https://example.com/iframe2" , container: { id: "example-id" , src: "https://foo.com/iframe1" , }, scope: "Window" , }, ], types: [ "JS" , "DOM" ], }, { bytes: 0 , attribution: [], types: [], }, ], }
2. データモデル
2.1. メモリ測定結果
performance.measureUserAgentSpecificMemory()
関数は Promise
を返し、MemoryMeasurement
辞書のインスタンスに解決されます:
dictionary {MemoryMeasurement unsigned long long ;bytes sequence <MemoryBreakdownEntry >; };breakdown
-
measurement .bytes -
合計メモリ使用量を表す数値。
-
measurement .breakdown -
合計
bytesを分割し、帰属情報やタイプ情報を提供する配列。
dictionary {MemoryBreakdownEntry unsigned long long ;bytes sequence <MemoryAttribution >;attribution sequence <DOMString >; };types
-
breakdown .bytes -
このエントリが記述するメモリのサイズ。
-
breakdown .attribution -
そのメモリを利用しているJavaScript realmのURLおよび/またはコンテナ要素の配列。
-
breakdown .types -
メモリに関連付けられた実装依存のメモリ種別の配列。
dictionary {MemoryAttribution USVString ;url MemoryAttributionContainer ;container DOMString ; };scope
-
attribution .url -
この帰属が同一オリジンのJavaScript realmに対応する場合、このフィールドはrealmのURLを含みます。 それ以外の場合、帰属は1つまたは複数のクロスオリジンJavaScript realm向けで、このフィールドはセンチネル値
"cross-origin-url"となります。 -
attribution .container -
JavaScript realmを(間接的に)内包するDOM要素を記述します。 このプロパティは、同一オリジンのトップレベルrealmの帰属では省略される場合があります。 クロスオリジンrealmはクロスオリジン分離のため、トップレベルになれない点に注意してください。
-
attribution .scope -
同一オリジンJavaScript realmの種別を記述します。
"Window", "DedicatedWorkerGlobalScope", "SharedWorkerGlobalScope", "ServiceWorkerGlobalScope"またはクロスオリジン用の"cross-origin-aggregated"。
dictionary {MemoryAttributionContainer DOMString ;id USVString ; };src
2.2. 中間メモリ測定
本仕様は、与えられた現在のエージェントクラスタのアドレス空間内で、指定されたエージェントクラスタ集合のメモリ使用量を測定する実装依存アルゴリズムの存在を仮定します。 このようなアルゴリズムの結果は中間メモリ測定であり、 これは集合としての中間メモリ分解エントリからなります。
アドレス空間分離のセキュリティ保証を維持するため、現在のアドレス空間外のメモリを表す中間メモリ分解エントリはbytesを0にする必要があります。
フィンガープリンティングリスク低減のため、 結果には与えられたエージェントクラスタ集合によって割り当てまたは利用されたWebプラットフォームオブジェクトに関連するメモリのみが含まれる必要があります。 例えばこれにはユーザーエージェント固有拡張や空ページのベースラインメモリは含まれません。 メモリはアドレス空間レベルで計上し、メモリ圧縮や遅延確保などプラットフォーム固有最適化は除外する必要があります。
- bytes
-
この中間メモリ分解エントリが記述するメモリサイズ、または現在のアドレス空間外の場合は0。
- realms
-
そのメモリが割り当てられるJavaScript realmの集合。
- types
本仕様で定義されるアルゴリズムは、中間メモリ測定をMemoryMeasurement
インスタンスに変換する方法を示します。
2.3. メモリ帰属トークン
埋め込みJavaScript realmとそのコンテナ要素のリンクは一時的で、常に存在が保証されるものではありません。 例えば、コンテナ要素で他のドキュメントへナビゲートしたり、DOMツリーから要素を削除した場合、このリンクは切断されます。
メモリ帰属トークンは、JavaScript realmからそのコンテナ要素への参照方法を提供します。 これは構造体で、以下の項目を持ちます:
- container
-
MemoryAttributionContainerのインスタンス。 - cross-origin aggregated flag
-
このトークンがクロスオリジンJavaScript realmのメモリ集計用として作成されたかどうかを示すブールフラグ。
この値はWindowOrWorkerGlobalScope
の新しい内部フィールドに構築時保持され、常にメモリレポートで利用可能です。
3. 処理モデル
3.1.
Performance
インターフェースへの拡張
partial interface Performance { [Exposed =(Window ,ServiceWorker ,SharedWorker ),CrossOriginIsolated ]Promise <MemoryMeasurement >measureUserAgentSpecificMemory (); };
-
performance .measureUserAgentSpecificMemory() -
非同期メモリ測定を行うメソッド。このメソッドの結果詳細は §2.1 メモリ測定結果 を参照。
3.2. トップレベルアルゴリズム
measureUserAgentSpecificMemory()
メソッドの手順:
-
アサート:現在のRealmのsettings objectのクロスオリジン分離機能がtrueであること。
-
memory measurement allowed predicate を現在のRealmで評価し、偽であれば:
-
現在のエージェントクラスタを、現在のRealmのagentのエージェントクラスタとする。
-
agent clusters を get all agent clustersに現在のRealmを渡して実行した結果とする。
-
新しい
Promiseをpromiseとする。 -
非同期で実装依存メモリ測定を現在のエージェントクラスタ、agent clusters、promiseを用いて開始する。
-
promiseを返す。
-
global object を realm の global object とする。
-
global objectが
SharedWorkerGlobalScopeならtrueを返す。 -
global objectが
ServiceWorkerGlobalScopeならtrueを返す。 -
global objectが
Windowなら-
settings object を realm の settings objectとする。
-
settings object のoriginが settings objectのトップレベルoriginと同じならtrueを返す。
-
-
falseを返す。
-
realmのglobal objectが
Windowのとき:-
group を browsing context groupで、realmのglobal objectのbrowsing contextを含むものとする。
-
groupのagent cluster mapの値取得結果を返す。
-
-
« realmのagentのエージェントクラスタ » を返す。
Promise
promise
を受けて 並列に以下を実行:
-
intermediate memory measurement を現在のエージェントクラスタとagent clustersで実装依存の中間メモリ測定の実行結果とする。
-
promiseのrelevant global objectに、グローバルタスクをキューイングし、 resolve promiseを新しいMemoryMeasurementの生成(intermediate memory measurement渡し)で解決する。
3.3. 中間メモリ測定を結果に変換する
-
bytes を0とする。
-
それぞれの 中間メモリ分解エントリ intermediate entry をintermediate measurementから取り出して:
-
bytes に bytes + intermediate entry の bytes を設定。
-
-
breakdown を新しいリストとする。
-
新規に
MemoryBreakdownEntryをbreakdownへ追加する。この:-
bytesは0, -
attributionは « », -
typesは « »。
-
-
それぞれの 中間メモリ分解エントリ intermediate entry をintermediate measurementから取り出して:
-
breakdown entry を 新しいメモリ分解エントリを作成する (intermediate entry ) の結果とする。
-
追加 breakdown entry をbreakdownへ。
-
-
breakdown 内の項目順をランダム化する。
-
新しい
MemoryMeasurementを返す。これは:
-
attribution を新しいリストとする。
-
それぞれの JavaScript realm realm を intermediate entry の realms から取り出して:
-
attribution entry を 新しいメモリ帰属を作成する (realm) の結果とする。
-
追加 attribution entry を attribution へ。
-
-
types を intermediate entry の types とする。
-
types 内の項目順をランダム化する。
-
新しい
MemoryBreakdownEntryを返す。これは:-
attributionは attribution, -
typesは types。
-
token を realm の global object の メモリ帰属トークン とする。
-
token の cross-origin aggregated flag がtrueなら:
-
scope name を identifier として、realm の global object のinterface名を取得。
-
新しい
MemoryAttributionを返す。これは:-
urlは realm の settings object の creation URL、 -
containerは token の container(nullでなければ)、そうでなければcontainer項目は省略される, -
scopeは scope name。
-
3.4. メモリ帰属トークンの作成または取得
HTMLElement
container element、およびメモリ帰属トークン parent tokenを与える:
-
container element がnullの場合:
-
parent origin がtop-level originと等しくない場合:
-
parent token を返す。
-
-
container を コンテナ要素属性抽出(container element) の結果とする。
-
origin がtop-level originと等しい場合:
-
新しいメモリ帰属トークンを返す。これは:
-
containerは container,
-
cross-origin aggregated flag はfalse。
-
-
-
新しいメモリ帰属トークンを返す。これは:
-
containerは container,
-
cross-origin aggregated flag はtrue。
-
WorkerGlobalScope
worker global scope、environment settings object outside settings
を与える:
-
worker global scope が
DedicatedWorkerGlobalScopeの場合、 outside settings の global object の メモリ帰属トークン を返す。 -
アサート:worker global scope は
SharedWorkerGlobalScopeまたはServiceWorkerGlobalScopeである。 -
新しいメモリ帰属トークンを返す。これは:
-
containerはnull,
-
cross-origin aggregated flag はfalse。
-
HTMLElement
container element を与える:
-
container element の local name を判定:
- "iframe"
-
新しい
MemoryAttributionContainerを返す。これは: - "frame"
-
新しい
MemoryAttributionContainerを返す。これは: - "object"
-
新しい
MemoryAttributionContainerを返す。これは:
4. 既存仕様との統合
4.1. WindowOrWorkerGlobalScopeへの拡張
新しい内部フィールドがWindowOrWorkerGlobalScopeに追加される:
- メモリ帰属トークン
-
この環境のメモリ使用量を報告するのに使われるメモリ帰属トークン。
4.2. 既存アルゴリズムへの拡張
ワーカーの実行アルゴリズムは、ステップ6で新たに作成されるグローバルオブジェクトの メモリ帰属トークンフィールドを設定する:
realm execution context を agent と以下カスタマイズで新しい JavaScript realm を作成した結果とする:
...
グローバルオブジェクトの メモリ帰属トークンを グローバルオブジェクトとoutside settingsを与えて ワーカーメモリ帰属トークン取得した結果に設定。
Document オブジェクトの作成および初期化アルゴリズムは、 新たに作成されるグローバルオブジェクトのメモリ帰属トークンフィールドを設定する:
それ以外の場合:
token を空のメモリ帰属トークンとする。
browsingContext がトップレベルでない場合:
parentToken を parentEnvironmentのグローバルオブジェクトのメモリ帰属トークンとする。
token に origin、 parentEnvironment のorigin、topLevelOrigin、 browsingContextのcontainer、parentToken を与えて windowメモリ帰属トークン取得した結果を設定。
そうでなければ、origin、null topLevelOrigin、null、null で windowメモリ帰属トークン取得し、 その結果をtokenに設定。
window global scope を realm execution contextの Realm コンポーネントのグローバルオブジェクトとする。
window global scopeのメモリ帰属トークンにtokenを設定。
新しいブラウジングコンテキストの作成アルゴリズムは、 新たに作成されるグローバルオブジェクトのメモリ帰属トークンフィールドを設定する:
token を空のトークンとする。
embedder がnullの場合、token にorigin、 null、topLevelOrigin、null、null で windowメモリ帰属トークン取得した結果を設定。
そうでなければ、tokenに origin、embedderのrelevant settings objectのorigin、 topLevelOrigin、embedder、embedderのrelevant global object のメモリ帰属トークンを与えて windowメモリ帰属トークン取得の結果を設定。
window global scope を realm execution contextの Realm コンポーネントのグローバルオブジェクトとする。
window global scopeのメモリ帰属トークンにtokenを設定。
5. プライバシーとセキュリティ
5.1. クロスオリジン情報漏洩
結果に現れるURLや他の文字列値は、APIを呼び出したオリジンが知っていることが保証されている。
クロスオリジンで公開される唯一の情報は、memoryMeasurement.bytes
とmemoryBreakdownEntry.bytes
で提供されるサイズ情報である。
APIはクロスオリジン分離
メカニズムによりクロスオリジンのサイズ情報漏洩を緩和している。
すなわち、現在のアドレス空間内のすべてのリソースが埋め込み元オリジンによる埋め込みや認識を許可しているという不変式を前提とする。
異なるアドレス空間でロードされるクロスオリジンリソースのサイズはAPIで公開されない。
5.2. フィンガープリンティング
このAPIの結果は、そのウェブページ自身が割り当てたオブジェクトのみに依存し、空のウェブページの基準メモリ使用量など、無関係なメモリは含まれない。 つまり、同一のバイナリのユーザーエージェントが異なる端末であっても、同じ固定ページに対しては同じ結果となるべきである。
ウェブページはユーザーエージェントについて以下の情報を推定可能である:
-
ユーザーエージェントのビット幅(32bit/64bit)
-
ある程度までユーザーエージェントのバージョン
同様の情報は既存API(navigator.userAgent、
navigator.platformなど)でも得られる。
ユーザーエージェントのビット幅は32/64bit演算の実行時間測定からも推定できる。
現状、このAPIはトップレベルオリジンだけが利用できる。 将来的にはトップレベルオリジンがPermissions Policyで他のオリジンへAPI利用を委譲可能になる予定である。 いずれの場合も、クロスオリジンiframeはデフォルトではAPIにアクセスできない。
6. 謝辞
Domenic Denicola氏、Shu-yu Guo氏にAPI設計貢献および仕様レビューの謝意を示す。また、Adam Giacobbe氏、Anne van Kesteren氏、Artur Janc氏、Boris Zbarsky氏、Chris Hamilton氏、Chris Palmer氏、Daniel Vogelheim氏、Dominik Inführ氏、Hannes Payer氏、Joe Mason氏、Kentaro Hara氏、L. David Baron氏、Mathias Bynens氏、Matthew Bolohan氏、Michael Lippautz氏、Mike West氏、Neil Mckay氏、Olga Belomestnykh氏、Per Parker氏、Philipp Weis氏、Yoav Weiss氏にフィードバックおよび貢献の謝意を示す。