1. 導入
このセクションは規範的ではありません。
ロック要求は、スクリプトによって特定のリソース名とモードに対して行われます。スケジューリングアルゴリズムは、現在および過去の要求の状態を見て、最終的にロック要求を付与します。ロックは付与された要求であり、リソース名とモードを持ちます。これはスクリプトに返されるオブジェクトとして表現されます。ロックが保持されている限り(名前やモードによっては)他のロック要求が付与されない可能性があります。ロックはスクリプトによって解放することができ、解放されると他のロック要求が付与される場合があります。
このAPIは必要に応じて利用できるオプション機能を提供します:
-
非同期タスクから値を返すこと、
-
共有および排他的ロックモード、
-
条件付き取得、
-
ロックの状態の診断用クエリ、
-
デッドロックを防ぐためのエスケープ手段。
協調的な調整は、エージェントがストレージバケットを共有する範囲内で行われます。これは複数のエージェントクラスタにまたがる場合があります。
1.1. 利用概要
APIの利用方法は以下の通りです:
-
ロックを要求します。
-
非同期タスクでロックを保持したまま作業を行います。
-
タスクが完了するとロックは自動的に解放されます。
1.2. 動機づけとなるユースケース
ウェブベースのドキュメントエディタは、高速アクセスのために状態をメモリに保持し、変更を(レコードの一連として)Indexed Database APIなどのストレージAPIに永続化して耐障害性やオフライン利用を実現し、サーバーにも保存して複数デバイス間で利用できるようにします。同じドキュメントが2つのタブで編集のために開かれている場合、どちらか一方だけがドキュメントの変更や同期を行えるように、タブ間で作業を協調する必要があります。これには、どちらがアクティブに変更(およびストレージAPIとの状態同期)を行うかを調整し、アクティブなタブが離脱(ナビゲート、閉じる、クラッシュ)した際に他のタブがアクティブになれるよう把握する必要があります。
データ同期サービスでは、「プライマリタブ」が指定されます。このタブだけが特定の操作(ネットワーク同期、キュー済みデータのクリーンアップなど)を実行します。プライマリタブはロックを保持し続け、決して解放しません。他のタブはロックの取得を試み、その試みはキューされます。「プライマリタブ」がクラッシュしたり閉じられたりすると、他のタブのいずれかがロックを取得して新しいプライマリとなります。
Indexed Database APIは、オリジン内の複数の名前付きストレージパーティション間で共有読み取りと排他的書き込みアクセスを可能にするトランザクションモデルを定義しています。この概念をプリミティブとして公開すれば、リソースの利用可能性に基づいて任意のWebプラットフォームの活動をスケジューリングでき、例えば他のストレージ型(Cache [Service-Workers]など)やストレージ型をまたいだトランザクションの合成、さらには非ストレージAPI(例えばネットワークフェッチ)にも応用できます。
2. 概念
本仕様においては、以下の通りとします:
-
ブラウザ内の個別ユーザープロファイルは、それぞれ別のユーザーエージェントとみなされます。
-
各 プライベートモードの閲覧セッションは、別個のユーザーエージェントとみなされます。
ユーザーエージェントは、ロックタスクキューを持ち、これは新しい並列キューの開始の結果です。
以下にエンキューされるタスクソースは、Web Locksタスクソースです。
2.1. リソース名
リソース名は、Webアプリケーションが抽象的なリソースを表すために選択するJavaScript文字列です。
リソース名はスケジューリングアルゴリズム以外には外部的な意味を持たず、agent が同じ storage bucket を共有する場合にグローバルです。Webアプリケーションは、任意のリソース命名規則を自由に使用できます。
U+002D HYPHEN-MINUS (-)で始まるリソース名は予約されており、これらを要求すると例外が発生します。
2.2. ロックマネージャ
ロックマネージャは、ロックとロック要求の状態をカプセル化します。各ストレージバケットは、Web Locks API用に関連付けられたストレージボトルを通じて1つのロックマネージャを含みます。
注: 同じユーザーエージェント内で開かれたストレージバケットを共有するページやワーカー(エージェント)は、ロックマネージャを共有します。たとえそれらが無関係なブラウジングコンテキストであってもです。
-
mapを、environmentと"
web-locks
"を与えてローカルストレージボトルマップの取得を実行した結果とします。 -
mapが失敗なら、失敗を返します。
-
bottleをmapの関連付けられたストレージボトルとします。
-
bottleの関連付けられたロックマネージャを返します。
ここで[Storage]との統合方法を調整してください。与えられた環境からロックマネージャを正しく取得する方法も含みます。
2.3. モードとスケジューリング
モードは、"exclusive
"
または"shared
"のいずれかです。モードは一般的な読取・書込ロックパターンをモデル化できます。"exclusive
"ロックが保持されている場合、その名前の他のロックは付与されません。"shared
"ロックが保持されている場合、その名前の他の"shared
"ロックは付与できますが、"exclusive
"ロックは付与できません。APIのデフォルトモードは"exclusive
"です。
追加のプロパティ(タイムアウト、公平性など)がスケジューリングに影響する場合があります。
2.4. ロック
ロックは、共有リソースへの排他的アクセスを表します。
ロックは、clientId(不透明な文字列)を持ちます。
ロックは、mode("exclusive
"または"shared
")を持ちます。
ロックは、waiting promise(Promise)を持ちます。
ロックは、released promise(Promise)を持ちます。
各ロックマネージャは、保持ロック集合(順序付きセット)を持ち、それはロックの集合です。
ロック lockのwaiting promiseが解決(fulfillまたはreject)されたとき、以下の手順をロックタスクキューにエンキューする:
-
ロックを解放する lock。
-
解決する lockのreleased promiseに、lockのwaiting promiseを与える。
2.5. ロック要求
ロック要求は、 保留中のロックの要求を表します。
ロック要求キューは、 キューであり、 ロック要求のキューです。
各ロックマネージャは、 ロック要求キュー・マップを持ちます。 これはマップであり、 リソース名から ロック要求キューへの対応を持ちます。
ロック要求キューを取得するには、 ロック要求キュー・マップ queueMapから リソース名 nameに対して、以下の手順を実行します:
ロック要求 requestが 付与可能かどうかは、以下の手順がtrueを返す場合です:
-
managerをrequestのmanagerとします。
-
queueMapをmanagerのロック要求キュー・マップとします。
-
nameをrequestのnameとします。
-
queueを、ロック要求キューの取得を queueMapとnameに対して実行した結果とします。
-
heldをmanagerの保持ロック集合とします。
-
modeをrequestのmodeとします。
-
もしmodeが"
exclusive
"の場合、 held内のいずれのロックも nameがnameと等しくなければtrue、そうでなければfalseを返します。 -
それ以外の場合、modeは"
shared
"です。 held内のいずれのロックも modeが"exclusive
"であり、 かつnameがnameと等しくなければtrue、そうでなければfalseを返します。
2.6. ロックの終了
ドキュメントアンロード時クリーンアップ手順が documentに対して実行された場合、 その残りのロックと要求の終了を そのagentで実行します。
agentが終了した場合、 残りのロックと要求の終了を そのagentで実行します。
これは現在ワーカーのみを対象としており、ワーカー終了時に手順を実行する規範的な方法がないため定義が曖昧です。
残りのロックと要求の終了を agentで実行するには、以下の手順をロックタスクキューにエンキューします:
3. API
3.1. Navigatorミックスイン
[SecureContext ]interface mixin {
NavigatorLocks readonly attribute LockManager locks ; };Navigator includes NavigatorLocks ;WorkerNavigator includes NavigatorLocks ;
各environment settings objectは
LockManager
オブジェクトを持ちます。
すべての現行エンジンで利用可能です。
Opera?Edge79以上
Edge (レガシー)?IE利用不可
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
locks
ゲッターの手順は
thisの
relevant settings objectが持つ
LockManager
オブジェクトを返します。
3.2. LockManager
クラス
すべての現行エンジンで利用可能です。
Opera?Edge79以上
Edge (レガシー)?IE利用不可
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
[SecureContext ,Exposed =(Window ,Worker )]interface {
LockManager Promise <any >request (DOMString ,
name LockGrantedCallback );
callback Promise <any >request (DOMString ,
name LockOptions ,
options LockGrantedCallback );
callback Promise <LockManagerSnapshot >query (); };callback =
LockGrantedCallback Promise <any > (Lock ?);
lock enum {
LockMode ,
"shared" };
"exclusive" dictionary {
LockOptions LockMode = "exclusive";
mode boolean =
ifAvailable false ;boolean =
steal false ;AbortSignal ; };
signal dictionary {
LockManagerSnapshot sequence <LockInfo >;
held sequence <LockInfo >; };
pending dictionary {
LockInfo DOMString ;
name LockMode ;
mode DOMString ; };
clientId
LockManager
のインスタンスは、スクリプトからロック要求を行ったり
ロックマネージャの状態をクエリすることができます。
3.2.1.
request()
メソッド
すべての現行エンジンで利用可能です。
Opera?Edge79以上
Edge (レガシー)?IE利用不可
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
- promise = navigator . locks .
request
(name, callback)- promise = navigator . locks .
request
(name, options, callback) - promise = navigator . locks .
-
request()
メソッドはロックを要求するために呼び出されます。name(最初の引数)はリソース名文字列です。
callback(最後の引数)は、ロックが付与されたときに呼び出されるコールバック関数です。これはスクリプトで指定され、通常は
async
関数です。ロックはコールバック関数が完了するまで保持されます。非asyncコールバック関数が渡された場合、即座にresolveされるPromiseでラップされるため、ロックは同期コールバックの間だけ保持されます。
返されるpromiseは、ロックが解放された後にコールバックの結果でresolve(またはreject)されるか、要求が中止された場合はrejectされます。
例:
try { const result= await navigator. locks. request( 'resource' , async lock=> { // この時点でロックが保持されています。 await do_something(); await do_something_else(); return "ok" ; // ここでロックが解放されます。 }); // |result|にはコールバックの戻り値が入ります。 } catch ( ex) { // コールバックがthrowされた場合、ここで捕捉されます。 }
コールバックがいかなる理由で終了してもロックは解放されます ― returnした場合もthrowした場合も同様です。
options辞書を第2引数として指定できます。callback引数は常に最後です。
- options . mode
-
mode
オプションは"exclusive
" (指定しない場合のデフォルト)または"shared
"のいずれかです。 複数のタブやワーカーが同じリソースに対し"shared
"モードのロックを保持できますが、 "exclusive
"モードでは1つのタブ/ワーカーだけがそのリソースのロックを保持できます。この主な用途は、複数のリーダーが同時にリソースへアクセスできるようにする一方、変更を防ぐことです。 リーダーロックが解放された後、排他的ライターがロックを取得して変更し、その後また排他的ライターや共有リーダーが続くことができます。
await navigator. locks. request( 'resource' , { mode: 'shared' }, async lock=> { // ここでロックが保持されています。他のコンテキストも共有モードでロックを保持できるが、排他モードでは保持できません。 });
- options . ifAvailable
-
ifAvailable
オプションがtrue
の場合、追加の待機なしでロックが付与可能な場合のみ取得します。これは依然として同期ではないことに注意してください。多くのユーザーエージェントではロックの可否確認にプロセス間通信が必要です。ロックが付与されない場合、コールバックにはnull
が渡されます(これは想定内なので、要求はrejectされません)。
await navigator. locks. request( 'resource' , { ifAvailable: true }, async lock=> { if ( ! lock) { // 取得できなかった。必要に応じて対応する。 return ; } // この時点でロックが保持されています。 });
- options . signal
-
signal
オプションにはAbortSignal
を設定できます。 これにより、例えば要求がタイムリーに付与されない場合など、ロック要求を中止できます:
const controller= new AbortController(); setTimeout(() => controller. abort(), 200 ); // 最大200ms待つ try { await navigator. locks. request( 'resource' , { signal: controller. signal}, async lock=> { // この時点でロックが保持されています。 }); // ロック解放後。 } catch ( ex) { // |ex|が"AbortError"というエラー名のDOMExceptionであれば、タイマーが発火した場合です。 }
ロックが付与される前にabortが発生した場合、要求のPromiseはAbortError
でrejectされます。ロックが付与された後はsignalは無視されます。
- options . steal
-
steal
オプションがtrue
の場合、そのリソースの保持中のロックはすべて解放され(そのロックのreleased promiseはAbortError
で解決されます)、 この要求が付与され、キュー済み要求も先取りされます。Webアプリケーションが回復不能な状態、例えばService Workerなどがロックを保持するタブに応答がないことを検出した場合、このオプションでロックを「奪う」ことが可能です。
request(name, callback)
および
request(name, options, callback)
メソッドの手順は次の通りです:
-
optionsが渡されなかった場合、optionsにデフォルトメンバーを持つ新しい
LockOptions
辞書を設定します。 -
environmentをthisのrelevant settings objectとします。
-
environmentの関連グローバルオブジェクトの関連付けられたDocumentが完全にアクティブでない場合、"
InvalidStateError
"DOMException
でrejectされたpromiseを返します。 -
managerをenvironmentでロックマネージャの取得の結果とします。それが失敗を返した場合、"
SecurityError
"DOMException
でrejectされたpromiseを返します。 -
nameがU+002Dハイフン(-)で始まる場合、"
NotSupportedError
"DOMException
でrejectされたpromiseを返します。 -
options["
steal
"]とoptions["ifAvailable
"]が両方trueの場合、"NotSupportedError
"DOMException
でrejectされたpromiseを返します。 -
options["
steal
"]がtrueで、options["mode
"]が"exclusive
"でない場合、"NotSupportedError
"DOMException
でrejectされたpromiseを返します。 -
options["
signal
"]が存在する場合、 options["steal
"]またはoptions["ifAvailable
"]のいずれかがtrueなら、"NotSupportedError
"DOMException
でrejectされたpromiseを返します。 -
options["
signal
"]が存在し、かつabortedなら、options["signal
"]のabort reasonでrejectされたpromiseを返します。 -
promiseを新しいpromiseとします。
-
ロックを要求するをpromise、現在のagent、environmentのid、manager、callback、name、options["
mode
"]、options["ifAvailable
"]、options["steal
"]、options["signal
"]で実行します。 -
promiseを返します。
3.2.2.
query()
メソッド
すべての現行エンジンで利用可能です。
Opera?Edge79以上
Edge (レガシー)?IE利用不可
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
- state = await navigator . locks .
query
() -
query()
メソッドは、オリジンのロックマネージャの状態スナップショットを生成するために利用できます。これにより、Webアプリケーションはロック利用状況をログやデバッグ目的で検査できます。返されるPromiseはstate(JSONライクなデータ構造)で解決され、以下の形式です:
{ held: [ { name: "resource1" , mode: "exclusive" , clientId: "8b1e730c-7405-47db-9265-6ee7c73ac153" }, { name: "resource2" , mode: "shared" , clientId: "8b1e730c-7405-47db-9265-6ee7c73ac153" }, { name: "resource2" , mode: "shared" , clientId: "fad203a5-1f31-472b-a7f7-a3236a1f6d3b" }, ], pending: [ { name: "resource1" , mode: "exclusive" , clientId: "fad203a5-1f31-472b-a7f7-a3236a1f6d3b" }, { name: "resource1" , mode: "exclusive" , clientId: "d341a5d0-1d8d-4224-be10-704d1ef92a15" }, ] }
clientId
フィールドは個別のコンテキスト(フレームやワーカー)に対応し、Client
の
id
属性で返される値と同じです。
query()
メソッドの手順は次の通りです:
-
environmentをthisのrelevant settings objectとします。
-
environmentの関連グローバルオブジェクトの関連付けられたDocumentが完全にアクティブでない場合、"
InvalidStateError
"DOMException
でrejectされたpromiseを返します。 -
managerをenvironmentでロックマネージャの取得の結果とします。それが失敗を返した場合、"
SecurityError
"DOMException
でrejectされたpromiseを返します。 -
promiseを新しいpromiseとします。
-
ロック状態のスナップショット手順をmanagerとpromiseでロックタスクキューにエンキューします。
-
promiseを返します。
3.3. Lock
クラス
すべての現行エンジンで利用可能です。
Opera?Edge79以上
Edge (レガシー)?IE利用不可
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
[SecureContext ,Exposed =(Window ,Worker )]interface {
Lock readonly attribute DOMString name ;readonly attribute LockMode mode ; };
すべての現行エンジンで利用可能です。
Opera?Edge79以上
Edge (レガシー)?IE利用不可
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
name
ゲッターの手順は、関連するロックのnameを返します。
すべての現行エンジンで利用可能です。
Opera?Edge79以上
Edge (レガシー)?IE利用不可
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
mode
ゲッターの手順は、関連するロックのmodeを返します。
4. アルゴリズム
4.1. ロックの要求
-
requestを新しいロック要求(agent, clientId, manager, name, mode, callback, promise, signal)とする。
-
signalが存在する場合、次のアルゴリズムを 要求中止のsignal通知として requestとsignalでsignalに追加する。
-
-
queueMapをmanagerのロック要求キュー・マップとする。
-
queueをqueueMapからnameでロック要求キューを取得した結果とする。
-
heldをmanagerの保持ロック集合とする。
-
stealがtrueなら、次の手順を実行:
-
held内のlockに対し:
-
lockのnameがnameなら、次を行う:
-
lockをheldから削除。
-
lockのreleased promiseを "
AbortError
"DOMException
でreject。
-
-
-
requestをqueueに先頭に追加。
-
-
それ以外の場合、次の手順を実行:
-
ifAvailableがtrueかつrequestがgrantableでない場合、 以下の手順を callbackのrelevant settings objectのresponsible event loopにエンキュー:
-
requestをqueueにエンキュー。
-
-
ロック要求キューを処理する queue。
-
-
requestを返す。
4.2. ロックの解放
-
managerをlockのmanagerとする。
-
queueMapをmanagerのロック要求キュー・マップとする。
-
nameをlockのリソース名とする。
-
queueをqueueMapからnameでロック要求キューを取得した結果とする。
-
ロック要求キューを処理する queue。
4.3. 要求の中止
-
managerをrequestのmanagerとする。
-
nameをrequestのnameとする。
-
queueMapをmanagerのロック要求キュー・マップとする。
-
queueをqueueMapからnameでロック要求キューを取得した結果とする。
-
requestをqueueから削除。
-
ロック要求キューを処理する queue。
4.4. 指定されたリソース名のロック要求キューの処理
-
queue内の各requestについて:
-
requestがgrantableでなければreturn。
注: キューの最初の項目だけがgrantableです。したがって、grantableでない場合、以降の項目も自動的にgrantableではありません。
-
requestをqueueから削除。
-
agentをrequestのagentとする。
-
managerをrequestのmanagerとする。
-
clientIdをrequestのclientIdとする。
-
nameをrequestのnameとする。
-
modeをrequestのmodeとする。
-
callbackをrequestのcallbackとする。
-
pをrequestのpromiseとする。
-
signalをrequestのsignalとする。
-
waitingを新しいPromiseとする。
-
lockを新しいロック(agent agent、 clientId clientId、 manager manager、 mode mode、 name name、 released promise p、 waiting promise waiting )とする。
-
以下の手順を callbackのrelevant settings objectの responsible event loopにエンキュー:
-
4.5. ロック状態のスナップショット
5. 利用上の注意事項
このセクションは規範的ではありません。
5.1. デッドロック
デッドロックは並行計算の概念であり、特定のロックマネージャに限定されたデッドロックがこのAPIによって生じることがあります。
デッドロックを防ぐには注意が必要です。一つの方法は、常に複数のロックを厳密な順序で取得することです。
6. セキュリティとプライバシーに関する考慮事項
6.1. ロックのスコープ
ロックマネージャのスコープ定義は、プライバシー境界を定めるため重要です。ロックは一時的な状態保持機構として使われ、ストレージAPI同様通信手段にもなり得るため、ストレージ機能以上の権限を持ってはなりません。ユーザーエージェントがこれらサービスのいずれかに細かい粒度を課す場合、他のサービスにも同じ粒度を課す必要があります。例えば、プライバシー理由でトップレベルページ(ファーストパーティ)とクロスオリジンiframe(サードパーティ)で異なるストレージ区画を同一オリジンに対し公開する場合、ロックも同様に分割しなければなりません。
これにより、Webアプリ開発者にも合理的な期待が与えられます。ストレージリソース上でロックを取得した場合、同一オリジンのすべてのブラウジングコンテキストが同じ状態を観測できなければなりません。
6.2. プライベートブラウジング
各プライベートモードのブラウジングセッションは、このAPIの観点では別のユーザーエージェントとみなされます。つまり、こうしたセッション外で要求・保持されたロックは、セッション内の要求・保持に影響しませんし、その逆も同様です。これにより、ウェブサイトがセッションが「シークレットモード」であることを判定したり、こうしたセッション間で通信手段を提供したりすることが防止されます。
6.3. 実装リスク
実装では、ロックがオリジンをまたがってはいけないことを保証しなければなりません。これを怠ると、2つのオリジンで動作するスクリプト間の通信のためのサイドチャネルが生じたり、一方のオリジンのスクリプトがもう一方の挙動を妨害する(例えばサービス拒否)ことが可能となります。
6.4. チェックリスト
W3C TAGはセキュリティ・プライバシー自己レビュー質問票を仕様編集者向けに作成しています。以下、その質問内容を再確認します:
-
本仕様は個人識別情報や高価値データを扱いません。
-
オリジンに対して、ブラウジングセッションをまたいで永続する新しい状態は導入されません。
-
ウェブに新たな永続的なクロスオリジン状態は公開されません。
-
オリジンに現在アクセス可能な情報以外(例:[IndexedDB-2]によるポーリング等)を新たに公開しません。
-
新たなスクリプト実行/読み込みメカニズムは有効化されません。
-
本仕様は以下のいずれにもオリジンへのアクセスを許可しません:
-
ユーザーの位置情報
-
ユーザー端末のセンサー
-
ユーザーのローカルコンピュータ環境の側面
-
他のデバイスへのアクセス
-
ユーザーエージェントのネイティブUIに対する制御権限
-
-
ウェブに一時的な識別子は公開されません。すべてのリソース名はWebアプリ自身が指定します。
-
ファーストパーティとサードパーティ文脈での挙動は、ユーザーエージェントがストレージを区別する場合区別されます。§6.1 ロックのスコープ参照。
-
ユーザーエージェントの「シークレットモード」文脈での挙動は§6.2 プライベートブラウジングで説明されています。
-
本APIによってユーザー端末へのデータ保存は行われません。
-
本APIはデフォルトのセキュリティ特性を下げることはありません。
7. 謝辞
この提案の作成にあたり、下記の方々に深く感謝します。 Alex Russell, Andreas Butler, Anne van Kesteren, Boris Zbarsky, Chris Messina, Darin Fisher, Domenic Denicola, Gus Caplan, Harald Alvestrand, Jake Archibald, Kagami Sascha Rosylight, L. David Baron, Luciano Pacheco, Marcos Caceres, Ralph Chelala, Raymond Toy, Ryan Fioravanti, そして Victor Costan 。
Bikeshed(この仕様書作成に用いたツール)および著者全般への助言をしてくださったTab Atkins, Jr.にも特別感謝いたします。