存储 · API
Document.requestStorageAccess()
发布于
document.requestStorageAccess() 请求浏览器让加载在跨站 <iframe> 中的文档访问它自己未分区的 Cookie,也就是它作为第一方时能看到、而浏览器已经拦截或分区的那些。document.hasStorageAccess() 报告该文档此刻是否拥有这种访问。有了这个 API,嵌入式登录、支付和评论挂件才能在 Firefox、Safari 和 Chrome 里继续工作,而无需在整站范围重新放开第三方 Cookie。
document.hasStorageAccess()document.requestStorageAccess()document.requestStorageAccess(types)document.hasUnpartitionedCookieAccess()hasStorageAccess() 与 hasUnpartitionedCookieAccess() 解析为布尔值;requestStorageAccess() 在授予时无值兑现,否则拒绝。支持情况:hasStorageAccess() 与 requestStorageAccess() 自 Firefox 65、Safari 11.1、Chrome 119 起;types 参数仅 Chrome 125+;hasUnpartitionedCookieAccess() 仅 Chrome 125+(BCD api.Document.requestStorageAccess)。Safari 按页面而不是按 frame 授予客户端存储访问(BCD 对 api.Document.requestStorageAccess 的备注),所以在一个 iframe 中取得的授权对同一页面上同一被嵌入站点的另一个 iframe 同样生效。
Safari 最早发布该 API(11.1,2018 年),Firefox 在 65 跟进;Chrome 在第三方 Cookie 限制使其成为必需之后于 119 暴露它(BCD api.Document.requestStorageAccess)。types 扩展与 hasUnpartitionedCookieAccess() 仅 Chrome 125+。Android WebView 没有实现,因此原生应用 WebView 内的嵌入式登录挂件会落入检测示例中「没有 API」的分支。
| 参数 | 类型 | 含义 |
|---|---|---|
types |
object,可选(Chrome 125+) | 要暴露哪些未分区存储。布尔成员:all、cookies、sessionStorage、localStorage、indexedDB、locks、caches、getDirectory、estimate、createObjectURL、revokeObjectURL、BroadcastChannel、SharedWorker。省略时只有 Cookie。 |
传入 types 时,Promise 解析为一个 StorageAccessHandle,其成员(handle.localStorage、handle.indexedDB 等)就是未分区的对象;文档自身的全局对象保持分区。不传 types 时,授权作用于 iframe 后续请求携带的 Cookie 和 document.cookie。
| 异常 | 触发条件 |
|---|---|
InvalidStateError DOMException |
文档不是完全活跃状态,或传入的 types 所有成员都为 false。 |
NotAllowedError DOMException |
窗口不是安全上下文;storage-access Permissions Policy 拦截了该特性;文档或顶级文档是不透明源;iframe 带沙箱但没有 allow-storage-access-by-user-activation;调用不在瞬时用户激活期间且尚无授权;用户拒绝了提示;或引擎特有的检查未通过(Safari 要求此前与被嵌入站点有第一方交互,Chrome 应用其站点对启发式规则)。 |
开发者最先撞上的是用户激活这一条。Chrome 为此在 Console 打出的信息是 requestStorageAccess: Must be handling a user gesture to use.(storage_access_grant_permission_context.cc,github.com)。已经持有当前 <顶级站点, 被嵌入站点> 组合授权的文档可以在手势之外调用该方法;但每个新的 iframe 实例或标签页仍要调用一次,才能在该上下文中激活授权。
三个示例都运行在被嵌入的文档内,这是该 API 唯一起作用的地方。
让登录挂件以存储访问为前提
Section titled “让登录挂件以存储访问为前提”先检查,只在点击中申请,并在 Promise 兑现之前让整个挂件在没有 Cookie 的情况下也能用。按钮就是 API 需要的那个激活。
async function initWidget(button) { if (await document.hasStorageAccess()) { return renderSignedIn(); } renderSignedOut(); button.addEventListener('click', async () => { try { await document.requestStorageAccess(); renderSignedIn(); // document.cookie 现在包含第一方会话 Cookie } catch (err) { if (err.name === 'NotAllowedError') renderDeniedHint(); else throw err; } });}Safari 第一次会显示写明两个站点的提示;Firefox 显示一次并为该站点对记住答案;Chrome 对同一 Related Website Set 内的站点对不提示直接决定,其他情况提示(Using the Storage Access API,developer.mozilla.org)。三种情况下代码相同。
通过句柄读取未分区的 localStorage
Section titled “通过句柄读取未分区的 localStorage”传入 types 时,Chrome 返回一个句柄而不是改动全局对象。用句柄读取未分区数据,让 window.localStorage 继续存放应当按顶级站点隔离的状态。
async function loadSharedPrefs() { const handle = await document.requestStorageAccess({ localStorage: true }); return JSON.parse(handle.localStorage.getItem('prefs') ?? '{}');}该调用同样要求激活和安全上下文,并且在所请求的类型全部为 false 时以 InvalidStateError 拒绝;只申请挂件真正要读的类型。
检测 API 并退回分区状态
Section titled “检测 API 并退回分区状态”没有该 API 的引擎要么第三方 Cookie 未分区(允许 Cookie 的旧版 Chrome),要么完全没有跨站 Cookie。两种情况的处理一样:尝试依赖 Cookie 的路径,失败即视为未登录。
async function cookieAccess() { if (!('requestStorageAccess' in document)) { return 'unknown'; // 没有 Storage Access API:按 Cookie 可能到也可能不到处理 } if (await document.hasStorageAccess()) return 'granted'; return 'needs-gesture'; // 显示按钮,在其 click 中调用 requestStorageAccess()}带沙箱的 <iframe> 需要在 sandbox 属性里同时写上 allow-storage-access-by-user-activation、allow-scripts 和 allow-same-origin,否则无论有没有手势,每次请求都以 NotAllowedError 拒绝。
- The Storage Access API: requestStorageAccess()(privacycg.github.io)
- Document: requestStorageAccess() method(developer.mozilla.org)
- 分区 Cookie(CHIPS)
- localStorage 与 sessionStorage
- Credential Management API
规范
| 规范 | 状态 |
|---|---|
| 无。 | |