# scope_extensions 清单成员

> scope_extensions 列出已安装 Web 应用视为作用域内的其他源，每个源都要用 web-app-origin-association 文件确认关联；Chrome 138 与 Edge 138 已发布。

`scope_extensions` 是一个源的数组，列出已安装 Web 应用希望纳入自身作用域的其他源，这样
`support.example.com` 或 `example.co.uk` 就能在应用窗口内打开，而不出现超出作用域的提示条。清单的
`scope` 成员只能覆盖一个源；该成员是让应用跨越多个源的双向握手，每个额外的源都要在
`/.well-known/web-app-origin-association` 文件里确认这层关联。

Chrome 138、Edge 138、Android WebView 138 与 Samsung Internet 30.0 发布了该成员（BCD
`html.manifest.scope_extensions`）；Chrome 曾在 121 到 126 以源试用形式提供，并自 115 起放在
`about://flags/#enable-desktop-pwas-scope-extensions` 标志之后。Firefox 157 与 Safari 27 不读取它，
会静默丢弃该成员。

## 成员

- **类型**：对象数组。Manifest Incubations 说明文档与 MDN 把每一项写作
  `{ "type": "origin", "origin": "https://support.example.com" }`；Chrome 为源试用撰写的开发者文档
  省略 `type`，写作 `{ "origin": "..." }`。`origin` 是一个 HTTPS 源，可以带 `*.` 通配标签以覆盖
  全部子域，例如 `https://*.example.com`。
- **默认值**：空数组；应用的作用域就是单源的 `scope` 成员。
- **示例值**：`[{ "type": "origin", "origin": "https://*.example.com" }]`。

只有当浏览器从所列的源抓取到 `https://<origin>/.well-known/web-app-origin-association` 并在其中
找到应用的清单 `id` 时，该条目才生效。两份来源对这个文件的形状同样不一致：说明文档与 MDN 以应用
id 为键，`{ "https://example.com/app": { "scope": "/" } }`，其中可选的 `scope` 把扩展收窄到被扩展
源上的某个路径；Chrome 的源试用文档则写作
`{ "web_apps": [{ "web_app_identity": "https://example.com" }] }`。按你的目标 Chromium 版本能校验
的形状提供文件，并把这层关联视为一份信任声明：列出某个应用的源，等于允许该应用把自己的页面
当作这个源的页面来展示。

权限不会随作用域扩展一起转移。在 `example.com` 应用窗口里打开的 `support.example.com` 页面保留
`support.example.com` 自己的权限状态、存储与 Cookie；改变的只有窗口外观。

:::observed
Chrome 155（macOS 26，英文界面）：DevTools > Application > Manifest 没有 `scope_extensions` 的分节，
带该成员的清单也不会在 **Errors and warnings** 下增加任何内容。解析出的数组出现在
`chrome://web-app-internals`：其 JSON 输出列出每个已安装应用的 `scope_extensions` 源，以及抓取各自
关联文件的结果，所以文件不可达或没有写明应用 `id` 的源在那里可见，而在 DevTools 里完全看不到。
:::

## 示例

清单与关联文件是同一份声明拆在两台服务器上；第三个示例是两者就位后页面能了解到什么。

### 把应用扩展到支持子域与国家域名

位于 `https://example.com/app` 的主应用列出两个额外的源。通配形式覆盖 `example.com` 的所有子域，
所以 `support.` 与 `help.` 不需要单独条目；国家域名是另一个可注册域，需要单独列出。

```json
{
  "id": "/app",
  "name": "Example",
  "start_url": "/app/index.html",
  "scope": "/app",
  "display": "standalone",
  "scope_extensions": [
    { "type": "origin", "origin": "https://*.example.com" },
    { "type": "origin", "origin": "https://example.co.uk" }
  ]
}
```

每个列出的源都要自己作答；`example.co.uk` 上缺失或格式错误的关联文件只会让这个源留在作用域外，
子域仍然正常工作。

### 在被扩展的源上提供关联文件

`https://example.co.uk/.well-known/web-app-origin-association` 以 `application/json` 类型、无需认证
地提供 JSON。说明文档的形状以应用完整的清单 id 为键，并把扩展限定到某个路径。

```json
{
  "https://example.com/app": { "scope": "/" }
}
```

对于校验源试用形状的 Chromium 版本，同一声明写作
`{ "web_apps": [{ "web_app_identity": "https://example.com/app" }] }`。一个文件里无法同时发布两种
形状，所以安装后在 `chrome://web-app-internals` 中检查解析结果。

### 让页面知道自己是否处于扩展后的应用窗口内

没有任何 API 会报告某次导航是否被接受为作用域内。页面能观察到的是显示模式：`support.example.com`
上的文档发现自己处于 `standalone`，说明它是在应用窗口里打开的，在 Chrome 138 及之后这意味着扩展
通过了校验。回退分支是浏览器标签页，那里指向关联源的链接表现为普通的跨源导航。

```js
const inAppWindow = ['standalone', 'minimal-ui', 'window-controls-overlay']
  .some((mode) => matchMedia(`(display-mode: ${mode})`).matches);

if (inAppWindow) {
  document.body.classList.add('in-app'); // 隐藏营销页头，保留应用导航
} else {
  // 浏览器标签页，或没有 scope_extensions 的引擎：指向 example.com 的链接
  // 作为普通导航打开，在旧版应用窗口里可能显示超出作用域的提示条。
  document.body.classList.add('in-tab');
}
```

两条分支里的导航本身保持一致：无论扩展是否被遵循，`location.assign()` 到关联源都是正确做法，
显示什么外观由浏览器决定。

## 另请参阅

- [scope 清单成员](/zh/reference/manifest/scope/)
- [id 清单成员](/zh/reference/manifest/id/)
- [handle_links 清单成员](/zh/reference/manifest/handle-links/)
- [Scope Extensions for Web App Manifest (explainer)](https://github.com/WICG/manifest-incubations/blob/gh-pages/scope_extensions-explainer.md)（github.com）
- [Web app scope extensions](https://developer.chrome.com/docs/capabilities/scope-extensions)（developer.chrome.com）
- [scope_extensions](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/scope_extensions)（developer.mozilla.org）