Cookie Store API

社区组报告草案

当前版本:
https://wicg.github.io/cookie-store/
测试套件:
https://github.com/web-platform-tests/wpt/tree/master/cookie-store
问题跟踪:
GitHub
规范内联问题
编辑:
(Google 公司)
前编辑:
(Google 公司)
(Google 公司)
(Google 公司)
(Google 公司)

摘要

一个用于文档和服务工作者的异步 JavaScript Cookie API。

本文档的状态

本规范由Web 平台孵化社区组发布。 它不是 W3C 标准,也不在 W3C 标准轨道上。 请注意,根据W3C 社区贡献者许可协议 (CLA),存在有限的选择退出权利,并适用其他条件。 了解更多关于W3C 社区和业务组的信息。

1. 介绍

本节为非规范性内容。

这是一个提议,旨在为运行在 HTML 文档和服务工作者中的脚本引入异步的 Cookie API。

HTTP Cookies 自其在 Netscape 起源以来(archive.org 保存的文档),为 Web 提供了有价值的状态管理机制

同步的、单线程的脚本级 document.cookie 接口一直是复杂性和性能问题的来源,尤其是在许多浏览器从以下模型转变时:

……到现代 Web,追求平滑响应的高性能:

在现代 Web 中,Web 应用程序某一部分的 Cookie 操作不能阻塞:

构建在服务工作者中的较新 Web 部分也需要访问 Cookies,但无法使用同步的、阻塞的 document.cookie 接口,因为它们既没有 document,也不能阻塞事件循环,否则会干扰无关事件的处理。

1.1. 提议变更概览

尽管完全重新思考 Cookies的想法很有吸引力,但当今的网站仍然严重依赖它们,而用于操作 Cookies 的脚本 API 在其最初几十年的使用中几乎没有变化。

如今,写入一个 Cookie 意味着在等待浏览器以 Set-Cookie 格式同步更新 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('It worked!');
} else {
    console.error('It did not work, and we do not know why');
}

如果你可以这样写会怎么样:

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('It worked!');
    }, function(reason) {
    console.error(
        'It did not work, and this is why:', 
        reason);
    });
// 同时我们可以在等待 Cookie 存储处理写入时做其他事情...

这还具有不依赖 document 且不阻塞的优势,这使得它可以从服务工作者中使用,而服务工作者脚本中通常无法访问 Cookies。

该提议还包括一个节能的监控 API,用于用 Cookie 更改观察器替代基于 setTimeout 的轮询 Cookie 监控。

1.2. 摘要

本提案概述了一个使用 Promises/async 函数的异步 API,用于以下 Cookie 操作:

1.3. 查询 Cookies

文档服务工作者通过全局对象上的 cookieStore 属性访问相同的查询 API。

get()getAll() 方法用于查询 Cookies。 两个方法都返回 Promise。 两个方法接受相同的参数,可以是:

get() 方法本质上是 getAll() 的一种形式,只返回第一个结果。

读取一个 Cookie:
try {
    const cookie = await cookieStore.get('session_id');
    if (cookie) {
    console.log(`找到 ${cookie.name} Cookie: ${cookie.value}`);
    } else {
    console.log('未找到 Cookie');
    }
} catch (e) {
    console.error(`Cookie 存储错误: ${e}`);
}
读取多个 Cookies:
try {
    const cookies = await cookieStore.getAll('session_id'});
    for (const cookie of cookies)
    console.log(`结果: ${cookie.name} = ${cookie.value}`);
} catch (e) {
    console.error(`Cookie 存储错误: ${e}`);
}

服务工作者可以获取将在其范围内的任何 URL 上通过fetch发送的 Cookies 列表。

读取特定 URL 的 Cookies(在服务工作者中):
await cookieStore.getAll({url: '/admin'});

文档只能获取其当前 URL 的 Cookies。换句话说,在文档上下文中,url 的唯一有效值是文档的 URL。

get()getAll() 返回的对象包含 Cookie 存储中的所有相关信息,而不仅仅是旧 document.cookie API 中的名称

访问所有 Cookie 数据:
await cookie = cookieStore.get('session_id');
console.log(`Cookie 范围 - 域: ${cookie.domain} 路径: ${cookie.path}`);
if (cookie.expires === null) {
    console.log('Cookie 在会话结束时过期');
} else {
    console.log(`Cookie 过期时间: ${cookie.expires}`);
}
if (cookie.secure)
    console.log('该 Cookie 仅限于安全来源');

1.4. 修改 Cookies

文档服务工作者通过全局对象上的 cookieStore 属性访问相同的修改 API。

使用 set() 方法创建或修改(写入)Cookies。

写入一个 Cookie:
try {
    await cookieStore.set('opted_out', '1');
} catch (e) {
    console.error(`设置 Cookie 失败: ${e}`);
}

上面的 set() 调用是使用选项字典的简写形式,如下所示:

await cookieStore.set({
    name: 'opted_out',
    value: '1',
    expires: null,  // 会话 Cookie

    // 默认情况下,domain 设置为 null,这意味着范围被锁定在当前域。 
    domain: null,
    path: '/' 
});

使用 delete() 方法删除(过期)Cookies。

删除一个 Cookie:
try {
    await cookieStore.delete('session_id');
} catch (e) {
    console.error(`删除 Cookie 失败: ${e}`);
}

实际上,删除一个 Cookie 是通过将其过期日期更改为过去的时间来完成的,这种方式仍然有效。

通过更改过期日期删除一个 Cookie:
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(`删除 Cookie 失败: ${e}`);
}

1.5. 监控 Cookies

为了避免轮询,可以观察 Cookies 的变化。

文档中,会为所有相关的 Cookie 更改触发 change 事件。

在文档中注册 change 事件:
cookieStore.addEventListener('change', event => {
    console.log(`${event.changed.length} 个 Cookie 已更改`);
    for (const cookie in event.changed)
    console.log(`Cookie ${cookie.name} 更改为 ${cookie.value}`);

    console.log(`${event.deleted.length} 个 Cookie 已删除`);
    for (const cookie in event.deleted)
    console.log(`Cookie ${cookie.name} 已删除`);
}); 

服务工作者中,会针对全局范围触发 cookiechange 事件,但需要显式订阅,并与服务工作者的注册相关联。

在服务工作者中注册 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 的 Cookie 的更改事件。 
        } 
    ]); 
    }); 
}); 

self.addEventListener('cookiechange', event => {
    // 该事件具有 |changed| 和 |deleted| 属性,与文档事件具有相同的语义。 
    console.log(`${event.changed.length} 个 Cookie 已更改`); 
    console.log(`${event.deleted.length} 个 Cookie 已删除`); 
}); 

subscribe() 的调用是累积的,因此独立维护的模块或库可以设置自己的订阅。如预期的那样,服务工作者的订阅会与服务工作者注册一起持久化。

订阅可以使用与 get()getAll() 相同的选项。细粒度订阅的复杂性是合理的,因为向服务工作者分派无关的 Cookie 更改事件的成本远高于向 Window 分派等效事件的成本。 特别是,向服务工作者分派事件可能需要唤醒工作者,这对电池寿命有显著影响。

getSubscriptions() 允许服务工作者检查已创建的订阅。

检查更改订阅:
const subscriptions = await self.registration.cookies.getSubscriptions(); 
for (const sub of subscriptions) { 
    console.log(sub.name, sub.url); 
} 

2. 概念

Cookie 在规范中由用户代理的 Cookies § 用户代理要求 定义。

根据 Cookies § 存储模型Cookie 包含以下字段: 名称过期时间路径创建时间最后访问时间持久标志仅主机标志仅安全标志HttpOnly 标志SameSite 标志

当 Cookie 在范围内且没有 HttpOnly 标志时,它是 脚本可见 的。这在处理模型中更正式地执行,该模型在适当的点参考 Cookies § 检索模型

Cookie 还受某些大小限制。根据 Cookies § 存储模型

注意: Cookie 属性值存储为 字节序列,而不是字符串。

Cookie 存储 在规范中由用户代理的 Cookies § 用户代理要求 定义。

当以下任一条件发生在 Cookie 存储时,执行 处理 Cookie 更改 的步骤。

2.3. 服务工作者的扩展

[Service-Workers] 定义了 服务工作者注册,本规范对此进行了扩展。

一个 服务工作者注册 具有一个关联的 Cookie 更改订阅列表,它是一个 列表; 每个成员是一个 Cookie 更改订阅。一个 Cookie 更改订阅 一个 元组,包含 名称URL

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;
  USVString? domain;
  USVString path;
  DOMHighResTimeStamp? expires;
  boolean secure;
  CookieSameSite sameSite;
  boolean partitioned;
};

typedef sequence<CookieListItem> CookieList;

3.1. get() 方法

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

返回一个 Promise,解析为给定 Cookie 名称(或其他选项)的第一个在范围内的 脚本可见值。 在服务工作者上下文中,默认路径为服务工作者注册范围的路径。 在文档中,默认路径为当前文档的路径,不受 replaceState()document.domain 的更改影响。

get(name) 方法的步骤如下:
  1. settingsthis相关设置对象

  2. originsettings来源

  3. 如果 origin不透明来源,则返回 一个被拒绝的 Promise,原因是 "SecurityError" DOMException

  4. urlsettings创建 URL

  5. p一个新的 Promise

  6. 并行运行以下步骤:

    1. list 为运行 查询 Cookies 的结果,参数为 urlname

    2. 如果 list 失败,则 拒绝 p,原因是 TypeError,并中止这些步骤。

    3. 如果 list 为空,则 解析 p 为 null。

    4. 否则,解析 plist 的第一个项目。

  7. 返回 p

get(options) 方法的步骤如下:
  1. settingsthis相关设置对象

  2. originsettings来源

  3. 如果 origin不透明来源,则返回 一个被拒绝的 Promise,原因是 "SecurityError" DOMException

  4. urlsettings创建 URL

  5. 如果 options 为空,则返回 一个被拒绝的 Promise,原因是 TypeError

  6. 如果 options["url"] 存在,则运行以下步骤:

    1. parsed 为使用 settingsAPI 基础 URL 解析 options["url"] 的结果。

    2. 如果 this关联全局对象(relevant global object)Window 对象,且 parsedurl忽略片段(exclude fragments)设为 true 的情况下不相等,则返回 一个以 TypeError 拒绝的 promise。

    3. 如果 parsed来源url来源相同, 则返回 一个被拒绝的 Promise,原因是 TypeError

    4. url 设置为 parsed

  7. p一个新的 Promise

  8. 并行运行以下步骤:

    1. list 为运行 查询 Cookies 的结果,参数为 urloptions["name"] (如果存在)。

    2. 如果 list 失败,则 拒绝 p,原因是 TypeError,并中止这些步骤。

    3. 如果 list 为空,则 解析 p 为 null。

    4. 否则,解析 plist 的第一个项目。

  9. 返回 p

3.2. getAll() 方法

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

返回一个 Promise,解析为给定 Cookie 名称(或其他选项)的所有在范围内的 脚本可见值。 在服务工作者上下文中,默认路径为服务工作者注册范围的路径。 在文档中,默认路径为当前文档的路径,不受 replaceState()document.domain 的更改影响。

getAll(name) 方法的步骤如下:
  1. settingsthis相关设置对象

  2. originsettings来源

  3. 如果 origin不透明来源,则返回 一个被拒绝的 Promise,原因是 "SecurityError" DOMException

  4. urlsettings创建 URL

  5. p一个新的 Promise

  6. 并行运行以下步骤:

    1. list 为运行 查询 Cookies 的结果,参数为 urlname

    2. 如果 list 失败,则 拒绝 p,原因是 TypeError

    3. 否则,解析 plist

  7. 返回 p

getAll(options) 方法的步骤如下:
  1. settingsthis相关设置对象

  2. originsettings来源

  3. 如果 origin不透明来源,则返回 一个被拒绝的 Promise,原因是 "SecurityError" DOMException

  4. urlsettings创建 URL

  5. 如果 options["url"] 存在,则运行以下步骤:

    1. parsed 为使用 settingsAPI 基础 URL 解析 options["url"] 的结果。

    2. 如果 this关联全局对象(relevant global object)Window 对象,且 parsedurl排除片段(exclude fragments)设为 true 的情况下不相等,则返回 一个以 TypeError 拒绝的 promise。

    3. 如果 parsed来源url来源相同, 则返回 一个被拒绝的 Promise,原因是 TypeError

    4. url 设置为 parsed

  6. p一个新的 Promise

  7. 并行运行以下步骤:

    1. list 为运行 查询 Cookies 的结果,参数为 urloptions["name"] (如果存在)。

    2. 如果 list 失败,则 拒绝 p,原因是 TypeError

    3. 否则,解析 plist

  8. 返回 p

3.3. set() 方法

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

写入(创建或修改)一个 Cookie。

选项的默认值为:

  • 路径:/

  • 域:与当前文档或服务工作者的位置相同

  • 无过期日期

  • SameSite:严格模式

set(name, value) 方法的步骤如下:
  1. settingsthis相关设置对象

  2. originsettings来源

  3. 如果 origin不透明来源,则返回 一个被拒绝的 Promise,原因是 "SecurityError" DOMException

  4. urlsettings创建 URL

  5. domain 为 null。

  6. path 为 "/".

  7. sameSitestrict

  8. partitioned 为 false。

  9. p一个新的 Promise

  10. 并行运行以下步骤:

    1. r 为运行 设置 Cookie 的结果,参数为 urlnamevaluedomainpathsameSitepartitioned

    2. 如果 r 失败,则 拒绝 p,原因是 TypeError 并中止这些步骤。

    3. 解析 p 为 undefined。

  11. 返回 p

set(options) 方法的步骤如下:
  1. settingsthis相关设置对象

  2. originsettings来源

  3. 如果 origin不透明来源,则返回 一个被拒绝的 Promise,原因是 "SecurityError" DOMException

  4. urlsettings创建 URL

  5. p一个新的 Promise

  6. 并行运行以下步骤:

    1. r 为运行 设置 Cookie 的结果,参数为 urloptions["name"]、 options["value"]、 options["expires"]、 options["domain"]、 options["path"]、 options["sameSite"]、 和 options["partitioned"]。

    2. 如果 r 失败,则 拒绝 p,原因是 TypeError 并中止这些步骤。

    3. 解析 p 为 undefined。

  7. 返回 p

3.4. delete() 方法

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

删除(过期)具有给定名称或名称及可选域和路径的 Cookie。

delete(name) 方法的步骤如下:
  1. settingsthis相关设置对象

  2. originsettings来源

  3. 如果 origin不透明来源,则返回 一个被拒绝的 Promise,原因是 "SecurityError" DOMException

  4. urlsettings创建 URL

  5. p一个新的 Promise

  6. 并行运行以下步骤:

    1. r 为运行 删除 Cookie 的结果,参数为 urlname、 null、 "/"、 true 和 "strict"。

    2. 如果 r 失败,则 拒绝 p,原因是 TypeError 并中止这些步骤。

    3. 解析 p 为 undefined。

  7. 返回 p

delete(options) 方法的步骤如下:
  1. settingsthis相关设置对象

  2. originsettings来源

  3. 如果 origin不透明来源,则返回 一个被拒绝的 Promise,原因是 "SecurityError" DOMException

  4. urlsettings创建 URL

  5. p一个新的 Promise

  6. 并行运行以下步骤:

    1. r 为运行 删除 Cookie 的结果,参数为 urloptions["name"]、 options["domain"]、 options["path"]、 和 options["partitioned"]。

    2. 如果 r 失败,则 拒绝 p,原因是 TypeError 并中止这些步骤。

    3. 解析 p 为 undefined。

  7. 返回 p

4. CookieStoreManager 接口

CookieStoreManager 具有一个关联的 注册,它是一个 服务工作者注册

CookieStoreManager 接口允许 服务工作者 订阅 Cookie 更改事件。使用 subscribe() 方法是必要的,以表明特定的 服务工作者注册 对更改事件感兴趣。

[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)

订阅 Cookie 的更改。订阅可以使用与 get()getAll() 相同的选项,并具有可选的 nameurl 属性。

一旦订阅,通知将作为 "cookiechange" 事件发送到 服务工作者 的全局范围:

subscribe(subscriptions) 方法的步骤如下:
  1. settingsthis相关设置对象

  2. registrationthis注册

  3. p一个新的 Promise

  4. 并行运行以下步骤:

    1. subscription listregistration 的关联 Cookie 更改订阅列表

    2. 对于 subscriptions 中的每个 entry,运行以下步骤:

      1. nameentry["name"]。

      2. url 为使用 settingsAPI 基础 URL 解析 entry["url"] 的结果。

      3. 如果 url 不以 registration范围 URL 开头, 则 拒绝 p,原因是 TypeError 并中止这些步骤。

      4. subscriptionCookie 更改订阅 (name, url)。

      5. 如果 subscription list 尚未 包含 subscription,则 追加 subscriptionsubscription list

    3. 解析 p 为 undefined。

  5. 返回 p

4.2. getSubscriptions() 方法

subscriptions = await registration . cookies . getSubscriptions()

此方法返回一个 Promise,该 Promise 解析为为此服务工作者注册所创建的 Cookie 更改订阅列表。

getSubscriptions() 方法的步骤如下:
  1. registrationthis注册

  2. p一个新的 Promise

  3. 并行运行以下步骤:

    1. subscriptionsregistration 的关联 Cookie 更改订阅列表

    2. result 为一个新的 列表

    3. 对于 subscriptions 中的每个 subscription,运行以下步骤:

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

    4. 解析 presult

  4. 返回 p

4.3. unsubscribe() 方法

await registration . cookies . unsubscribe(subscriptions)

调用此方法将停止已注册的服务工作者接收先前订阅的事件。subscriptions 参数应以与传递给 subscribe() 或从 getSubscriptions() 返回的形式相同的方式列出订阅。

unsubscribe(subscriptions) 方法的步骤如下:
  1. settingsthis相关设置对象

  2. registrationthis注册

  3. p一个新的 Promise

  4. 并行运行以下步骤:

    1. subscription listregistration 的关联 Cookie 更改订阅列表

    2. 对于 subscriptions 中的每个 entry,运行以下步骤:

      1. nameentry["name"]。

      2. url 为使用 settingsAPI 基础 URL 解析 entry["url"] 的结果。

      3. 如果 url 不以 registration范围 URL 开头, 则 拒绝 p,原因是 TypeError 并中止这些步骤。

      4. subscriptionCookie 更改订阅 (name, url)。

      5. 移除 subscription list 中等于 subscription 的任何

    3. 解析 p 为 undefined。

  5. 返回 p

4.4. ServiceWorkerRegistration 接口

ServiceWorkerRegistration 接口被扩展以通过 CookieStoreManager 提供对 cookies 的访问,该接口提供订阅 Cookie 更改的功能。

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

每个 ServiceWorkerRegistration 都有一个关联的 CookieStoreManager 对象。 CookieStoreManager注册等于 ServiceWorkerRegistration服务工作者注册

cookies getter 的步骤是返回 this 的关联 CookieStoreManager 对象。

从服务工作者脚本订阅 Cookie 更改:
self.registration.cookies.subscribe([{name:'session-id'}]);
从窗口上下文中的脚本订阅 Cookie 更改:
navigator.serviceWorker.register('sw.js').then(registration => {
    registration.cookies.subscribe([{name:'session-id'}]);
});

5. 事件接口

5.1. CookieChangeEvent 接口

CookieChangeEventCookieStore 对象上分派,位于 Window 上下文中,当发生任何 脚本可见的 Cookie 更改时。

[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 接口

ExtendableCookieChangeEventServiceWorkerGlobalScope 对象上分派,当发生任何与 Cookie 更改订阅列表 匹配的 脚本可见的 Cookie 更改时。

注意: ExtendableEvent 被用作所有服务工作者事件的祖先接口,以便在执行异步操作时保持工作者本身的存活。

[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 可通过全局对象中的属性访问,例如 WindowServiceWorkerGlobalScope

6.1. Window 接口

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

Window 有一个 关联的 CookieStore,它是一个 CookieStore

cookieStore getter 的步骤是返回 this关联的 CookieStore

6.2. ServiceWorkerGlobalScope 接口

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

    attribute EventHandler oncookiechange;
};

ServiceWorkerGlobalScope 有一个 关联的 CookieStore,它是一个 CookieStore

cookieStore getter 的步骤是返回 this关联的 CookieStore

7. 算法

要将日期和时间 dateTime 表示为时间戳, 返回从 1970 年 1 月 1 日 00:00:00 UTC 到 dateTime 的毫秒数(假设每天恰好有 86,400,000 毫秒)。

注意: 这与 时间值[ECMAScript] 中的表示方式相同。

序列化日期 DOMHighResTimeStamp millis, 令 dateTime 为从 1970 年 1 月 1 日 00:00:00 UTC 开始经过 millis 毫秒后的日期和时间 (假设每天恰好有 86,400,000 毫秒), 并返回一个 字节序列,该序列对应于根据 Cookies § Datescookie-date 表示形式的最接近值。

7.1. 查询 Cookies

要使用 url 和可选的 name 查询 Cookies,运行以下步骤:

  1. 执行 Cookies § 检索模型 中定义的步骤,以使用 url 作为 request-uri 计算“从给定的 Cookie 存储中生成的 cookie-string”。 忽略 cookie-string 本身,但在后续步骤中使用中间的 cookie-list

    在这些步骤中,cookie-string 是为“非 HTTP”API 生成的。

  2. list 为一个新的 列表

  3. 对于 cookie-list 中的每个 cookie,运行以下步骤:

    1. 断言:cookiehttp-only-flag 为 false。

    2. 如果提供了 name,则运行以下步骤:

      1. cookieName 为对 cookiename 运行 UTF-8 无 BOM 解码 的结果。

      2. 如果 cookieName 不等于 name,则 继续

    3. item 为对 cookie 运行 创建 CookieListItem 的结果。

    4. 追加 itemlist

  4. 返回 list

要从 cookie 创建一个 CookieListItem,运行以下步骤:

  1. name 为对 cookiename 运行 UTF-8 无 BOM 解码 的结果。

  2. value 为对 cookievalue 运行 UTF-8 无 BOM 解码 的结果。

  3. domain 为对 cookiedomain 运行 UTF-8 无 BOM 解码 的结果。

  4. path 为对 cookiepath 运行 UTF-8 无 BOM 解码 的结果。

  5. expirescookieexpiry-time作为时间戳)。

  6. securecookiesecure-only-flag

  7. 根据 cookiesame-site-flag 进行切换:

    `None`

    sameSite 为 "none"。

    `Strict`

    sameSite 为 "strict"。

    `Lax`

    sameSite 为 "lax"。

  8. partitioned 为一个布尔值,指示用户代理是否支持 Cookie 分区,并且 cookie 是否具有分区键。

  9. 返回 «[ "name" → name, "value" → value, "domain" → domain, "path" → path, "expires" → expires, "secure" → secure, "sameSite" → sameSite, "partitioned" → partitioned

注意: cookiecreation-timelast-access-timepersistent-flaghost-only-flaghttp-only-flag 属性不会暴露给脚本。

要使用 urlnamevalue,可选的 expiresdomainpathsameSitepartitioned 设置 Cookie,运行以下步骤:

  1. 如果 namevalue 包含 U+003B (;),或包含除 U+0009 TAB 外的任意 C0 控制字符,或 U+007F DELETE,则返回失败。

    注意,目前还在讨论这些字符限制是否也应适用于 expiresdomainpathsameSite[httpwg/http-extensions Issue #1593]

  2. 如果 name 包含 U+003D (=),则返回失败。

  3. 如果 name长度为 0:

    1. 如果 value 包含 U+003D (=),则返回失败。

    2. 如果 value长度为 0,则返回失败。

    3. 如果 value 字节小写后, `__host-`、`__hosthttp-`、`__http-` 或 `__secure-` 开头,则返回失败。

  4. 如果 name 字节小写后, `__http-` 或 `__hosthttp-` 开头,则返回失败。

  5. encodedNameUTF-8 编码后的 name

  6. encodedValueUTF-8 编码后的 value

  7. 如果 encodedName字节序列长度加上 encodedValue字节序列长度大于最大 name/value 对长度,则返回失败。

  8. hosturl主机

  9. attributes 为一个新的列表

  10. 如果 domain 不为 null,则执行以下步骤:

    1. 如果 domain 以 U+002E (.) 开头,则返回失败。

    2. 如果 name 字节小写后, `__host-` 开头,则返回失败。

    3. 如果 domain 不是 host 的可注册域后缀且不等于 host,则返回失败。

    4. parsedDomain主机解析 domain 的结果。

    5. 断言:parsedDomain 不为失败。

    6. encodedDomainUTF-8 编码后的 parsedDomain

    7. 如果 encodedDomain字节序列长度大于最大属性值长度,则返回失败。

    8. 追加 `Domain`/encodedDomainattributes

  11. 如果给定了 expires,则追加 `Expires`/expires序列化日期)到 attributes

  12. 如果 path 不为 null:

    1. 如果 path 不以 U+002F (/) 开头,则返回失败。

    2. 如果 path 不是 U+002F (/) 且 name 字节小写后, `__host-` 开头,则返回失败。

    3. encodedPathUTF-8 编码后的 path

    4. 如果 encodedPath字节序列长度大于最大属性值长度,则返回失败。

    5. 追加 `Path`/encodedPathattributes

  13. 否则,追加 `Path`/ U+002F (/) 到 attributes

  14. 追加 `Secure`/`` 到 attributes

  15. 根据 sameSite 的值,执行如下操作:

    "none"

    追加 `SameSite`/`None` 到 attributes

    "strict"

    追加 `SameSite`/`Strict` 到 attributes

    "lax"

    追加 `SameSite`/`Lax` 到 attributes

  16. 如果 partitioned 为 true,追加 `Partitioned`/`` 到 attributes

  17. 按照 Cookies § 存储模型中“用户代理接收到 cookie”时的步骤执行, url 作为 request-uriencodedName 作为 cookie-nameencodedValue 作为 cookie-valueattributes 作为 cookie-attribute-list

    就这些步骤而言,新创建的 cookie 被视为来自“非 HTTP”API。

  18. 返回成功。

    注意:由于 [RFC6265BIS-14] 中的要求,存储 cookie 可能仍然会失败, 但这些步骤将视为已成功。

要使用 urlnamedomainpathpartitioned 删除 Cookie,运行以下步骤:

  1. expires 为最早可表示的日期,表示为 时间戳

    注意: 对于此算法的目的,expires 的确切值并不重要,只要它是过去的时间即可。

  2. value 为空字符串。

  3. 如果 name长度为 0,则将 value 设置为任意非空的实现定义的字符串。

  4. sameSite 为 "strict"。

    注意: 此算法不会持久化 valuesameSite 的值。

  5. 返回运行 设置 Cookie 的结果,使用 urlnamevalueexpiresdomainpathsameSitepartitioned

7.4. 处理更改

处理 Cookie 更改,运行以下步骤:

  1. 对于每个 Window window,运行以下步骤:

    1. urlwindow相关设置对象创建 URL

    2. changesurl可观察更改

    3. 如果 changes 为空,则 继续

    4. 在全局任务队列中排队一个任务,使用 window,以 触发一个更改事件,事件名称为 "change",并将 changes 传递给 windowCookieStore

  2. 对于每个 服务工作者注册 registration,运行以下步骤:

    1. changes 为一个新的 集合

    2. 对于 registration可观察更改 中的每个 change,运行以下步骤:

      1. cookiechange 的 Cookie。

      2. 对于 registrationCookie 更改订阅列表 中的每个 subscription,运行以下步骤:

        1. 如果 change 不在 subscription可观察更改 中,则 继续

        2. cookieName 为对 cookieUTF-8 无 BOM 解码 的结果。

        3. 如果 cookieName 等于 subscription名称,则将 change 添加到 changes 并中断循环。

    3. 如果 changes 为空,则继续。

    4. changedListdeletedList 为运行 准备列表 的结果,使用 changes

    5. 触发一个功能事件,事件名称为 "cookiechange",使用 ExtendableCookieChangeEvent,在 registration 上,具有以下属性:

      changed

      changedList

      deleted

      deletedList

可观察的更改 是针对 url集合,包含对 Cookie更改,这些更改满足 Cookies § 检索算法 第 1 步的要求,用于计算给定 Cookie 存储的 "cookie-string",其中 url 作为 request-uri,适用于 "非 HTTP" API。

Cookie 更改 是一个 Cookie 和一个类型(更改删除):

触发更改事件,事件名称为 type,更改为 changes,目标为 target,运行以下步骤:

  1. event 为使用 CookieChangeEvent 创建的 事件

  2. eventtype 属性设置为 type

  3. eventbubblescancelable 属性设置为 false。

  4. changedListdeletedList 为运行 准备列表 的结果,使用 changes

  5. eventchanged 属性设置为 changedList

  6. eventdeleted 属性设置为 deletedList

  7. 分派 eventtarget

要从 changes准备列表,运行以下步骤:

  1. changedList 为一个新的 列表

  2. deletedList 为一个新的 列表

  3. 对于 changes 中的每个 change,运行以下步骤:

    1. item 为运行 创建 CookieListItem 的结果,使用 change 的 cookie。

    2. 如果 change 的类型是 changed,则 追加 itemchangedList

    3. 否则,运行以下步骤:

      1. item["value"] 设置为 undefined。

      2. 追加 itemdeletedList

  4. 返回 changedListdeletedList

8. 安全注意事项

除了服务工作者上下文中的 Cookie 访问外,此 API 并不打算向 Web 暴露任何新功能。

8.1. 注意事项

尽管浏览器的 Cookie 实现正在朝着更好的安全性和更少的意外和易出错的默认值方向发展,但目前对 Cookie 数据安全性几乎没有保证。

因此,最好在解释任何 Cookie 的值时保持谨慎,且永远不要将 Cookie 的值作为脚本、HTML、CSS、XML、PDF 或任何其他可执行格式执行。

8.2. 限制?

此 API 可能会意外地使 Cookie 更易于使用,从而鼓励其进一步使用。如果它导致在 非安全上下文 中的进一步使用,这可能会导致对用户而言更不安全的 Web。因此,此 API 已被限制为仅在 安全上下文 中使用。

8.3. 安全 Cookie

本节为非规范性内容。

此 API 仅允许写入 Secure Cookie,以鼓励围绕安全性做出更好的决策。然而,该 API 仍允许读取非 Secure Cookie,以便于迁移到 Secure Cookie。作为副作用,当使用此 API 获取和修改非 Secure Cookie 时,该非 Secure Cookie 将自动修改为 Secure

8.4. 意外行为

某些现有的 Cookie 行为(尤其是基于域而非来源的取向、非安全上下文 能够设置在 安全上下文 中可读的 Cookie,以及脚本能够设置在脚本上下文中不可读的 Cookie)从 Web 安全的角度来看可能非常令人意外。

其他意外行为记录在 Cookies § Introduction 中,例如,可以为超级域设置 Cookie(例如,app.example.com 可以为整个 example.com 域设置 Cookie),并且 Cookie 可以跨给定域名的所有端口号读取。

进一步复杂化的是主要浏览器在 Cookie 处理上的历史差异,尽管其中一些(例如端口号处理)现在比以前更一致。

8.5. 前缀

在可行的情况下,示例使用了 __Host-__Secure- 名称前缀,这会导致某些当前浏览器禁止从 非安全上下文 覆盖,禁止没有 Secure 标志的覆盖,并且在 __Host- 的情况下,禁止具有显式 Domain 或非 '/' Path 属性的覆盖(有效地强制执行同源语义)。这些前缀在实现安全 Cookie 的浏览器中提供了重要的安全优势,并在其他浏览器中优雅降级(即,特殊语义可能不会在其他 Cookie API 中强制执行,但 Cookie 正常工作,异步 Cookie API 在写操作中强制执行安全语义)。不过,此 API 的一个主要目标是与现有 Cookie 的互操作性,因此也提供了一些未使用这些前缀的 Cookie 名称示例。

前缀规则也由此 API 在写操作中强制执行,但可能不会在同一浏览器的其他 API 中强制执行。因此,在这些规则被更广泛采用之前,不建议过于依赖它们的强制执行。

8.6. URL 范围

尽管服务工作者脚本今天无法直接访问 Cookie,但它已经可以使用受控渲染的范围内 HTML 和脚本资源,在服务工作者脚本的远程控制下注入 Cookie 监控代码。这意味着在服务工作者范围内访问 Cookie 在技术上已经可能,只是并不十分方便。

当服务工作者的范围比 / 更窄时,它仍然可以通过成功猜测/构造允许 IFRAME 的 404 页面 URL 并在其中运行脚本来读取其范围路径空间之外的路径范围 Cookie。同样的技术可以扩展到整个来源,但精心构建的网站(一个没有超出范围页面可被 IFRAME 的网站)实际上可以今天拒绝路径范围服务工作者的这种能力,而我不愿意在没有进一步讨论其影响的情况下移除这种限制。

8.7. Cookie 厌恶

为了减少开发人员的复杂性并消除对临时测试 Cookie 的需求,此异步 Cookie API 将明确拒绝在操作将被忽略时写入或删除 Cookie 的尝试。同样,它将明确拒绝在操作将忽略实际 Cookie 数据并模拟空 Cookie Jar 时读取 Cookie 的尝试。在这些上下文中尝试观察 Cookie 更改仍然“有效”,但不会调用回调,直到读取访问被允许(例如,由于站点权限更改)。

今天,在脚本启动的 Cookie 写入被禁止的上下文中写入 document.cookie 通常是无操作的。然而,许多 Cookie 写入脚本和框架总是写入一个测试 Cookie,然后检查其存在性以确定是否可以进行脚本启动的 Cookie 写入。

同样,今天在脚本启动的 Cookie 读取被禁止的上下文中读取 document.cookie 通常返回一个空字符串。然而,一个合作的 Web 服务器可以验证服务器启动的 Cookie 写入和读取是否有效,并将此报告给脚本(脚本仍然看到空字符串),脚本可以使用此信息推断脚本启动的 Cookie 读取被禁止。

9. 隐私注意事项

9.1. 清除 Cookie

本节为非规范性内容。

当用户清除某个来源的 Cookie 时,用户代理需要清除该来源的所有存储,包括该来源的服务工作者和 DOM 可访问的存储。这是为了防止网站在用户执行此操作后,通过持久存储恢复任何用户标识符。

10. 致谢

感谢 Benjamin Sittler,他为此 API 创建了初始提案。

特别感谢以下人员帮助完善此提案:

Adam Barth, Alex Russell, Andrea Marchesini, Andrew Williams, Anne van Kesteren, Ben Kelly, Craig Francis, Daniel Appelquist, Daniel Murphy, Domenic Denicola, Elliott Sprehn, Fagner Brack, Jake Archibald, Joel Weinberger, Kenneth Rohde Christiansen, Lukasz Olejnik, Marijn Kruisselbrink, 和 Mike West。

特别感谢 Tab Atkins, Jr. 创建并维护了 Bikeshed,这是用于创建本文档的规范编写工具,并感谢他提供的通用编写建议。

一致性

文档约定

一致性要求通过描述性断言和 RFC 2119 术语的组合来表达。 规范性部分中的关键字 “MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY” 和 “OPTIONAL” 应按照 RFC 2119 中的描述进行解释。 然而,为了可读性,这些词在本规范中并未全部以大写字母出现。

本规范的所有文本均为规范性内容,除非明确标记为非规范性内容的章节、示例和注释。[RFC2119]

本规范中的示例以“例如”一词引入,或者通过 class="example" 与规范性文本区分开来,例如:

这是一个信息性示例。

信息性注释以“注意”一词开头,并通过 class="note" 与规范性文本区分开来,例如:

注意,这是一个信息性注释。

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[ENCODING]
Anne van Kesteren. Encoding Standard. Living Standard. URL: https://encoding.spec.whatwg.org/
[FETCH]
Anne van Kesteren. Fetch Standard. Living 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. 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
[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. Living Standard. URL: https://url.spec.whatwg.org/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

Informative References

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

IDL Index

[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;
  USVString? domain;
  USVString path;
  DOMHighResTimeStamp? expires;
  boolean secure;
  CookieSameSite sameSite;
  boolean partitioned;
};

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;
};

Issues Index

Note that it’s up for discussion whether these character restrictions should also apply to expires, domain, path, and sameSite as well. [httpwg/http-extensions Issue #1593]
MDN

CookieChangeEvent/CookieChangeEvent

In only one current engine.

FirefoxNoneSafariNoneChrome87+
Opera?Edge87+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

CookieChangeEvent/changed

In only one current engine.

FirefoxNoneSafariNoneChrome87+
Opera?Edge87+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

CookieChangeEvent/deleted

In only one current engine.

FirefoxNoneSafariNoneChrome87+
Opera?Edge87+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

CookieChangeEvent

In only one current engine.

FirefoxNoneSafariNoneChrome87+
Opera?Edge87+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

CookieStore/change_event

In only one current engine.

FirefoxNoneSafariNoneChrome87+
Opera?Edge87+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

CookieStore/change_event

In only one current engine.

FirefoxNoneSafariNoneChrome87+
Opera?Edge87+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

CookieStore/delete

In only one current engine.

FirefoxNoneSafariNoneChrome87+
Opera?Edge87+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

CookieStore/get

In only one current engine.

FirefoxNoneSafariNoneChrome87+
Opera?Edge87+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

CookieStore/getAll

In only one current engine.

FirefoxNoneSafariNoneChrome87+
Opera?Edge87+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

CookieStore/set

In only one current engine.

FirefoxNoneSafariNoneChrome87+
Opera?Edge87+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

CookieStore

In only one current engine.

FirefoxNoneSafariNoneChrome87+
Opera?Edge87+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

CookieStoreManager/getSubscriptions

In only one current engine.

FirefoxNoneSafariNoneChrome87+
Opera?Edge87+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

CookieStoreManager/subscribe

In only one current engine.

FirefoxNoneSafariNoneChrome87+
Opera?Edge87+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

CookieStoreManager/unsubscribe

In only one current engine.

FirefoxNoneSafariNoneChrome87+
Opera?Edge87+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

CookieStoreManager

In only one current engine.

FirefoxNoneSafariNoneChrome87+
Opera?Edge87+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

ExtendableCookieChangeEvent/ExtendableCookieChangeEvent

In only one current engine.

FirefoxNoneSafariNoneChrome87+
Opera?Edge87+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

ExtendableCookieChangeEvent/changed

In only one current engine.

FirefoxNoneSafariNoneChrome87+
Opera?Edge87+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

ExtendableCookieChangeEvent/deleted

In only one current engine.

FirefoxNoneSafariNoneChrome87+
Opera?Edge87+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

ExtendableCookieChangeEvent

In only one current engine.

FirefoxNoneSafariNoneChrome87+
Opera?Edge87+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?