# Document.requestStorageAccess()

> requestStorageAccess() 与 hasStorageAccess() 如何让跨站 iframe 访问未分区的 Cookie、用户激活的要求，以及拒绝条件。

`document.requestStorageAccess()` 请求浏览器让加载在跨站 `<iframe>` 中的文档访问它自己未分区的 Cookie，也就是它作为第一方时能看到、而浏览器已经拦截或分区的那些。`document.hasStorageAccess()` 报告该文档此刻是否拥有这种访问。有了这个 API，嵌入式登录、支付和评论挂件才能在 Firefox、Safari 和 Chrome 里继续工作，而无需在整站范围重新放开第三方 Cookie。

## 语法

```js
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](https://github.com/chromium/chromium/blob/main/chrome/browser/storage_access_api/storage_access_grant_permission_context.cc)，github.com）。已经持有当前 `<顶级站点, 被嵌入站点>` 组合授权的文档可以在手势之外调用该方法；但每个新的 iframe 实例或标签页仍要调用一次，才能在该上下文中激活授权。

## 示例

三个示例都运行在被嵌入的文档内，这是该 API 唯一起作用的地方。

### 让登录挂件以存储访问为前提

先检查，只在点击中申请，并在 Promise 兑现之前让整个挂件在没有 Cookie 的情况下也能用。按钮就是 API 需要的那个激活。

```js
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](https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API/Using)，developer.mozilla.org）。三种情况下代码相同。

### 通过句柄读取未分区的 localStorage

传入 `types` 时，Chrome 返回一个句柄而不是改动全局对象。用句柄读取未分区数据，让 `window.localStorage` 继续存放应当按顶级站点隔离的状态。

```js
async function loadSharedPrefs() {
  const handle = await document.requestStorageAccess({ localStorage: true });
  return JSON.parse(handle.localStorage.getItem('prefs') ?? '{}');
}
```

该调用同样要求激活和安全上下文，并且在所请求的类型全部为 `false` 时以 `InvalidStateError` 拒绝；只申请挂件真正要读的类型。

### 检测 API 并退回分区状态

没有该 API 的引擎要么第三方 Cookie 未分区（允许 Cookie 的旧版 Chrome），要么完全没有跨站 Cookie。两种情况的处理一样：尝试依赖 Cookie 的路径，失败即视为未登录。

```js
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` 拒绝。

:::observed
在 Chrome 中于跨站 iframe 的 Console 里、不在任何点击内直接调用 `document.requestStorageAccess()`，Promise 立即拒绝并打出 `Uncaught (in promise) DOMException: requestStorageAccess: Must be handling a user gesture to use.`；该字符串定义在 Chromium 的 [storage_access_grant_permission_context.cc](https://github.com/chromium/chromium/blob/main/chrome/browser/storage_access_api/storage_access_grant_permission_context.cc)（github.com）中，旁边还有顶级变体对应的 `requestStorageAccessFor: Must be handling a user gesture to use.`。同样的调用放进 `click` 处理函数后要么兑现、要么弹出权限提示，不会打出这条信息。
:::

## 另请参阅

- [The Storage Access API: requestStorageAccess()](https://privacycg.github.io/storage-access/#dom-document-requeststorageaccess)（privacycg.github.io）
- [Document: requestStorageAccess() method](https://developer.mozilla.org/en-US/docs/Web/API/Document/requestStorageAccess)（developer.mozilla.org）
- [分区 Cookie（CHIPS）](/zh/reference/storage/partitioned-storage-chips/)
- [localStorage 与 sessionStorage](/zh/reference/storage/localstorage/)
- [Credential Management API](/zh/reference/capabilities/credential-management/)