跳转到内容

存储 · 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 唯一起作用的地方。

先检查,只在点击中申请,并在 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 的引擎要么第三方 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 拒绝。

规范

规范状态
无。