# handle_links 清单成员

> handle_links 是 WICG 的一项提案，想让已安装的 PWA 请求浏览器在应用窗口中打开作用域内的链接。没有任何引擎实现它，Chrome 155 会直接丢弃该成员。

`handle_links` 是一个提案中的清单成员，写在 WICG 的 `pwa-url-handler` 说明文档里：已安装的 PWA 用它
告诉浏览器，指向其 `scope` 内的链接是否应在已安装的应用窗口中打开，而不是在浏览器标签页中。该成员取
三个字符串值之一，而且只是提示，不是保证：用户代理仍可以让用户偏好压过清单。

没有任何引擎实现它。chromestatus.com 上 Chromium 的条目把实现状态列为 "On hold"，没有 origin
trial，也没有里程碑；Firefox 与 Safari 未发表任何立场（chromestatus.com，feature
5740751225880576）。Chrome 155 解析清单时把它当作未知键静默丢弃。Chromium 里倒是有链接捕获，但它是
按应用设置的用户选项，不是清单声明。

## 成员

- **类型**：字符串，`"auto"`、`"preferred"` 或 `"not-preferred"` 之一。
- **默认值**：成员缺失或取其他值时为 `"auto"`，由用户代理按平台惯例决定。
- **示例值**：`"preferred"`。

`preferred` 请求用户代理在已安装应用中打开作用域内链接；`not-preferred` 请求保留在浏览器中；
`auto` 交给平台。说明文档的"非目标"划定了边界：它不扩大 `scope`（那是 `scope_extensions` 的事），
也不决定链接落到应用内之后发生什么（那是 `launch_handler` 的事）。它只回答一个问题：作用域内的
链接到底会不会到达应用。

实际行为由已经发布的机制决定。桌面版 Chrome 155 与 Edge 154 在应用设置页（`chrome://apps`，右键，
**App settings**）提供名为 **Opening supported links** 的按应用开关，选项是 **Open in [应用名]**；
默认关闭，清单无法改变它。Android 版 Chrome 在站点的 `assetlinks.json` 通过 Digital Asset Links
验证了应用后，会把链接捕获进 WebAPK，同样不读清单。

:::observed
Chrome 155（macOS 26，英文界面）：在一份其他方面有效的清单里加入 `"handle_links": "preferred"`
后，DevTools > Application > Manifest 既没有该成员的区块，也没有 **Errors and warnings** 条目，
控制台也没有任何输出。未知成员被丢弃时不给诊断，所以要发现 `handle_links` 没有生效，只能从别的
应用点击一个作用域内链接，然后看着它在浏览器标签页中打开。
:::

## 示例

第一个示例展示提案的语法；第二个展示在 Chromium 中真正可用于处理被捕获链接的成员。

### 按说明文档声明偏好

该成员位于顶层，与 `scope` 并列。`start_url` 必须保持在 `scope` 之内，声明的边界才会生效；否则
用户代理会回退到从 `start_url` 推导的作用域（w3.org，Web Application Manifest "scope member"），
`handle_links` 覆盖的链接就不再是作者想要的那些。

```json
{
  "name": "Tracker",
  "start_url": "/app/",
  "scope": "/app/",
  "handle_links": "preferred"
}
```

这份清单在任何地方都有效，因为未知成员会被忽略。在 Chrome 155 上这一行不改变任何行为；它只是为
将来可能实现该说明文档的浏览器记录意图。

### 用 launch_handler 与 launchQueue 路由被捕获的链接

用户为应用开启链接捕获后，Chrome 110+ 会启动已安装应用，并通过 `LaunchParams.targetURL` 报告被
点击的 URL。把 `launch_handler` 与 `launchQueue` 消费者配合使用，可以只保留一个窗口并就地导航；
没有消费者时，浏览器会在新的应用窗口中打开该 URL。

```json
{
  "launch_handler": { "client_mode": "navigate-existing" }
}
```

```js
if ('launchQueue' in window) {
  window.launchQueue.setConsumer((launchParams) => {
    if (!launchParams.targetURL) return; // 从图标启动，无需路由。
    const url = new URL(launchParams.targetURL);
    router.navigate(url.pathname + url.search);
  });
} else {
  // Firefox 157 与 Safari 27：链接在标签页中打开，页面正常加载该 URL。
}
```

让被捕获的链接复用已打开窗口的是 `client_mode: "navigate-existing"`；`launchQueue` 消费者只在
客户端路由时才需要。加载时按 URL 渲染的页面两者都不需要。

## 另请参阅

- [scope 清单成员](/zh/reference/manifest/scope/)
- [scope_extensions 清单成员](/zh/reference/manifest/scope-extensions/)
- [launch_handler 清单成员](/zh/reference/manifest/launch-handler/)
- [WebAPK：Chrome 在 Android 上安装 PWA 的方式](/zh/reference/installation/webapk/)
- [PWA URL Handler: handle_links explainer](https://github.com/WICG/pwa-url-handler/blob/main/handle_links/explainer.md)（github.com）
- [Chrome Platform Status: Web App Link Handling (handle_links)](https://chromestatus.com/feature/5740751225880576)（chromestatus.com）
- [Launch Handler API](https://developer.chrome.com/docs/web-platform/launch-handler)（developer.chrome.com）