Web Locks API

W3C 作業草案,

この文書の詳細情報
このバージョン:
https://www.w3.org/TR/2025/WD-web-locks-20250924/
最新公表バージョン:
https://www.w3.org/TR/web-locks/
編集者草案:
https://w3c.github.io/web-locks/
以前のバージョン:
履歴:
https://www.w3.org/standards/history/web-locks/
テストスイート:
https://github.com/web-platform-tests/wpt/tree/master/web-locks
フィードバック:
GitHub
仕様内インライン
編集者:
(Mozilla)
前編集者:
(Google Inc.)

概要

この文書は、スクリプトがリソースのロックを非同期的に取得し、作業中保持し、完了後に解放できるウェブプラットフォームAPIを定義します。ロックが保持されている間は、同一オリジン内の他のスクリプトは同じリソースのロックを取得できません。これにより、ウェブアプリケーション内のコンテキスト(ウィンドウやワーカー)がリソースの利用を協調できるようになります。

この文書のステータス

このセクションは、本書が公開された時点での文書のステータスを説明します。現在の W3C 公開文書および本技術レポートの最新版は、W3C 標準とドラフトのインデックスで確認できます。

この文書は Web Applications Working Group によって 作業草案 として 勧告トラックを利用して公開されました。 作業草案として公開されていることは、W3C およびその会員による承認を意味するものではありません。

この文書はドラフトであり、 今後更新、置換、または他の文書によって廃止される可能性があります。 この文書を進行中の作業以外のものとして引用することは適切ではありません。

この文書は、W3C 特許ポリシーの下で運営されているグループによって作成されました。W3Cは、そのグループの成果物に関連して提出された 特許開示の公開リストを管理しています。そのページには特許開示の手順も記載されています。個人が特許の内容を把握しており、その特許が 必須クレームを含むと考える場合は、W3C特許ポリシー第6節に従って情報開示しなければなりません。

この文書は 2025年8月18日版W3Cプロセス文書 に従って管理されています。

1. はじめに

このセクションは規範的ではありません。

ロックリクエストは、スクリプトによって特定のリソース名モードで行われます。スケジューリングアルゴリズムは現在および過去のリクエストの状態を調べ、最終的にロックリクエストを許可します。ロックは許可されたリクエストであり、リソース名モードを持ちます。これはスクリプトに返されるオブジェクトとして表現されます。ロックが保持されている間は、(名前やモードによって)他のロックリクエストが許可されなくなる場合があります。ロックはスクリプトによって解放でき、その時点で他のロックリクエストが許可される可能性があります。

APIは必要に応じて利用できる追加機能を提供します。例:

協調的な調整は、agentが同じstorage bucketを共有する範囲内で行われます。これは複数のagent clusterにまたがる場合もあります。

注: agentはウィンドウ(タブ)、iframe、workerなどに概ね対応します。agent clusterは、一部のユーザーエージェント実装では独立したプロセスに対応します。

1.1. 利用概要

APIの使用方法は以下の通りです:

  1. ロックを要求する。

  2. ロックを保持した非同期タスクで作業を行う。

  3. タスク完了時にロックが自動的に解放される。

API利用の基本例は以下のとおりです:

navigator.locks.request('my_resource', async lock => {
   // ロックを取得しました。
   await do_something();
   await do_something_else();
   // ここでロックが解放されます。
});

非同期関数内では、リクエスト自体を await することも可能です:

// ロックを要求する前。
await navigator.locks.request('my_resource', async lock => {
   // ロックを取得しました。
   await do_something();
   // ここでロックが解放されます。
});
// ロックが解放された後

1.2. 動機となるユースケース

ウェブベースの文書編集ツールは、高速なアクセスのために状態をメモリ上に保存し、変更を(記録の一連として)Indexed Database APIなどのストレージAPIへ永続化して回復性とオフライン利用を実現し、さらにサーバーへも保存してデバイス間同期も可能にします。同じ文書を2つのタブで編集する場合、どちらか一方だけが変更や同期を行えるように、タブ間で作業を調整する必要があります。これは、どちらのタブが実際に変更(およびストレージAPIとの同期)を行うか調整し、アクティブなタブが消失(遷移、閉じる、クラッシュ)した際に他のタブがアクティブになる仕組みが必要です。

データ同期サービスでは「プライマリタブ」が指定され、このタブだけが特定の操作(ネットワーク同期、キュー済みデータのクリーンアップ等)を実行します。プライマリタブはロックを保持し続け、他のタブがロック取得を試みるとキューされます。プライマリタブがクラッシュや閉じられた場合、他のタブがロックを取得し新しいプライマリとなります。

Indexed Database APIは、オリジン内の複数の名前付きストレージパーティション間で共有読み取り・排他書き込みアクセスを可能にするトランザクションモデルを定義しています。この概念をプリミティブとして公開することで、Web Platformの任意の活動をリソース可用性に基づいてスケジューリングできるようになります。例えば、他のストレージタイプ(Cache [Service-Workers])やストレージ種別をまたいだトランザクションの構成、さらには非ストレージAPI(例:ネットワークフェッチ)を組み合わせることも可能になります。

2. 概念

この仕様の目的上:

ユーザーエージェントは、ロックタスクキューを持ちます。これは新しい並列キューを開始した結果です。

以下のタスクソースは、enqueuedされた手順のためのweb locks tasks sourceです。

2.1. リソース名

リソース名は、ウェブアプリケーションが抽象的なリソースを表すために選択するJavaScript文字列です。

リソース名はスケジューリングアルゴリズム以外に外部的な意味はなく、agentが同じstorage bucketを共有している範囲でグローバルです。ウェブアプリケーションは自由に任意のリソース命名スキームを利用できます。

[IndexedDB-2]内の名前付きデータベースにおける名前付きストアに対するトランザクションロックを模倣するには、スクリプトはリソース名を次のように構成できます:
encodeURIComponent(db_name) + '/' + encodeURIComponent(store_name)

U+002D HYPHEN-MINUS (-)で始まるリソース名は予約されています。これらをリクエストすると例外が発生します。

2.2. ロックマネージャー

ロックマネージャーは、ロックロックリクエストの状態をカプセル化します。各storage bucketには、Web Locks API用の関連storage bottleを通じて、1つのロックマネージャーが含まれます。

注: 同じユーザーエージェント内で開かれたロックマネージャーは、storage bucketを共有するページやワーカー(agent)間で共有されます。たとえそれらが無関係な閲覧コンテキストであってもです。

ロックマネージャーを取得するには、environment settings object environmentが与えられた場合、以下の手順を実行します:
  1. mapを、ローカルストレージボトルマップを取得した結果(environmentと"web-locks"を指定)とする。

  2. mapが失敗なら、失敗を返す。

  3. bottlemapの関連storage bottleとする。

  4. bottleの関連ロックマネージャーを返す。

[Storage]との統合方法(与えられた環境からロックマネージャーを適切に取得する方法を含む)をここでさらに詳しく記述すること。

2.3. モードとスケジューリング

モードは "exclusive" または "shared" のいずれかです。モードは典型的なリーダ-ライターロックパターンのモデル化に利用できます。"exclusive"ロックが保持されている場合、その名前の他のロックは許可されません。"shared"ロックが保持されている場合は、同じ名前の他の"shared"ロックは許可されますが、"exclusive"ロックは許可されません。APIのデフォルトモードは"exclusive"です。

タイムアウト、公平性など、追加のプロパティがスケジューリングに影響を与える場合があります。

2.4. ロック

ロックは共有リソースへの排他的なアクセスを表します。

ロックは、agentagent)を持ちます。

ロックは、clientId(不透明な文字列)を持ちます。

ロックは、managerロックマネージャー)を持ちます。

ロックは、nameリソース名)を持ちます。

ロックは、mode("exclusive"または"shared")を持ちます。

ロックは、waiting promise(Promise)を持ちます。

ロックは、released promise(Promise)を持ちます。

ロックのライフサイクルに関連するPromiseは2つあります:
  • ロックが許可された際にコールバックによって暗黙的または明示的に提供されるPromiseで、ロックが保持される期間を決定します。このPromiseがsettle(完了)するとロックが解放されます。これがロックのwaiting promiseです。

  • LockManagerrequest() メソッドから返されるPromiseで、ロックが解放されるかリクエストが中止されるとsettleします。これがロックのreleased promiseです。

const p1 = navigator.locks.request('resource', lock => {
  const p2 = new Promise(r => {
    // ロックを使用するロジック、およびPromiseを解決…
  });
  return p2;
});

上記の例では、p1released promiseであり、p2waiting promiseです。 多くの場合、コールバックはasync関数として実装され、返されるPromiseは暗黙的となります。以下の例のようになります:

const p1 = navigator.locks.request('resource', async lock => {
  // ロックを使用するロジック…
});

上記コードではwaiting promiseは名前付きで示されていませんが、匿名のasyncコールバックの戻り値として存在します。コールバックがasyncでなく非Promise値を返す場合、その値は即座に解決されるPromiseでラップされます。ロックは次のマイクロタスクで解放され、released promiseも次のマイクロタスクで解決されます。

ロックマネージャーは、保持ロック集合set of ロック)を持ちます。

lock lockwaiting promiseがsettle(fulfillまたはreject)した時、以下の手順をロックタスクキューにenqueueします:

  1. ロック lockを解放する。

  2. resolve lockreleased promiselockwaiting promiseで)を解決する。

2.5. ロックリクエスト

ロックリクエストは、ロックに対する保留中のリクエストを表します。

ロックリクエストは、構造体であり、項目として agentclientIdmanagernamemodecallbackpromise、および signal を持ちます。

ロックリクエストキューは、キューであり、ロックリクエストの集合です。

ロックマネージャは、ロックリクエストキューマップを持ちます。これは、マップであり、リソース名からロックリクエストキューへの対応関係です。

ロックリクエストキューを取得するには、ロックリクエストキューマップ queueMap から リソース名 name を使って、以下の手順を実行します:

  1. もし queueMap[name] が 存在しない場合、新しく空の ロックリクエストキューqueueMap[name] に設定します。

  2. queueMap[name] を返します。

ロックリクエスト requestgrantable(付与可能)とされるのは、以下の手順が true を返す場合です:

  1. managerrequestmanager とします。

  2. queueMapmanagerロックリクエストキューマップ とします。

  3. namerequestname とします。

  4. queueロックリクエストキューを取得する結果として queueMap から name で取得します。

  5. heldmanager保持中ロック集合とします。

  6. moderequestmode とします。

  7. もし queue空でないかつ requestqueue の最初の 項目でない場合、false を返します。

  8. もし mode が "exclusive" なら、held 内のいずれの ロックnamename と等しくなければ true、そうでなければ false を返します。

  9. それ以外の場合、mode は "shared" です。held 内のいずれの ロックmode が "exclusive" かつ namename と等しくなければ true、そうでなければ false を返します。

2.6. ロックの終了

ドキュメントのアンロードクリーンアップ手順documentで実行されるとき、残りのロックとリクエストを終了するをそのagentで実行します。

agentが終了するとき、残りのロックとリクエストを終了するをその agent で実行します。

これは現在ワーカーのみを対象としており、ワーカー終了時に手順を実行する規範的な方法がないため、定義が曖昧です。

残りのロックとリクエストを終了するには agent を使い、以下の手順をロックタスクキューにエンキューします:

  1. ロックリクエスト requestについて、agentagentと等しい場合:

    1. リクエストを中止する request

  2. ロック lockについて、agentagentと等しい場合:

    1. ロックを解放する lock

3. API

[SecureContext]
interface mixin NavigatorLocks {
  readonly attribute LockManager locks;
};
Navigator includes NavigatorLocks;
WorkerNavigator includes NavigatorLocks;

環境設定オブジェクトは、LockManagerオブジェクトを持ちます。

locksゲッターの手順は、this関連設定オブジェクトLockManagerオブジェクトを返すことです。

3.2. LockManager クラス

[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 mode = "exclusive";
  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() メソッド

promise = navigator . locks . request(name, callback)
promise = navigator . locks . request(name, options, callback)

request() メソッドはロックを要求するために呼び出されます。

name(最初の引数)はリソース名の文字列です。

callback(最後の引数)は、コールバック関数であり、Lockが付与されたときに呼び出されます。これはスクリプトで指定され、通常はasync関数です。ロックはコールバック関数が完了するまで保持されます。非async関数が渡された場合は、自動的に即座に解決されるPromiseでラップされるため、ロックは同期コールバックの実行中のみ保持されます。

返されるpromiseは、ロックが解放された後にコールバックの結果で解決(または拒否)されるか、リクエストが中止された場合は拒否されます。

例:

try {
  const result = await navigator.locks.request('resource', async lock => {
    // ロックがここで保持されます。
    await do_something();
    await do_something_else();
    return "ok";
    // ロックはここで解放されます。
  });
  // |result| にはコールバックの戻り値が入ります。
} catch (ex) {
  // コールバックが例外を投げた場合、ここで捕捉されます。
}

コールバックが何らかの理由で終了すると、ロックは解放されます ― 戻り値が返された場合も、例外が投げられた場合も同様です。

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 で呼び出されます。(これは想定されるため、リクエストは拒否されません。)

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 になります。
}

ロックが付与される前に中止が通知された場合、リクエストのPromiseは AbortError で拒否されます。 ロックが付与された後は signal は無視されます。

options . steal

steal オプションが true の場合、そのリソースに対して保持されているロックはすべて解放され(そのreleased promiseAbortError で解決されます)、 リクエストは付与され、キューされている他のリクエストより優先されます。

Webアプリケーションが回復不能な状態を検出した場合 ― 例えば、Service Workerがロックを保持しているタブが応答しなくなったと判断した場合 ― このオプションでロックを「奪う」ことができます。

stealオプションの使用には注意してください。 使用した場合、以前ロックを保持していたコードは、もはやリソースへの唯一のアクセス権が保証されない状態で実行されることになります。 同様に、このオプションを使用したコードも、他のコンテキストが抽象リソースへのアクセス権を持っているかのように実行されていない保証はありません。 これは、アプリケーションやユーザーエージェントの不具合など、すでに動作が予測できない状況で回復を試みる必要があるWebアプリケーション向けに設計されています。

request(name, callback) および request(name, options, callback) メソッドの手順は以下の通りです:

  1. optionsが渡されなかった場合、新しいLockOptions辞書(デフォルトメンバー付き)をoptionsとします。

  2. environmentthis関連設定オブジェクトとします。

  3. もしenvironment関連グローバルオブジェクト関連付けられたDocument完全にアクティブでない場合、"InvalidStateError" DOMExceptionPromiseを拒否して返します

  4. managerロックマネージャを取得するenvironmentを渡して得ます。失敗した場合は、"SecurityError" DOMExceptionPromiseを拒否して返します

  5. もしnameがU+002D HYPHEN-MINUS(-)で始まる場合、"NotSupportedError" DOMExceptionPromiseを拒否して返します

  6. もしoptions["steal"]とoptions["ifAvailable"]が両方ともtrueの場合、"NotSupportedError" DOMExceptionPromiseを拒否して返します

  7. もしoptions["steal"]がtrueかつoptions["mode"]が"exclusive"でない場合、"NotSupportedError" DOMExceptionPromiseを拒否して返します

  8. もしoptions["signal"]が存在し、かつoptions["steal"]またはoptions["ifAvailable"]のいずれかがtrueの場合、"NotSupportedError" DOMExceptionPromiseを拒否して返します

  9. もしoptions["signal"]が存在し、かつ中止されている場合、options["signal"]の中止理由Promiseを拒否して返します

  10. promise新しいPromiseとします。

  11. ロックをリクエストするpromise、現在のagentenvironmentidmanagercallbacknameoptions["mode"]、options["ifAvailable"]、options["steal"]、options["signal"]で実行します。

  12. promiseを返します。

3.2.2. query() メソッド

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フィールドは一意なコンテキスト(フレームまたはワーカー)に対応し、Clientid 属性で返される値と同じです。

このデータはロックマネージャの状態のスナップショットに過ぎません。スクリプトにデータが返される時点で、実際のロック状態は変化している可能性があります。

query()メソッドの手順は以下の通りです:

  1. environmentthis関連設定オブジェクトとします。

  2. もしenvironment関連グローバルオブジェクト関連付けられたDocument完全にアクティブでない場合、"InvalidStateError" DOMExceptionPromiseを拒否して返します

  3. managerロックマネージャを取得するenvironmentを渡して得ます。失敗した場合は、"SecurityError" DOMExceptionPromiseを拒否して返します

  4. promise新しいPromiseとします。

  5. 以下の手順をエンキューして、managerロック状態のスナップショットpromiseとともにロックタスクキューに追加します。

  6. promiseを返します。

3.3. Lockクラス

[SecureContext, Exposed=(Window,Worker)]
interface Lock {
  readonly attribute DOMString name;
  readonly attribute LockMode mode;
};

Lockオブジェクトは、関連付けられたロックを持ちます。

nameゲッターの手順は、関連付けられたロックnameを返すことです。

modeゲッターの手順は、関連付けられたロックmodeを返すことです。

4. アルゴリズム

4.1. ロックをリクエストする

ロックをリクエストするには promise, agent, clientId, manager, callback, name, mode, ifAvailable, steal, signalを使います:
  1. requestを新しいロックリクエストagent, clientId, manager, name, mode, callback, promise, signal)とします。

  2. signalが存在する場合、アルゴリズム signal to abort the request requestsignalに追加します。

  3. 以下の手順をロックタスクキューにエンキューします:

    1. queueMapmanagerロックリクエストキューマップとします。

    2. queueロックリクエストキューを取得するqueueMapからnameで取得します。

    3. heldmanager保持中ロック集合とします。

    4. stealがtrueの場合、以下の手順を実行します:

      1. lockheldの要素)について:

        1. locknamenameと等しい場合、以下の手順を実行します:

          1. Remove lockheldから削除します。

          2. Reject lockreleased promiseを"AbortError" DOMExceptionで拒否します。

      2. Prepend requestqueueに追加します。

    5. それ以外の場合、以下の手順を実行します:

      1. ifAvailableがtrueかつrequestgrantableでない場合、以下の手順をcallback関連設定オブジェクト責任イベントループにエンキューします:

        1. rコールバック関数を呼び出すcallbacknullのみを引数として渡した結果とします。

        2. Resolve promiserで解決し、これらの手順を中止します。

      2. Enqueue requestqueueに追加します。

    6. ロックリクエストキューを処理する queue

  4. requestを返します。

4.2. ロックを解放する

ロックを解放する lock
  1. Assert: これらの手順はロックタスクキュー上で実行されています。

  2. managerlockmanagerとします。

  3. queueMapmanagerロックリクエストキューマップとします。

  4. namelockリソース名とします。

  5. queueロックリクエストキューを取得するqueueMapからnameで取得します。

  6. Remove lockmanager保持中ロック集合から削除します。

  7. ロックリクエストキューを処理する queue

4.3. リクエストを中止する

リクエストを中止する request
  1. Assert: これらの手順はロックタスクキュー上で実行されています。

  2. managerrequestmanagerとします。

  3. namerequestnameとします。

  4. queueMapmanagerロックリクエストキューマップとします。

  5. queueロックリクエストキューを取得するqueueMapからnameで取得します。

  6. Remove requestqueueから削除します。

  7. ロックリクエストキューを処理する queue

signal to abort the request requestsignal
  1. 以下の手順をリクエストを中止する requestとしてロックタスクキューにエンキューします。

  2. Reject requestpromisesignal中止理由で拒否します。

4.4. 指定されたリソース名のロックリクエストキューを処理する

ロックリクエストキューを処理する queue
  1. Assert: これらの手順はロックタスクキュー上で実行されています。

  2. requestqueueの要素)について:

    1. requestgrantableでない場合、returnします。

      NOTE: キュー内の最初の項目のみがgrantableです。したがって、grantableでない場合、以降の項目も自動的にgrantableではありません。

    2. Remove requestqueueから削除します。

    3. agentrequestagentとします。

    4. managerrequestmanagerとします。

    5. clientIdrequestclientIdとします。

    6. namerequestnameとします。

    7. moderequestmodeとします。

    8. callbackrequestcallbackとします。

    9. prequestpromiseとします。

    10. signalrequestsignalとします。

    11. waiting新しいPromiseとします。

    12. lockを新しいロックagent agent, clientId clientId, manager manager, mode mode, name name, released promise p, waiting promise waiting)とします。

    13. Append lockmanager保持中ロック集合に追加します。

    14. 以下の手順をcallback関連設定オブジェクト責任イベントループにエンキューします:

      1. signalが存在する場合、以下の手順を実行します:

        1. signal中止されている場合、以下の手順を実行します:

          1. 以下の手順をロックタスクキューにエンキューします:

            1. ロックを解放する lock

          2. returnします。

        2. アルゴリズム signal to abort the request requestsignalから削除します。

      2. rコールバック関数を呼び出すcallbackに新しいLockオブジェクト(lockに関連付け)を唯一の引数として渡した結果とします。

      3. Resolve waitingrで解決します。

4.5. ロック状態のスナップショット

ロック状態のスナップショットを取得するには managerpromiseを使います:
  1. Assert: これらの手順はロックタスクキュー上で実行されています。

  2. pendingを新しいリストとします。

  3. queuemanagerロックリクエストキューマップ)について:

    1. requestqueueの要素)について:

      1. Append «[ "name" → requestname, "mode" → requestmode, "clientId" → requestclientId ]» をpendingに追加します。

  4. heldを新しいリストとします。

  5. lockmanager保持中ロック集合の要素)について:

    1. Append «[ "name" → lockname, "mode" → lockmode, "clientId" → lockclientId ]» をheldに追加します。

  6. Resolve promiseを «[ "held" → held, "pending" → pending ]» で解決します。

あるリソースに対して、pendingロックリクエストのスナップショットはリクエストが行われた順序で返されますが、異なるリソース間のリクエストの相対的な順序については保証されません。例えば、リソースAに対してA1とA2のpendingロックリクエストがその順で行われ、リソースBに対してB1とB2のpendingロックリクエストがその順で行われた場合、スナップショットのpendingリストの順序として «A1, A2, B1, B2» や «A1, B1, A2, B2» のような並びがあり得ます。

保持中ロック状態のスナップショットについては順序保証はありません。

5. 利用上の注意

このセクションは規範的ではありません。

5.1. デッドロック

デッドロックは並行計算における概念であり、このAPIによって特定のロックマネージャにスコープされたデッドロックが発生する可能性があります。

このAPIの利用によってデッドロックが発生する例を以下に示します。

スクリプト1:

navigator.locks.request('A', async a => {
  await navigator.locks.request('B', async b => {
    // AとBを使った処理
  });
});

スクリプト2:

navigator.locks.request('B', async b => {
  await navigator.locks.request('A', async a => {
    // AとBを使った処理
  });
});

スクリプト1とスクリプト2がほぼ同時に実行されると、スクリプト1がロックAを保持し、スクリプト2がロックBを保持し、どちらもそれ以上進行できなくなる(デッドロック)可能性があります。これはユーザーエージェント全体やタブの停止、同一オリジンの他のスクリプトには影響しませんが、この機能自体はブロックされます。

デッドロックを防ぐには注意が必要です。一つの方法は、複数のロックを必ず厳密な順序で取得することです。

以下のようなヘルパー関数を使うことで、複数のロックを一貫した順序でリクエストできます。

async function requestMultiple(resources, callback) {
  const sortedResources = [...resources];
  sortedResources.sort(); // 必ず同じ順序でリクエストする。
  async function requestNext(locks) {
    return await navigator.locks.request(sortedResources.shift(), async lock => {
      // このロックと、これまでに取得したロックを保持している。
      locks.push(lock);
      // 必要なら次のロックを再帰的にリクエスト。
      if (sortedResources.length > 0)
        return await requestNext(locks);
      // すべて取得したらコールバックを実行。
      return await callback(locks);
      // コールバックが返る(またはthrow)と全ロックが解放される。
    });
  }
  return await requestNext([]);
}

実際には、複数ロックの利用はそれほど単純ではなく、ライブラリや他のユーティリティが意図せず利用状況を複雑にすることがあります。

6. セキュリティとプライバシーに関する考慮事項

6.1. ロックスコープ

ロックマネージャのスコープ定義は、プライバシー境界を定めるため重要です。ロックは一時的な状態保持手段として使われるほか、ストレージAPI同様に通信手段としても使われ得るため、ストレージ機能以上の権限を持ってはなりません。ユーザーエージェントがこれらのサービスの一つに細かい粒度を課す場合、他のサービスにも同様の粒度を課す必要があります。例えば、プライバシーのために同一オリジン内でトップレベルページ(ファーストパーティ)とクロスオリジンiframe(サードパーティ)に異なるストレージパーティションを提供する場合、ロックも同様に分割しなければなりません。

これはWebアプリケーションの作者にとっても合理的な期待を与えます。ストレージリソースに対してロックを取得した場合、同一オリジンのすべての閲覧コンテキストが同じ状態を観測できなければなりません。

6.2. プライベートブラウジング

すべてのプライベートモードの閲覧セッションは、このAPIの目的上、別のユーザーエージェントとみなされます。つまり、そのようなセッション外でリクエスト・保持されたロックは、セッション内のリクエスト・保持に影響せず、逆も同様です。これにより、ウェブサイトがセッションが「シークレット」であるかを判定したり、セッション間で通信手段として使うことを防ぎます。

6.3. 実装上のリスク

実装では、ロックがオリジンをまたがらないことを保証しなければなりません。これを怠ると、2つのオリジンで動作するスクリプト間の通信のためのサイドチャネルが生じたり、一方のオリジンのスクリプトが他方の動作を妨害(例:サービス拒否)することが可能になります。

6.4. チェックリスト

W3C TAGは、仕様編集者が参考として回答できるセキュリティ・プライバシー自己レビュー質問票を作成しています。ここでの質問を再確認します:

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.にも特別な謝意を表します。

適合性

文書の慣例

適合性要件は、記述的な断定とRFC 2119の用語の組み合わせで表現されます。 規範的な部分で使われる “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, “OPTIONAL” などのキーワードは、RFC 2119で定義された通りに解釈されます。 ただし、可読性のため、本仕様ではこれらの語はすべて大文字では記載されていません。

本仕様のすべてのテキストは規範的です。ただし、明示的に非規範的と記載されたセクション、例、注記は除きます。[RFC2119]

本仕様の例は “for example” という語で始まるか、class="example" を付与して規範的な本文から区別されます。例えば次のようになります:

これは情報提供的な例です。

情報提供的な注記は “Note” で始まり、class="note" で規範的な本文から区別されます。例えば次のようになります:

Note, これは情報提供的な注記です。

適合するアルゴリズム

アルゴリズムの一部として命令形で記述された要件(例:"strip any leading space characters" や "return false and abort these steps")は、アルゴリズムの導入時に使われているキーワード("must", "should", "may" など)の意味で解釈されます。

アルゴリズムや特定の手順として記述された適合性要件は、最終的な結果が同等である限り、どのような方法で実装しても構いません。 特に、本仕様で定義されているアルゴリズムは理解しやすいことを意図しており、性能を重視したものではありません。 実装者は最適化することが推奨されます。

索引

本仕様で定義されている用語

参照で定義される用語

参考文献

規範的参考文献

[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[Storage]
Anne van Kesteren. Storage Standard. Living Standard. URL: https://storage.spec.whatwg.org/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

参考情報

[IndexedDB-2]
Ali Alabbas; Joshua Bell. Indexed Database API 2.0. 2018年1月30日. REC. URL: https://www.w3.org/TR/IndexedDB-2/
[Service-Workers]
Yoshisato Yanagisawa; Monica CHINTALA. Service Workers. 2025年3月6日. CRD. URL: https://www.w3.org/TR/service-workers/

IDL索引

[SecureContext]
interface mixin NavigatorLocks {
  readonly attribute LockManager locks;
};
Navigator includes NavigatorLocks;
WorkerNavigator includes NavigatorLocks;

[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 mode = "exclusive";
  boolean ifAvailable = false;
  boolean steal = false;
  AbortSignal signal;
};

dictionary LockManagerSnapshot {
  sequence<LockInfo> held;
  sequence<LockInfo> pending;
};

dictionary LockInfo {
  DOMString name;
  LockMode mode;
  DOMString clientId;
};

[SecureContext, Exposed=(Window,Worker)]
interface Lock {
  readonly attribute DOMString name;
  readonly attribute LockMode mode;
};

課題索引

[Storage]との統合方法(特に環境からロックマネージャを正しく取得する方法)をここで精緻化すること。
これは現在ワーカーのみを対象としており、ワーカー終了時に手順を実行する規範的な方法がないため、定義が曖昧です。
MDN

Lock/mode

In all current engines.

Firefox96+Safari15.4+Chrome69+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

Lock/name

In all current engines.

Firefox96+Safari15.4+Chrome69+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

Lock

In all current engines.

Firefox96+Safari15.4+Chrome69+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

LockManager/query

In all current engines.

Firefox96+Safari15.4+Chrome69+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

LockManager/request

In all current engines.

Firefox96+Safari15.4+Chrome69+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

LockManager

In all current engines.

Firefox96+Safari15.4+Chrome69+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

Navigator/locks

In all current engines.

Firefox96+Safari15.4+Chrome69+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?

WorkerNavigator/locks

In all current engines.

Firefox96+Safari15.4+Chrome69+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?