Cookie Store API

現行標準 — 最終更新日

参加方法:
GitHub whatwg/cookiestore (新しい課題, 未解決の課題)
Matrixでチャット
コミット履歴:
GitHub whatwg/cookiestore/commits
このコミット時点のスナップショット
@cookiestoreapi
テスト:
web-platform-tests cookiestore/ (作業中)
翻訳 (参考訳):
简体中文
日本語
한국어

概要

ドキュメントおよびService Worker向けの非同期JavaScriptクッキーAPIです。

1. はじめに

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

本標準は、HTMLドキュメントおよびService Workerで実行されるスクリプト向けの非同期クッキーAPIを定義します。

HTTPクッキーは、Netscapeで誕生して以来(archive.orgに保存されたドキュメント)、ウェブのための有用な状態管理機構を提供してきました。

クッキーへの同期的・単一スレッド・スクリプトレベルのdocument.cookie インターフェイスは、複雑さやパフォーマンス問題の原因となり、多くのブラウザが以下の点から移行したことでさらに悪化しました:

…から、現代のウェブは滑らかで高パフォーマンスな応答性を目指しています:

現代のウェブでは、ウェブアプリケーションの一部でのクッキー操作が以下をブロックできません:

Service Workerで構築された新しいウェブの部分もクッキーにアクセスする必要がありますが、同期・ブロッキングなdocument.cookie インターフェイスは利用できません。なぜなら、ドキュメントを持たず、イベントループをブロックしては他のイベント処理に支障があるためです。

1.1. document.cookieの代替

現在、クッキーを書き込む際はイベントループをブロックしながら、ブラウザが同期的にSet-Cookie形式のクッキー文字列でクッキージャーを更新するのを待つ必要があります:

document.cookie =
  '__Secure-COOKIENAME=cookie-value' +
  '; Path=/' +
  '; expires=Fri, 12 Aug 2016 23:05:17 GMT' +
  '; Secure' +
  '; Domain=example.org';
// これで書き込みが成功したと仮定できるが、失敗はサイレントなので判別しにくいため、
// 読み込みで書き込みが成功したか確認する必要がある
var successRegExp =
  /(^|; ?)__Secure-COOKIENAME=cookie-value(;|$)/;
if (String(document.cookie).match(successRegExp)) {
  console.log('成功しました!');
} else {
  console.error('成功しませんでした。理由は不明です');
}

代わりに、以下のように書けるとしたらどうでしょうか:

const one_day_ms = 24 * 60 * 60 * 1000;
cookieStore.set(
  {
    name: '__Secure-COOKIENAME',
    value: 'cookie-value',
    expires: Date.now() + one_day_ms,
    domain: 'example.org'
  }).then(function() {
    console.log('成功しました!');
  }, function(reason) {
    console.error(
      '成功しませんでした。理由はこちら:',
      reason);
  });
// 書き込みを待つ間、他の処理もできる…

この方法には、ドキュメントに依存せずブロッキングもしないという利点があり、これによってスクリプトからクッキーにアクセスできないService Workerでも利用可能です。

また、setTimeoutによるポーリング型のクッキー監視を、クッキー変更のオブザーバーAPIで置き換える省電力な監視も含まれます。

1.2. 概要

要約すると、本APIは以下の機能を提供します:

1.3. クッキーの検索

ドキュメントService Workerも、同じクエリAPIにアクセスできます。これはグローバルオブジェクトcookieStoreプロパティ経由です。

get()getAll()メソッドでクッキーを検索します。 どちらもPromiseを返します。 両メソッドは同じ引数を取ることができ、引数は下記のいずれかです:

get()は、実質的にはgetAll()の最初の結果のみを返す形です。

クッキーの読み取り:
try {
  const cookie = await cookieStore.get('session_id');
  if (cookie) {
    console.log(`「${cookie.name}」クッキーが見つかりました: ${cookie.value}`);
  } else {
    console.log('クッキーが見つかりません');
  }
} catch (e) {
  console.error(`クッキーストアエラー: ${e}`);
}
複数のクッキーの読み取り:
try {
  const cookies = await cookieStore.getAll('session_id'});
  for (const cookie of cookies)
    console.log(`結果: ${cookie.name} = ${cookie.value}`);
} catch (e) {
  console.error(`クッキーストアエラー: ${e}`);
}

Service Workerは、fetchで 自身のスコープ下の任意URLに送信されるクッキー一覧を取得できます。

特定URLのクッキーの読み取り(Service Worker内):
await cookieStore.getAll({url: '/admin'});

ドキュメントは、自身の現在URLのクッキーのみ取得できます。つまり、Documentコンテキストで有効なurl値はドキュメントのURLのみです。

get()getAll()で返されるオブジェクトには、クッキーストア内の関連情報がすべて含まれています。古いdocument.cookieAPIのようにnamevalueだけではありません。

クッキーデータ全体へのアクセス:
await cookie = cookieStore.get('session_id');
console.log(`クッキーのスコープ - Domain: ${cookie.domain} Path: ${cookie.path}`);
if (cookie.expires === null) {
  console.log('クッキーはセッション終了時に期限切れ');
} else {
  console.log(`クッキーの有効期限: ${cookie.expires}`);
}
if (cookie.secure)
  console.log('このクッキーはセキュアオリジンに限定されています');

1.4. クッキーの変更

ドキュメントService Workerも、同じ変更APIにアクセスできます。これはグローバルオブジェクトcookieStoreプロパティ経由です。

クッキーの作成や変更(書き込み)はset()メソッドで行います。

クッキーの書き込み:
try {
  await cookieStore.set('opted_out', '1');
} catch (e) {
  console.error(`クッキーの設定に失敗: ${e}`);
}

上記のset()呼び出しは、オプション辞書を使う場合の省略形です:

await cookieStore.set({
  name: 'opted_out',
  value: '1',
  expires: null,  // セッションクッキー

  // デフォルトではdomainはnull(現在のドメインに限定)
  domain: null,
  path: '/'
});

クッキーの削除(有効期限切れ)はdelete()メソッドで行います。

クッキーの削除:
try {
  await cookieStore.delete('session_id');
} catch (e) {
  console.error(`クッキーの削除に失敗: ${e}`);
}

内部的には、削除はクッキーの有効期限を過去に変更することで実現されます。

有効期限変更によるクッキーの削除:
try {
  const one_day_ms = 24 * 60 * 60 * 1000;
  await cookieStore.set({
    name: 'session_id',
    value: 'value will be ignored',
    expires: Date.now() - one_day_ms });
} catch (e) {
  console.error(`クッキーの削除に失敗: ${e}`);
}

1.5. クッキーの監視

ポーリングを避けるため、クッキーの変更を監視できます。

ドキュメントでは、すべての関連クッキー変更に対してchangeイベントが発火されます。

ドキュメントでchangeイベントを登録:
cookieStore.addEventListener('change', event => {
  console.log(`${event.changed.length} 件のクッキーが変更されました`);
  for (const cookie in event.changed)
    console.log(`クッキー ${cookie.name}${cookie.value} に変更されました`);

  console.log(`${event.deleted.length} 件のクッキーが削除されました`);
  for (const cookie in event.deleted)
    console.log(`クッキー ${cookie.name} が削除されました`);
});

Service Workerでは、グローバルスコープにcookiechangeイベントが発火されますが、Service Workerの登録ごとに明示的な購読が必要です。

Service Workerでcookiechangeイベントを登録:
self.addEventListener('activate', (event) => {
  event.waitUntil(async () => {
    // 現在の購読状態をスナップショット
    const subscriptions = await self.registration.cookies.getSubscriptions();

    // 既存の購読をすべて解除
    await self.registration.cookies.unsubscribe(subscriptions);

    await self.registration.cookies.subscribe([
      {
        name: 'session_id',  // session_idという名前のクッキー変更イベントを受信
      }
    ]);
  });
});

self.addEventListener('cookiechange', event => {
  // eventの|changed|や|deleted|プロパティはDocumentイベントと同じ意味
  console.log(`${event.changed.length} 件のクッキーが変更されました`);
  console.log(`${event.deleted.length} 件のクッキーが削除されました`);
});

subscribe()の呼び出しは累積されるため、独立したモジュールやライブラリがそれぞれ購読を設定できます。Service Workerの購読状態はService Worker登録に対して永続化されます。

購読はget()getAll()と同じオプションを使えます。 Service Workerへの無関係なクッキー変更イベントの配送コストが高いため、細かい購読の複雑さは正当化されます。 Windowへの配送コストより、Service Workerへの配送はWorkerの起動が必要な場合もあり、バッテリー消費に大きな影響があります。

getSubscriptions()は、Service Workerで購読状況を確認できます。

変更購読の確認:
const subscriptions = await self.registration.cookies.getSubscriptions();
for (const sub of subscriptions) {
  console.log(sub.name, sub.url);
}

2. 概念

クッキーは、ユーザーエージェント向けにCookies § User Agent Requirementsで規定されています。

Cookies § Storage Modelによると、クッキーは以下のフィールドを持ちます: name, value, domain, path, http-only-flag

クッキー名または値を正規化するには、string inputが与えられた場合、 inputの先頭または末尾にあるU+0009 TABおよびU+0020 SPACEをすべて削除する。

クッキーは、スコープ内かつhttp-only-flagが未設定の時、スクリプトから見える状態です。これはより厳密には処理モデルで強制され、適切なタイミングでCookies § Retrieval Modelを参照します。

クッキーにはサイズ制限もあります。Cookies § Storage Modelによると:

クッキーの属性値はバイト列として保存され、文字列ではありません。

クッキーストアは、ユーザーエージェント向けにCookies § User Agent Requirementsで規定されています。

クッキーストアに対して以下のいずれかが発生したときは、クッキー変更処理を実行します。

2.3. Service Workerへの拡張

[Service-Workers]Service Worker登録を定義しており、本仕様はこれを拡張します。

Service Worker登録には関連付けられたクッキー変更購読リストがあり、これはリストです。 各メンバーはクッキー変更購読です。クッキー変更購読 タプル(組)で、 nameurl から成ります。

3. CookieStore インターフェイス

[Exposed=(ServiceWorker,Window),
 SecureContext]
interface CookieStore : EventTarget {
  Promise<CookieListItem?> get(USVString name);
  Promise<CookieListItem?> get(optional CookieStoreGetOptions options = {});

  Promise<CookieList> getAll(USVString name);
  Promise<CookieList> getAll(optional CookieStoreGetOptions options = {});

  Promise<undefined> set(USVString name, USVString value);
  Promise<undefined> set(CookieInit options);

  Promise<undefined> delete(USVString name);
  Promise<undefined> delete(CookieStoreDeleteOptions options);

  [Exposed=Window]
  attribute EventHandler onchange;
};

dictionary CookieStoreGetOptions {
  USVString name;
  USVString url;
};

enum CookieSameSite {
  "strict",
  "lax",
  "none"
};

dictionary CookieInit {
  required USVString name;
  required USVString value;
  DOMHighResTimeStamp? expires = null;
  USVString? domain = null;
  USVString path = "/";
  CookieSameSite sameSite = "strict";
  boolean partitioned = false;
};

dictionary CookieStoreDeleteOptions {
  required USVString name;
  USVString? domain = null;
  USVString path = "/";
  boolean partitioned = false;
};

dictionary CookieListItem {
  USVString name;
  USVString value;
};

typedef sequence<CookieListItem> CookieList;

3.1. get() メソッド

cookie = await cookieStore . get(name)
cookie = await cookieStore . get(options)

指定したクッキー名(または他のオプション)に対して、スコープ内の最初のスクリプトから見える値を解決するPromiseを返します。 Service Workerのコンテキストでは、Service Workerの登録スコープのパスがデフォルトになります。 ドキュメントでは、現在のドキュメントのパスがデフォルトとなり、replaceState()document.domainでの変更は反映されません。

get(name)メソッドの手順:
  1. settingsに、this関連設定オブジェクトを設定する。

  2. originに、settingsオリジンを設定する。

  3. origin不透明オリジンの場合、"SecurityError" DOMExceptionPromiseを拒否して返す。

  4. urlに、settings作成URLを設定する。

  5. pに、新しいPromiseを設定する。

  6. 以下の手順を並行して実行:

    1. listurlnameクッキー検索を実行した結果を設定する。

    2. listが失敗なら、TypeErrorp拒否し、この手順を中断する。

    3. listなら、pをnullで解決する。

    4. それ以外の場合、plistの最初の要素で解決する。

  7. pを返す。

get(options) メソッドの手順:
  1. settingsに、this関連設定オブジェクトを設定する。

  2. originに、settingsオリジンを設定する。

  3. origin不透明オリジンなら、"SecurityError" DOMExceptionPromiseを拒否して返す。

  4. urlに、settings作成URLを設定する。

  5. optionsが空なら、TypeErrorPromiseを拒否して返す。

  6. options["url"] が存在する場合、以下の手順を実行:

    1. parsedに、options["url"]をsettingsAPI基本URLパースした結果を設定する。

    2. this関連グローバルオブジェクトWindow かつparsedurlと等しくない場合(exclude fragmentsをtrue)、TypeErrorPromiseを拒否して返す。

    3. parsedoriginurlorigin同一オリジンでない場合、TypeErrorPromiseを拒否して返す。

    4. urlparsedを設定する。

  7. p新しいPromiseを設定する。

  8. 以下の手順を並行して実行:

    1. listを、urlおよびoptions["name"]でクッキーを照会するを実行した結果とする。ただし、デフォルト値付きでnullを指定する。

    2. listが失敗なら、TypeErrorp拒否する。

    3. listなら、pをnullで解決する。

    4. それ以外の場合、plistの最初の要素で解決する。

  9. pを返す。

3.2. getAll() メソッド

cookies = await cookieStore . getAll(name)
cookies = await cookieStore . getAll(options)

指定したクッキー名(または他のオプション)に対して、スコープ内のすべてのスクリプトから見える値を解決するPromiseを返します。 Service Workerのコンテキストでは、Service Workerの登録スコープのパスがデフォルトとなります。 ドキュメントでは、現在のドキュメントのパスがデフォルトとなり、replaceState()document.domainの変更は反映されません。

getAll(name)メソッドの手順:
  1. settingsに、this関連設定オブジェクトを設定する。

  2. originに、settingsオリジンを設定する。

  3. origin不透明オリジンの場合、"SecurityError" DOMExceptionPromiseを拒否して返す。

  4. urlに、settings作成URLを設定する。

  5. pに、新しいPromiseを設定する。

  6. 以下の手順を並行して実行:

    1. listurlnameクッキー検索を実行した結果を設定する。

    2. listが失敗なら、TypeErrorp拒否する。

    3. それ以外の場合、plist解決する。

  7. pを返す。

getAll(options)メソッドの手順:
  1. settingsに、this関連設定オブジェクトを設定する。

  2. originに、settingsオリジンを設定する。

  3. origin不透明オリジンの場合、"SecurityError" DOMExceptionPromiseを拒否して返す。

  4. urlに、settings作成URLを設定する。

  5. options["url"] が存在する場合、以下の手順を実行:

    1. parsedに、options["url"]をsettingsAPI基本URLパースした結果を設定する。

    2. this関連グローバルオブジェクトWindow かつparsedurlと等しくない場合(exclude fragmentsをtrue)、TypeErrorPromiseを拒否して返す。

    3. parsedoriginurlorigin同一オリジンでない場合、TypeErrorPromiseを拒否して返す。

    4. urlparsedを設定する。

  6. p新しいPromiseを設定する。

  7. 以下の手順を並行して実行:

    1. listを、urlおよびoptions["name"]でクッキーを照会するを実行した結果とする。ただし、デフォルト値付きでnullを指定する。

    2. listが失敗なら、TypeErrorp拒否する。

    3. それ以外の場合、plist解決する。

  8. pを返す。

3.3. set() メソッド

await cookieStore . set(name, value)
await cookieStore . set(options)

クッキーを書き込みます(作成または変更)。

オプションのデフォルト値:

  • Path: /

  • Domain: 現在のドキュメントまたはService Workerの場所と同じドメイン

  • 有効期限なし

  • SameSite: strict

set(name, value)メソッドの手順:
  1. settingsに、this関連設定オブジェクトを設定する。

  2. originに、settingsオリジンを設定する。

  3. origin不透明オリジンの場合、"SecurityError" DOMExceptionPromiseを拒否して返す。

  4. urlに、settings作成URLを設定する。

  5. domainにnullを設定する。

  6. pathに"/"を設定する。

  7. sameSitestrictを設定する。

  8. partitionedにfalseを設定する。

  9. p新しいPromiseを設定する。

  10. 以下の手順を並行して実行:

    1. rに、url, name, value, domain, path, sameSite, partitionedクッキー設定を実行した結果を設定する。

    2. rが失敗なら、TypeErrorp拒否し、この手順を中断する。

    3. pをundefinedで解決する。

  11. pを返す。

set(options)メソッドの手順:
  1. settingsに、this関連設定オブジェクトを設定する。

  2. originに、settingsオリジンを設定する。

  3. origin不透明オリジンなら、"SecurityError" DOMExceptionPromiseを拒否して返す。

  4. urlに、settings作成URLを設定する。

  5. p新しいPromiseを設定する。

  6. 以下の手順を並行して実行:

    1. rに、url, options["name"], options["value"], options["expires"], options["domain"], options["path"], options["sameSite"], options["partitioned"]でクッキー設定を実行した結果を設定する。

    2. rが失敗なら、TypeErrorp拒否し、この手順を中断する。

    3. pをundefinedで解決する。

  7. pを返す。

3.4. delete() メソッド

await cookieStore . delete(name)
await cookieStore . delete(options)

指定した名前、または名前+オプションのドメインとパスで、クッキーを削除(有効期限切れ)します。

delete(name)メソッドの手順:
  1. settingsに、this関連設定オブジェクトを設定する。

  2. originに、settingsオリジンを設定する。

  3. origin不透明オリジンの場合、"SecurityError" DOMExceptionPromiseを拒否して返す。

  4. urlに、settings作成URLを設定する。

  5. p新しいPromiseを設定する。

  6. 以下の手順を並行して実行:

    1. rに、url, name, null, "/", trueでクッキー削除を実行した結果を設定する。

    2. rが失敗なら、TypeErrorp拒否し、この手順を中断する。

    3. pをundefinedで解決する。

  7. pを返す。

delete(options)メソッドの手順:
  1. settingsに、this関連設定オブジェクトを設定する。

  2. originに、settingsオリジンを設定する。

  3. origin不透明オリジンの場合、"SecurityError" DOMExceptionPromiseを拒否して返す。

  4. urlに、settings作成URLを設定する。

  5. p新しいPromiseを設定する。

  6. 以下の手順を並行して実行:

    1. rに、url, options["name"], options["domain"], options["path"], options["partitioned"]でクッキー削除を実行した結果を設定する。

    2. rが失敗なら、TypeErrorp拒否し、この手順を中断する。

    3. pをundefinedで解決する。

  7. pを返す。

4. CookieStoreManager インターフェイス

CookieStoreManager には、関連付けられたregistrationService Worker登録)があります。

CookieStoreManager インターフェイスは、Service Workerがクッキー変更イベントを購読できるようにします。subscribe() メソッドの利用で、特定のService Worker登録が変更イベントに関心があることを示します。

[Exposed=(ServiceWorker,Window),
 SecureContext]
interface CookieStoreManager {
  Promise<undefined> subscribe(sequence<CookieStoreGetOptions> subscriptions);
  Promise<sequence<CookieStoreGetOptions>> getSubscriptions();
  Promise<undefined> unsubscribe(sequence<CookieStoreGetOptions> subscriptions);
};

4.1. subscribe() メソッド

await registration . cookies . subscribe(subscriptions)

クッキーの変更を購読します。購読はget()getAll()と同じオプション(nameurlプロパティ)を使えます。

購読後は、通知がcookiechangeイベントとしてService Workerのグローバルスコープで発火されます。

subscribe(subscriptions)メソッドの手順:
  1. settingsに、this関連設定オブジェクトを設定。

  2. registrationに、thisregistrationを設定。

  3. p新しいPromiseを設定。

  4. 以下の手順を並行して実行:

    1. subscription listregistrationに関連付けられたクッキー変更購読リストとする。

    2. entrysubscriptions内で順に、次の手順を実行する:

      1. nameentry["name"]とする。

      2. nameを正規化する

      3. urlを、entry["url"]をsettingsAPI基本URL解析した結果とする。

      4. もしurlregistrationスコープURLで始まらない場合、pをTypeErrorで拒否し、これ以降の手順を中止する。

      5. subscriptionクッキー変更購読(name, url)とする。

      6. もしsubscription listがすでにsubscriptionを含まない場合、subscriptionをsubscription listに追加する。

    3. pをundefinedで解決する

  5. pを返す。

4.2. getSubscriptions() メソッド

subscriptions = await registration . cookies . getSubscriptions()

このメソッドは、このService Worker登録に対して行われたクッキー変更購読のリストを返すPromiseを返します。

getSubscriptions()メソッドの手順:
  1. registrationに、thisregistrationを設定。

  2. p新しいPromiseを設定。

  3. 以下の手順を並行して実行:

    1. subscriptionsregistrationの関連付けられたクッキー変更購読リストを設定する。

    2. resultに新しいリストを設定する。

    3. subscriptionについてsubscriptionsを順に処理:

      1. 追加 «[ "name" → subscriptionname, "url" → subscriptionurl]» をresultに。

    4. pをresultで解決する

  4. pを返す。

4.3. unsubscribe() メソッド

await registration . cookies . unsubscribe(subscriptions)

このメソッドを呼ぶと、登録済みService Workerは以前購読していたイベントの受信を停止します。 subscriptions引数は、subscribe() で渡したもの、またはgetSubscriptions() で返されたものと同じ形式であるべきです。

unsubscribe(subscriptions)メソッドの手順:
  1. settingsに、this関連設定オブジェクトを設定。

  2. registrationに、thisregistrationを設定。

  3. p新しいPromiseを設定。

  4. 以下の手順を並行して実行:

    1. subscription listregistrationの関連付けられたクッキー変更購読リストを設定する。

    2. entryについてsubscriptionsを順に処理:

      1. nameentry["name"]とする。

      2. nameを正規化する

      3. urlを、entry["url"]をsettingsAPI基本URL解析した結果とする。

      4. もしurlregistrationスコープURLで始まらない場合、pをTypeErrorで拒否し、これ以降の手順を中止する。

      5. subscriptionクッキー変更購読(name, url)とする。

      6. 削除する、アイテムで、subscription listからsubscriptionと等しいものを。

    3. pをundefinedで解決する

  5. pを返す。

4.4. ServiceWorkerRegistration インターフェイス

ServiceWorkerRegistration インターフェイスは、CookieStoreManager へのアクセスを提供するよう拡張されています。cookiesプロパティ経由で、クッキー変更購読用インターフェイスが使えます。

[Exposed=(ServiceWorker,Window)]
partial interface ServiceWorkerRegistration {
  [SameObject] readonly attribute CookieStoreManager cookies;
};

ServiceWorkerRegistration には関連するCookieStoreManager オブジェクトがあります。 CookieStoreManagerregistrationは、ServiceWorkerRegistrationService Worker登録と等しいです。

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

Service Workerスクリプトでクッキー変更を購読:
self.registration.cookies.subscribe([{name:'session-id'}]);
ウィンドウコンテキストのスクリプトからクッキー変更を購読:
navigator.serviceWorker.register('sw.js').then(registration => {
  registration.cookies.subscribe([{name:'session-id'}]);
});

5. イベントインターフェイス

5.1. CookieChangeEvent インターフェイス

CookieChangeEvent は、CookieStore オブジェクトに対して、Window コンテキストで、スクリプトから見えるクッキーが変更されたときにdispatchされます。

[Exposed=Window,
 SecureContext]
interface CookieChangeEvent : Event {
  constructor(DOMString type, optional CookieChangeEventInit eventInitDict = {});
  [SameObject] readonly attribute FrozenArray<CookieListItem> changed;
  [SameObject] readonly attribute FrozenArray<CookieListItem> deleted;
};

dictionary CookieChangeEventInit : EventInit {
  CookieList changed;
  CookieList deleted;
};

changeddeleted 属性は初期化時の値を返さなければなりません。

5.2. ExtendableCookieChangeEvent インターフェイス

ExtendableCookieChangeEvent は、ServiceWorkerGlobalScope オブジェクトに対して、スクリプトから見える クッキーの変更が発生し、その変更がService Workerクッキー変更購読リストに一致する場合にdispatchされます。

注: ExtendableEvent は、Service Worker内のすべてのイベントの祖先インターフェイスとして使われており、非同期処理中にWorker自体を存続させるためです。

[Exposed=ServiceWorker]
interface ExtendableCookieChangeEvent : ExtendableEvent {
  constructor(DOMString type, optional ExtendableCookieChangeEventInit eventInitDict = {});
  [SameObject] readonly attribute FrozenArray<CookieListItem> changed;
  [SameObject] readonly attribute FrozenArray<CookieListItem> deleted;
};

dictionary ExtendableCookieChangeEventInit : ExtendableEventInit {
  CookieList changed;
  CookieList deleted;
};

changeddeleted 属性は初期化時の値を返さなければなりません。

6. グローバルインターフェイス

CookieStore には、グローバルスコープの属性として、Window またはServiceWorkerGlobalScope コンテキストで、スクリプトからアクセスできます。

6.1. Window インターフェイス

[SecureContext]
partial interface Window {
  [SameObject] readonly attribute CookieStore cookieStore;
};

Window には、関連付けられたCookieStoreがあり、これはCookieStoreです。

cookieStoreゲッターの手順は、this関連付けられたCookieStoreを返すことです。

6.2. ServiceWorkerGlobalScope インターフェイス

partial interface ServiceWorkerGlobalScope {
  [SameObject] readonly attribute CookieStore cookieStore;

  attribute EventHandler oncookiechange;
};

ServiceWorkerGlobalScope には、関連付けられたCookieStoreがあり、これはCookieStoreです。

cookieStoreゲッターの手順は、this関連付けられたCookieStoreを返すことです。

7. アルゴリズム

dateTimeタイムスタンプとして表すには、 dateTimeが1970年1月1日00:00:00 UTCから何ミリ秒経過したかを返す(1日=86,400,000ミリ秒とする)。

注: これはECMAScript[ECMAScript]で使われている表現と同じです。

date serializeDOMHighResTimeStamp millisを扱うには、 dateTimeを1970年1月1日00:00:00 UTCからmillisミリ秒後の日時とし(1日=86,400,000ミリ秒とする)、 そのdateTimeの最も近いcookie-date表現に対応するバイト列を返す(Cookies § Dates参照)。

7.1. クッキーの検索

クッキーを照会するには、URL urlと、stringまたはnullのnameが与えられる:

  1. Cookies § 取得モデルで定義された手順を実行し、urlrequest-uriとして「cookie-string from a given cookie store」を算出する。cookie-string自体は無視し、中間のcookie-listを以降の手順で利用する。

    この手順では、cookie-stringは「非HTTP」APIのために生成される。

  2. listを新しいリストとする。

  3. cookiecookie-listで順に、次の手順を実行する:

    1. Assert: cookiehttp-only-flagはfalseである。

    2. もしnameがnullでない場合:

      1. nameを正規化する

      2. cookieNameを、cookienameBOMなしUTF-8デコードを実行した結果とする。

      3. もしcookieNamenameと一致しない場合、continueする。

    3. itemcreate a CookieListItemcookieから生成した結果とする。

    4. itemをlistに追加する。

  4. listを返す。

CookieListItemを生成するには、cookie cookieが与えられる:

  1. nameを、cookienameBOMなしUTF-8デコードを実行した結果とする。

  2. valueを、cookievalueBOMなしUTF-8デコードを実行した結果とする。

  3. «[ "name" → name, "value" → value ]»を返す。

Note: 1つの実装は_name_と_value_以外の情報も公開することが知られている。

クッキーを設定するには、 urlnamevalue、 任意のexpiresdomainpathsameSitepartitioned を用いて、以下の手順を実行する:

  1. nameを正規化する

  2. valueを正規化する

  3. もしnameまたはvalueがU+003B(;)、U+0009 TAB以外のC0制御文字、またはU+007F DELETEを含む場合、失敗を返す。

    この文字制限がexpiresdomainpathsameSiteにも適用されるかどうかは議論中。[httpwg/http-extensions Issue #1593]

  4. もしnameがU+003D(=)を含む場合、失敗を返す。

  5. もしname長さが0の場合:

    1. もしvalueがU+003D(=)を含む場合、失敗を返す。

    2. もしvalue長さが0の場合、失敗を返す。

    3. もしvalueバイト小文字化し、次で始まる場合:__host-__host-http-__http-__secure-、失敗を返す。

  6. もしnameバイト小文字化し、次で始まる場合:__host-http-または__http-、失敗を返す。

  7. encodedNamenameUTF-8エンコードした結果とする。

  8. encodedValuevalueUTF-8エンコードした結果とする。

  9. encodedNameバイト系列長さと、encodedValueバイト系列長さの合計が最大name/valueペアサイズを超える場合、失敗を返す。

  10. hosturlホストとする。

  11. attributesを新しいリストとする。

  12. もしdomainがnullでない場合、次の手順を実行する:

    1. もしdomainがU+002E(.)で始まる場合、失敗を返す。

    2. もしnameバイト小文字化し、次で始まる場合:__host-、失敗を返す。

    3. もしdomainhost登録可能なドメインサフィックスでなく、等しくもない場合、失敗を返す。

    4. parsedDomaindomainホストパースした結果とする。

    5. Assert: parsedDomainは失敗ではない。

    6. encodedDomainparsedDomainUTF-8エンコードした結果とする。

    7. encodedDomainバイト系列長さ最大属性値サイズを超える場合、失敗を返す。

    8. Domain/encodedDomainをattributesに追加する。

  13. expires が与えられている場合、append `Expires`/expires日付の直列化)を attributes に追加する。

  14. path が空文字列の場合、path直列化された Cookie のデフォルトパスurl のもの)を設定する。

  15. path が U+002F(/)で始まらない場合、失敗を返す。

  16. path が U+002F(/)でない場合、かつ nameバイト小文字化し、`__host-` で始まる場合、失敗を返す。

  17. encodedPathUTF-8 エンコードした path とする。

  18. バイト列 長さencodedPath において、 最大属性値サイズ より大きい場合、失敗を返す。

  19. `Path`/encodedPathattributes に追加する。

  20. `Secure`/`` を attributes に追加する。

  21. sameSite に応じて切り替える:

    "none"

    `SameSite`/`None` を attributes に追加する。

    "strict"

    `SameSite`/`Strict` を attributes に追加する。

    "lax"

    `SameSite`/`Lax` を attributes に追加する。

  22. partitioned が true の場合、`Partitioned`/`` を attributes に追加する。

  23. Cookies § Storage Model に定義されている手順を、ユーザーエージェントが「Cookie を受け取った」ときについて、 urlrequest-uri として、 encodedNamecookie-name として、 encodedValuecookie-value として、 attributescookie-attribute-list として実行する。

    これらの手順において、新しく作成された Cookie は「非 HTTP」API から受け取ったものとみなす。

  24. 成功を返す。

    注: Cookie の保存は [RFC6265BIS-14] の要件により失敗する場合があるが、 これらの手順は成功したものとみなされる。

クッキーを削除するには、 urlnamedomainpathpartitioned を用いて、以下の手順を実行する:

  1. expiresを、タイムスタンプとして表される表現可能な最も早い日付とする。

    Note: このアルゴリズムの目的上、expiresの正確な値は重要ではなく、過去の日付であればよい。

  2. nameを正規化する

  3. valueを空文字列とする。

  4. もしname長さが0の場合、valueを空でない実装定義文字列に設定する。

  5. set a cookieurlnamevalueexpiresdomainpath、 "strict"、 partitioned で実行した結果を返す。

7.4. 変更の処理

クッキー変更処理を行うには、次の手順を実行:

  1. すべてのWindow windowについて、以下を実行:

    1. urlwindow関連設定オブジェクト作成URLを設定する。

    2. changesurl観測可能変更を設定する。

    3. changesなら、次へ

    4. グローバルタスクをキューに追加して、DOM操作タスクソースwindowに対し、 changeイベント発火(名前"change"、changeswindowCookieStore)を行う。

  2. すべてのService Worker登録 registrationについて、以下を実行:

    1. changesに新しいセットを設定。

    2. changeについて、registration観測可能変更registrationscope url)を順に処理:

      1. cookiechangeのクッキーを設定。

      2. subscriptionについてregistrationクッキー変更購読リストを順に処理:

        1. changesubscription観測可能変更subscriptionurl)に含まれなければ、次へ

        2. cookieNamecookienameUTF-8 BOMなしでデコードした結果を設定。

        3. cookieNamesubscriptionnameと一致すれば、changeをchangesに追加して、breakする。

    3. changesなら、次へ

    4. changedListdeletedListchangesからリスト準備を実行した結果を設定。

    5. 機能イベント発火(名前"cookiechange"、ExtendableCookieChangeEventregistration)を、下記プロパティで行う:

      changed

      changedList

      deleted

      deletedList

url観測可能変更は、セットであり、クッキー変更で、クッキークッキーストア内にあり、Cookies § Retrieval Algorithmの「cookie-string from a given cookie store」算出手順の1番に合致し、urlrequest-uri、非HTTP APIとする。

クッキー変更は、 クッキーとタイプ(changedまたはdeleted)の組み合わせ:

typeという名前で、changestargetchangeイベント発火するには、次の手順を行う:

  1. eventCookieChangeEventEvent生成を設定。

  2. eventtype属性にtypeを設定。

  3. eventbubblescancelable属性をfalseに設定。

  4. changedListdeletedListに、changesからリスト準備した結果を設定。

  5. eventchanged属性にchangedListを設定。

  6. eventdeleted属性にdeletedListを設定。

  7. eventをtargetでdispatchする。

changesからリスト準備を行うには、次の手順:

  1. changedListに新しいリストを設定。

  2. deletedListに新しいリストを設定。

  3. changeについてchangesを順に処理:

    1. itemchangeのクッキーからCookieListItem生成を実行した結果を設定。

    2. changeのtypeがchangedなら、itemをchangedListに追加

    3. それ以外の場合、以下を実行:

      1. item["value"]にundefinedを設定。

      2. itemをdeletedListに追加

  4. changedListdeletedListを返す。

8. セキュリティに関する考慮事項

Service Workerコンテキストからのクッキーアクセス以外、このAPIはウェブに新たな機能を公開することを意図していません。

8.1. 注意点!

ブラウザのクッキー実装は現在より良いセキュリティと予想外・誤りやすいデフォルトの削減に進化していますが、現状クッキーデータのセキュリティについてはほとんど保証がありません。

これらの理由から、クッキーの値を解釈する際は注意し、クッキーの値をスクリプト、HTML、CSS、XML、PDF、その他実行可能な形式として決して実行しないでください。

8.2. 制限?

このAPIはクッキー利用を容易にし、結果としてクッキー利用をさらに促進する副作用があるかもしれません。非セキュアなコンテキストでクッキー利用が促進されると、ユーザーにとって安全性が低下するウェブになる可能性があります。そのため、このAPIはセキュアなコンテキスト限定としています。

8.3. セキュアクッキー

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

このAPIはセキュリティ向上のためSecureクッキーのみ書き込み可能です。ただし、Secureでないクッキーも読み取り可能で、Secureクッキーへの移行を容易にしています。副作用として、このAPIで非Secureクッキーを取得・変更した場合、そのクッキーは自動的にSecureに変更されます。

8.4. 驚き

既存のクッキー挙動(特にドメイン志向、非セキュアなコンテキストからSecureコンテキストで読めるクッキーを設定できること、スクリプトから読めないクッキーをスクリプトで設定できること等)は、ウェブセキュリティ観点でかなり驚くべきものです。

その他の驚きはCookies § Introductionでも記載されており、例えばクッキーはスーパー・ドメイン(例: app.example.comがexample.com全体)に設定できる、クッキーはドメイン名の任意ポート番号で読める等があります。

さらに主要ブラウザ間のクッキー処理の歴史的な違いも複雑化要因ですが、最近は(例:ポート番号処理)は以前より一貫性が向上しています。

8.5. プレフィックス

可能な限り、例では__Host-__Secure-の名前プレフィックスを使っています。これにより現行ブラウザの一部では、非セキュアなコンテキストからの上書き、Secureフラグなし上書き、さらに__Host-の場合は明示的なDomainや非'/' Path属性での上書き(事実上same-origin制約)が禁止されます。これらプレフィックスはSecure Cookieを実装しているブラウザで重要なセキュリティ効果を発揮し、他のブラウザでは劣化動作(特別な意味は強制しないがクッキー自体は通常通り動作、非同期APIではセキュアな意味を強制)します。本APIの大きな目的は既存クッキーとの相互運用なので、プレフィックス無しの例も一部記載しています。

プレフィックスの規則はこのAPIの書き込み操作でも強制されますが、他のAPIでは必ずしも同じ規則が適用されるとは限りません。そのため、規則の強制を過度に信頼するのは推奨されません(広範な採用がされるまで)。

8.6. URLスコープ

現状ではService Workerスクリプトから直接クッキーにアクセスできませんが、Service Workerが支配するHTMLやスクリプトリソースを制御描画することで、クッキー監視コードをリモート制御下に挿入でき、技術的にはService Workerのスコープ内でクッキーアクセスが可能です(使い勝手が悪いだけ)。

Service Workerのスコープが/より狭い場合でも、スコープ外パスのクッキーを404ページURLのIFRAME化などでスクリプトを実行し、読み取れる場合があります。同様の技術でオリジン全体にも拡張可能ですが、IFRAME化できないよう設計したサイトなら、パススコープのService Workerがこの機能を拒否できます。この制約の削除には追加議論が必要と考えています。

8.7. クッキー嫌い

開発者の複雑性削減と一時的なテストクッキー不要化のため、この非同期クッキーAPIは無視される操作の書き込み・削除を明示的に拒否します。同様に、実際のクッキーデータを無視し空のクッキーjarを模擬するような読み取りも明示的に拒否します。こうしたコンテキストでの変更監視は「動作」しますが、実際の読み取りが許可されるまでコールバックは呼ばれません(例:サイト権限変更時など)。

現状、document.cookie でスクリプトによるクッキー書き込み不可コンテキストでは通常何も起こりません。ただし、多くのスクリプトやフレームワークは必ずテストクッキーを書き、存在確認で書き込み可否を判定しています。

同様に、document.cookie で読み取り不可コンテキストでは通常空文字列が返ります。協力的なウェブサーバーはサーバー主導のクッキー書き込み・読み取りが機能することを検証し、スクリプトに報告できます(スクリプトは空文字列しか見えないが、これで読み取り不可を判別可能)。

9. プライバシーに関する考慮事項

9.1. クッキーの消去

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

ユーザーがあるオリジンのクッキーを消去した場合、ユーザーエージェントはそのオリジンのすべてのストレージ(Service Worker・DOMストレージ含む)も消去する必要があります。これは、ウェブサイトが消去後に永続ストレージでユーザー識別子を復元するのを防ぐためです。

謝辞

このAPIの初期提案者Benjamin Sittlerに感謝します。

Adam Barth、 Alex Russell、 Andrea Marchesini、 Andrew Williams、 Anne van Kesteren、 Ayu Ishii、 Ben Kelly、 Craig Francis、 Daniel Appelquist、 Daniel Murphy、 Domenic Denicola、 Elliott Sprehn、 Fagner Brack、 Idan Horowitz、 Jake Archibald、 Joel Weinberger、 Joshua Bell、 Kenneth Rohde Christiansen、 Lukasz Olejnik、 Marijn Kruisselbrink、 Mike West、 Raymond Toy、 Rupin Mittal、 Tab Atkins、 Victor Costan ほか、多くの方々に現行標準策定の支援をいただきました。

この現行標準はDylan Cutler(Google, dylancutler@google.com)によって執筆されています。

知的財産権

この現行標準は当初W3C WICGで開発され、W3C Software and Document Licenseの下で公開されました。

Copyright © WHATWG(Apple, Google, Mozilla, Microsoft)。この成果物はクリエイティブ・コモンズ 表示 4.0 国際ライセンスの下でライセンスされています。ソースコードへ組み込まれる部分については、ソースコード内でBSD 3-Clause Licenseの下でライセンスされます。

索引

本仕様で定義される用語

参照で定義される用語

参考文献

規定参考文献

[DOM]
Anne van Kesteren. DOM Standard. 現行標準. URL: https://dom.spec.whatwg.org/
[ENCODING]
Anne van Kesteren. Encoding Standard. 現行標準. URL: https://encoding.spec.whatwg.org/
[FETCH]
Anne van Kesteren. Fetch Standard. 現行標準. URL: https://fetch.spec.whatwg.org/
[HR-TIME-3]
Yoav Weiss. High Resolution Time. URL: https://w3c.github.io/hr-time/
[HTML]
Anne van Kesteren; et al. HTML Standard. 現行標準. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. 現行標準. URL: https://infra.spec.whatwg.org/
[RFC6265BIS-14]
S. Bingler; M. West; J. Wilander. Cookies: HTTP State Management Mechanism. Internet-Draft. URL: https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-14
[Service-Workers]
Yoshisato Yanagisawa; Monica CHINTALA. Service Workers. URL: https://w3c.github.io/ServiceWorker/
[URL]
Anne van Kesteren. URL Standard. 現行標準. URL: https://url.spec.whatwg.org/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. 現行標準. URL: https://webidl.spec.whatwg.org/

参考情報

[ECMAScript]
ECMAScript Language Specification. URL: https://tc39.es/ecma262/multipage/

IDL索引

[Exposed=(ServiceWorker,Window),
 SecureContext]
interface CookieStore : EventTarget {
  Promise<CookieListItem?> get(USVString name);
  Promise<CookieListItem?> get(optional CookieStoreGetOptions options = {});

  Promise<CookieList> getAll(USVString name);
  Promise<CookieList> getAll(optional CookieStoreGetOptions options = {});

  Promise<undefined> set(USVString name, USVString value);
  Promise<undefined> set(CookieInit options);

  Promise<undefined> delete(USVString name);
  Promise<undefined> delete(CookieStoreDeleteOptions options);

  [Exposed=Window]
  attribute EventHandler onchange;
};

dictionary CookieStoreGetOptions {
  USVString name;
  USVString url;
};

enum CookieSameSite {
  "strict",
  "lax",
  "none"
};

dictionary CookieInit {
  required USVString name;
  required USVString value;
  DOMHighResTimeStamp? expires = null;
  USVString? domain = null;
  USVString path = "/";
  CookieSameSite sameSite = "strict";
  boolean partitioned = false;
};

dictionary CookieStoreDeleteOptions {
  required USVString name;
  USVString? domain = null;
  USVString path = "/";
  boolean partitioned = false;
};

dictionary CookieListItem {
  USVString name;
  USVString value;
};

typedef sequence<CookieListItem> CookieList;

[Exposed=(ServiceWorker,Window),
 SecureContext]
interface CookieStoreManager {
  Promise<undefined> subscribe(sequence<CookieStoreGetOptions> subscriptions);
  Promise<sequence<CookieStoreGetOptions>> getSubscriptions();
  Promise<undefined> unsubscribe(sequence<CookieStoreGetOptions> subscriptions);
};

[Exposed=(ServiceWorker,Window)]
partial interface ServiceWorkerRegistration {
  [SameObject] readonly attribute CookieStoreManager cookies;
};

[Exposed=Window,
 SecureContext]
interface CookieChangeEvent : Event {
  constructor(DOMString type, optional CookieChangeEventInit eventInitDict = {});
  [SameObject] readonly attribute FrozenArray<CookieListItem> changed;
  [SameObject] readonly attribute FrozenArray<CookieListItem> deleted;
};

dictionary CookieChangeEventInit : EventInit {
  CookieList changed;
  CookieList deleted;
};

[Exposed=ServiceWorker]
interface ExtendableCookieChangeEvent : ExtendableEvent {
  constructor(DOMString type, optional ExtendableCookieChangeEventInit eventInitDict = {});
  [SameObject] readonly attribute FrozenArray<CookieListItem> changed;
  [SameObject] readonly attribute FrozenArray<CookieListItem> deleted;
};

dictionary ExtendableCookieChangeEventInit : ExtendableEventInit {
  CookieList changed;
  CookieList deleted;
};

[SecureContext]
partial interface Window {
  [SameObject] readonly attribute CookieStore cookieStore;
};

partial interface ServiceWorkerGlobalScope {
  [SameObject] readonly attribute CookieStore cookieStore;

  attribute EventHandler oncookiechange;
};