1. 介绍
本节是非规范性的。
本标准为运行在HTML文档和服务工作线程中的脚本定义了一个异步的Cookie API。
HTTP Cookie自从其在Netscape的起源以来(由archive.org保存的文档),一直为网络提供了有价值的状态管理机制。
同步的单线程脚本级别document.cookie
接口是复杂性和性能问题的来源,这些问题因许多浏览器从以下模型转变而愈加严重:
-
单一浏览器进程,
-
单线程事件循环模型,以及
-
处理Cookie操作时对脚本事件处理没有响应性的一般期望
……到追求平滑响应和高性能的现代网络:
-
在多个浏览器进程中,
-
带有多线程、多事件循环模型,以及
-
在人类反射时间尺度上响应的期望。
在现代网络中,网络应用程序某部分的Cookie操作不能阻塞:
-
网络应用程序的其他部分,
-
同一网络来源的其他部分,或
-
整个浏览器。
在服务工作线程中构建的网络的新部分也需要访问Cookie,但由于没有文档且无法阻塞事件循环(这会干扰无关事件的处理),因此无法使用同步的阻塞document.cookie
接口。
1.1. 作为document.cookie
的替代方案
如今写入一个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( '成功了!' ); } 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); }); // 同时我们可以在等待Cookie存储处理写入时做其他事情...
这还具有不依赖文档且不阻塞的优点,这使得它可以在服务工作线程中使用,后者从脚本中无法访问Cookie。
本标准还包含一个高效的监控API,用于替代基于setTimeout
的轮询Cookie监视器,通过Cookie更改观察器监控Cookie更改。
1.2. 概要
简而言之,该API提供以下功能:
-
写入(或“设置”)和删除(或“过期”)Cookies
-
-
... 包括指定范围请求路径下的 服务工作线程上下文中的Cookies
-
-
监控 脚本可见的Cookies变化,使用
CookieChangeEvent
-
... 在长时间运行的脚本上下文中(例如
document
) -
... 针对脚本提供的指定范围请求路径的 服务工作线程上下文
-
1.3. 查询Cookies
文档和服务工作线程通过
cookieStore
属性访问相同的查询API,该属性位于全局对象上。
get()
和getAll()
方法位于CookieStore
上,用于查询Cookies。
两种方法均返回Promise
。
两种方法接受相同的参数,可以是:
-
一个名称,或
-
一个选项字典(对于
getAll()
是可选的)
get()
方法本质上是getAll()
的一种形式,只返回第一个结果。
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} ` ); }
try { const cookies= await cookieStore. getAll( 'session_id' }); for ( const cookieof cookies) console. log( `结果: ${ cookie. name} = ${ cookie. value} ` ); } catch ( e) { console. error( `Cookie存储错误: ${ e} ` ); }
服务工作线程可以获取将在fetch请求发送到 其范围内的任何URL时发送的Cookies列表。
文档只能获取其当前URL下的Cookies。换句话说,
文档上下文中唯一有效的url
值是该文档的URL。
get()
和getAll()
方法返回的对象包含Cookie存储中的所有相关信息,而不仅仅是旧版document.cookie
API中的名称和值。
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,该属性位于全局对象上。
Cookie的创建或修改(写入)通过set()
方法完成。
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: '/' });
Cookie的删除(过期)通过delete()
方法完成。
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
为避免轮询,可以直接观察Cookie的变化。
在文档中,所有相关Cookie变更都会触发change
事件。
change
事件:
cookieStore. addEventListener( 'change' , event=> { console. log( ` ${ event. changed. length} 个Cookie已更改` ); for ( const cookiein event. changed) console. log( `Cookie ${ cookie. name} 变更为 ${ cookie. value} ` ); console. log( ` ${ event. deleted. length} 个Cookie已删除` ); for ( const cookiein 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| 属性,与Document事件语义一致。 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 subof subscriptions) { console. log( sub. name, sub. url); }
2. 概念
2.1. Cookie
cookie 的规范性定义见 Cookies § User Agent Requirements。
对给定的string input,规范化 cookie 名称或值: 移除所有位于input开头或结尾的 U+0009 TAB 和 U+0020 SPACE。
当一个cookie处于作用域内,且其 http-only-flag 未设定时,该cookie为 脚本可见。这一点在处理模型中得到更正式的约束,会在适当的时候参考 Cookies § Retrieval Model。
Cookie 还受限于一些大小限制。根据 Cookies § Storage Model:
2.2. Cookie 存储区
cookie store(Cookie存储区) 的规范性定义见 Cookies § User Agent Requirements。
当 cookie store 发生下列任一情况时,执行 处理Cookie变更 的步骤。
-
新创建的 cookie 被插入到 cookie store 中。
-
User Agent 会从 cookie store 中清除过期的 cookie。
-
User Agent 会从 cookie store 中移除多余的 cookie。
2.3. Service Worker 扩展
[Service-Workers] 定义了 service worker registration(服务工作线程注册),本规范对此进行了扩展。
服务工作线程注册 关联了一个 Cookie 变更订阅列表,它是一个 列表; 每个成员是一个 Cookie 变更订阅项。一个 Cookie 变更订阅项 是 一个包含 二元组 的结构: name(名称) 和 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 = "strict";
sameSite 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) - cookie = await cookieStore .
-
返回一个Promise,解析为指定cookie名称(或其他选项)的第一个在作用域内的脚本可见值。 在服务工作线程上下文中,默认路径为服务工作线程注册的scope路径。 在文档中,默认路径为当前文档的路径,不受
replaceState()
或document.domain
的更改影响。
get(name)
方法步骤如下:
-
令 origin 为 settings 的 来源。
-
如果 origin 是 不透明来源,则返回 一个被拒绝的Promise,错误为 "
SecurityError
"DOMException
。 -
令 url 为 settings 的 创建URL。
-
令 p 为 一个新的Promise。
-
并行执行以下步骤:
-
返回 p。
get(options)
方法步骤如下:
-
令 origin 为 settings 的 来源。
-
如果 origin 是 不透明来源,则返回 一个被拒绝的Promise,错误为 "
SecurityError
"DOMException
。 -
令 url 为 settings 的 创建URL。
-
如果 options 为空,则返回 一个被拒绝的Promise,错误为
TypeError
。 -
如果 options["
url
"] 存在,则执行以下步骤: -
令 p 为 一个新的Promise。
-
并行执行以下步骤:
-
返回 p。
3.2. getAll()
方法
- cookies = await cookieStore .
getAll
(name)- cookies = await cookieStore .
getAll
(options) - cookies = await cookieStore .
-
返回一个Promise,解析为指定cookie名称(或其他选项)的所有在作用域内的脚本可见值。 在服务工作线程上下文中,默认路径为服务工作线程注册的scope路径。 在文档中,默认路径为当前文档的路径,不受
replaceState()
或document.domain
的更改影响。
getAll(name)
方法步骤如下:
-
令 origin 为 settings 的 来源。
-
如果 origin 是 不透明来源,则返回 一个被拒绝的Promise,错误为 "
SecurityError
"DOMException
。 -
令 url 为 settings 的 创建URL。
-
令 p 为 一个新的Promise。
-
并行执行以下步骤:
-
返回 p。
getAll(options)
方法步骤如下:
-
令 origin 为 settings 的 来源。
-
如果 origin 是 不透明来源,则返回 一个被拒绝的Promise,错误为 "
SecurityError
"DOMException
。 -
令 url 为 settings 的 创建URL。
-
如果 options["
url
"] 存在,则执行以下步骤: -
令 p 为 一个新的Promise。
-
并行执行以下步骤:
-
返回 p。
3.3. set()
方法
- await cookieStore .
set
(name, value)- await cookieStore .
set
(options) - await cookieStore .
-
写入(创建或修改)一个cookie。
选项默认值为:
-
路径:
/
-
域名:与当前文档或服务工作线程的位置域名一致
-
无过期时间
-
SameSite:strict
-
set(name, value)
方法步骤如下:
-
令 origin 为 settings 的 来源。
-
如果 origin 是 不透明来源,则返回 一个被拒绝的Promise,错误为 "
SecurityError
"DOMException
。 -
令 url 为 settings 的 创建URL。
-
令 domain 为 null。
-
令 path 为 "/"。
-
令 sameSite 为
strict
。 -
令 partitioned 为 false。
-
令 p 为 一个新的Promise。
-
并行执行以下步骤:
-
返回 p。
set(options)
方法步骤如下:
-
令 origin 为 settings 的 来源。
-
如果 origin 是 不透明来源,则返回 一个被拒绝的Promise,错误为 "
SecurityError
"DOMException
。 -
令 url 为 settings 的 创建URL。
-
令 p 为 一个新的Promise。
-
并行执行以下步骤:
-
返回 p。
3.4. delete()
方法
- await cookieStore .
delete
(name)- await cookieStore .
delete
(options) - await cookieStore .
-
删除(使过期)指定名称或名称与可选域名和路径的cookie。
delete(name)
方法步骤如下:
-
令 origin 为 settings 的 来源。
-
如果 origin 是 不透明来源,则返回 一个被拒绝的Promise,错误为 "
SecurityError
"DOMException
。 -
令 url 为 settings 的 创建URL。
-
令 p 为 一个新的Promise。
-
并行执行以下步骤:
-
返回 p。
delete(options)
方法步骤如下:
-
令 origin 为 settings 的 来源。
-
如果 origin 是 不透明来源,则返回 一个被拒绝的Promise,错误为 "
SecurityError
"DOMException
。 -
令 url 为 settings 的 创建URL。
-
令 p 为 一个新的Promise。
-
并行执行以下步骤:
-
返回 p。
4. CookieStoreManager
接口
CookieStoreManager
关联了一个 registration(注册),即 服务工作线程注册。
CookieStoreManager
接口允许 Service Worker 订阅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()
相同的选项, 属性name
和url
是可选的。订阅后,通知会以 "
cookiechange
" 事件的形式在 Service Worker 全局作用域上触发:
subscribe(subscriptions)
方法步骤如下:
-
令 registration 为 this 的 registration。
-
令 p 为 一个新的Promise。
-
并行执行以下步骤:
-
令 subscription list 为 registration 关联的 cookie 变更订阅列表。
-
对于 subscriptions 中的每个 entry,执行以下步骤:
-
-
返回 p。
4.2.
getSubscriptions()
方法
- subscriptions = await registration . cookies .
getSubscriptions()
-
此方法返回一个Promise,解析为该服务工作线程注册所订阅的cookie变更列表。
getSubscriptions()
方法步骤如下:
-
令 registration 为 this 的 registration。
-
令 p 为 一个新的Promise。
-
并行执行以下步骤:
-
返回 p。
4.3.
unsubscribe()
方法
- await registration . cookies .
unsubscribe
(subscriptions) -
调用此方法将使已注册的服务工作线程停止接收之前订阅的事件。subscriptions参数应与传给
subscribe()
或getSubscriptions()
返回的订阅项格式相同。
unsubscribe(subscriptions)
方法步骤如下:
-
令 registration 为 this 的 registration。
-
令 p 为 一个新的Promise。
-
并行执行以下步骤:
-
令 subscription list 为 registration 关联的 cookie变更订阅列表。
-
对 subscriptions 中每个 entry,执行以下步骤:
-
解析 p 为 undefined。
-
-
返回 p。
4.4.
ServiceWorkerRegistration
接口
ServiceWorkerRegistration
接口被扩展,以通过 CookieStoreManager
提供 cookies
属性,用于订阅cookie变更。
[Exposed =(ServiceWorker ,Window )]partial interface ServiceWorkerRegistration { [SameObject ]readonly attribute CookieStoreManager cookies ; };
每个 ServiceWorkerRegistration
都关联一个 CookieStoreManager
对象。
该 CookieStoreManager
的
registration 等于 ServiceWorkerRegistration
的 服务工作线程注册。
cookies
getter 步骤是返回 this 关联的 CookieStoreManager
对象。
navigator. serviceWorker. register( 'sw.js' ). then( registration=> { registration. cookies. subscribe([{ name: 'session-id' }]); });
5. 事件接口
5.1. CookieChangeEvent
接口
CookieChangeEvent
会在CookieStore
对象上分发,适用于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
changed
和 deleted
属性必须返回初始化时设置的值。
5.2.
ExtendableCookieChangeEvent
接口
ExtendableCookieChangeEvent
会在ServiceWorkerGlobalScope
对象上分发,当发生任何匹配Service Worker
的cookie变更订阅列表的脚本可见cookie变更时。
注意: ExtendableEvent
作为所有Service 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
changed
和 deleted
属性必须返回初始化时设置的值。
6. 全局接口
CookieStore
可通过在Window
或 ServiceWorkerGlobalScope
上下文的全局作用域属性访问。
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. 算法
注意: 这与ECMAScript中的[ECMAScript]时间值表示方法一致。
DOMHighResTimeStamp
millis,
令dateTime为自1970年1月1日00:00:00 UTC起millis毫秒后的日期和时间
(假定每一天正好为86,400,000毫秒),
并返回dateTime对应的最接近的cookie-date
表示的字节序列,该表示依据Cookies § Dates定义。
7.1. 查询cookie
要根据 URL url 和 string-或-null name 查询 cookies:
-
执行 Cookies § 检索模型 中定义的步骤,以使用 url 作为 request-uri 计算 "cookie-string from a given cookie store"。 cookie-string 本身会被忽略,后续步骤使用其中间结果 cookie-list。
在这些步骤中,cookie-string 是为 “非 HTTP” API 生成的。
-
令 list 为一个新的 列表。
-
对于 cookie-list 中的每个 cookie,执行以下步骤:
-
断言:cookie 的 http-only-flag 为 false。
-
如果 name 非 null:
-
规范化 name。
-
令 cookieName 为对 cookie 的 无 BOM 的 UTF-8 解码结果, 使用 cookie 的 name。
-
如果 cookieName 不等于 name,则 继续下一项。
-
-
令 item 为对 cookie 创建 CookieListItem 的结果。
-
将 item 添加到 list。
-
-
返回 list。
要根据 cookie cookie
创建 CookieListItem
:
-
令 name 为对 cookie 的 无 BOM 的 UTF-8 解码结果, 使用 cookie 的 name。
-
令 value 为对 cookie 的 无 BOM 的 UTF-8 解码结果, 使用 cookie 的 value。
注意: 已知有一个实现会暴露除 name 和 value 之外的信息。
7.2. 设置cookie
要使用 url、name、value、可选的 expires、domain、path、sameSite 和 partitioned 设置 cookie,请执行以下步骤:
-
规范化 name。
-
规范化 value。
-
如果 name 或 value 包含 U+003B (;)、任何 C0 控制字符(除了 U+0009 TAB),或 U+007F DELETE,则返回失败。
注意,这些字符限制是否也应该应用于 expires、domain、path 和 sameSite 仍在讨论中。[httpwg/http-extensions Issue #1593]
-
如果 name 包含 U+003D (=),则返回失败。
-
如果 name 的 长度为 0:
-
令 encodedName 为 UTF-8 编码 name 的结果。
-
令 encodedValue 为 UTF-8 编码 value 的结果。
-
如果 encodedName 的 字节序列 长度加上 encodedValue 的 字节序列 长度大于 最大 name/value 对长度,则返回失败。
-
令 host 为 url 的 主机。
-
令 attributes 为一个新的 列表。
-
如果 domain 不为 null,则执行以下步骤:
-
如果 path 是空字符串,则将 path 设为 url 的 序列化 Cookie 默认路径。
-
如果 path 不以 U+002F(/)开头,则返回失败。
-
令 encodedPath 为 UTF-8 编码后的 path。
-
追加 `
Path
`/encodedPath 到 attributes。 -
追加 `
Secure
`/`` 到 attributes。 -
根据 sameSite 进行分支:
-
如果 partitioned 为 true,追加 `
Partitioned
`/`` 到 attributes。 -
执行 Cookies § 存储模型中,当用户代理“收到 cookie”时定义的步骤,其中 url 作为 request-uri,encodedName 作为 cookie-name,encodedValue 作为 cookie-value,attributes 作为 cookie-attribute-list。
在这些步骤中,新创建的 cookie 被视为来自“非 HTTP”API。
-
返回成功。
注意:由于 [RFC6265BIS-14] 中的要求,存储 cookie 仍可能失败,但这些步骤将视为成功。
7.3. 删除cookie
要使用 url、name、domain、path 和 partitioned 删除 cookie,请执行以下步骤:
7.4. 处理变更
要处理cookie变更,执行以下步骤:
-
对于每个
Window
window,执行以下步骤:-
令changes为url的可观察变更。
-
在 DOM 操作任务源上排队全局任务,给定 window,以触发名为 "
change
" 的更改事件,携带 changes,在 window 的CookieStore
上。
-
对于每个服务工作线程注册 registration,执行以下步骤:
-
令changes为一个新的集合。
-
对registration的可观察变更中的每个change(针对registration的scope url),执行:
-
令cookie为change的cookie。
-
对registration的cookie变更订阅列表中的每个subscription,执行:
-
-
令changedList和deletedList为对changes执行准备列表得到的结果。
-
在registration上触发一个名为"
cookiechange
"的功能事件, 使用ExtendableCookieChangeEvent
, 并设置如下属性:
-
可观察变更,针对url,是集合, 包含cookie变更, 这些变更针对cookie存储区中的cookie, 满足Cookies § Retrieval Algorithm步骤1的要求, 即用url作为request-uri,针对“非HTTP”API计算“从给定cookie存储生成的cookie-string”的条件。
cookie变更是一个cookie和一个类型(changed或deleted):
要在target上触发change事件,事件名为type,带有changes,执行:
-
令event为用
CookieChangeEvent
创建的事件。 -
设置event的
type
属性为type。 -
设置event的
bubbles
和cancelable
属性为false。 -
令changedList和deletedList为对changes执行准备列表的结果。
-
设置event的
changed
属性为changedList。 -
设置event的
deleted
属性为deletedList。 -
在target上分发event。
要对changes准备列表,执行以下步骤:
8. 安全性考量
除服务工作线程上下文中的cookie访问外,本API并不打算向Web开放任何新能力。
8.1. 注意事项!
尽管浏览器的cookie实现正在朝着更高安全性和更少“坑点”的默认值演进,目前对于cookie数据安全性仍难以给出强保证。
-
非安全源通常可以覆盖安全源使用的cookie
-
超级域通常可以覆盖子域看到的cookie
-
跨站脚本攻击和其他脚本、头部注入攻击也可伪造cookie
-
cookie读取操作(无论脚本还是Web服务器)不会指明cookie的来源
-
浏览器有时会以令人意外且不易理解的方式截断、转换或驱逐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时,该cookie会自动变为Secure
。
8.4. 意外行为
一些现有cookie行为(尤其是域而非origin的定向、非安全上下文可设置安全上下文可读的cookie,以及脚本可设置脚本不可读的cookie)在Web安全角度来看可能非常意外。
其它意外行为见Cookies § Introduction,例如一个cookie可设置到超级域(如app.example.com可设置整个example.com域的cookie),且cookie可被同一域名下所有端口读取。
进一步复杂化的是各大浏览器cookie处理的历史差异,虽然部分(如端口处理)现已趋于一致。
8.5. 前缀
在可行情况下,示例使用__Host-
和__Secure-
前缀,这会让某些浏览器禁止非安全上下文覆盖、禁止无Secure
标志覆盖,并且__Host-
前缀下禁止显式Domain
或非'/'
Path
属性(实际上强制同源语义)。这些前缀在实现安全cookie的浏览器中提供重要安全益处,并在其他浏览器中平滑降级(即特殊语义未强制但cookie正常工作,且异步cookie
API会强制写入操作的安全语义)。本API的主要目标是与现有cookie兼容,因此也有部分不带这些前缀的cookie名示例。
这些前缀规则也会被本API的写入操作强制,但其他API可能未强制。故依赖其强制性并不明智,除非广泛采用。
8.6. URL作用域
虽然服务工作线程脚本目前无法直接访问cookie,但已经可以通过受控渲染范围内的HTML和脚本资源,注入cookie监控代码,从而实现服务工作线程脚本的远程控制。这意味着在服务工作线程作用域内访问cookie在技术上已经可行,只是不太方便。
当服务工作线程作用域比/
更窄时,仍可能通过猜测或构造一个允许IFRAME的404页面URL,借助脚本在该页面内运行,读取超出作用域路径空间的cookie。相同技术也可扩展到整个origin,但精心构建的网站(即所有超出作用域页面都不可IFRAME)实际上可拒绝路径作用域服务工作线程此能力,因此在未进一步讨论影响前,我不愿取消该限制。
8.7. Cookie规避
为降低开发复杂度并避免临时测试cookie,本异步cookie API会显式拒绝写入或删除操作被忽略的情况。同时会拒绝读取操作被忽略实际cookie数据、模拟空cookie jar的情况。在这些上下文下,观察cookie变更的尝试仍会“工作”,但仅当读访问允许时(如站点权限变更)才会调用回调。
目前在脚本发起cookie写入被禁止的上下文下写document.cookie
通常什么也不做。但许多cookie写入脚本和框架总是写测试cookie再检查其存在,以判断脚本能否写cookie。
同理,在脚本发起cookie读取被禁止的上下文下读document.cookie
通常返回空字符串。但配合Web服务器可验证服务端写入和读取cookie是否生效,并将结果报告给脚本(脚本仍看到空字符串),脚本据此可推断脚本发起的cookie读取被禁止。
9. 隐私考量
9.1. 清除cookie
本节为非规范性内容。
当用户清除某origin的cookie时,User Agent需要清除该origin的所有存储,包括服务工作线程和该origin的DOM可访问存储,防止网站在用户操作后通过持久化存储恢复用户标识。
致谢
感谢Benjamin Sittler,他提出了本API的初步方案。
特别感谢 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) 编写。
知识产权
版权所有 © WHATWG (Apple, Google, Mozilla, Microsoft)。本作品采用知识共享署名4.0国际许可协议(CC BY 4.0)许可。若部分内容被纳入源代码,则这些部分遵循BSD三条款许可证。