# protocol_handlers 清单成员

> protocol_handlers 把已安装的 Web 应用注册为操作系统里 mailto、web+note 这类 URL 协议的处理程序；Chrome 96 与 Edge 96 仅在桌面端支持。

`protocol_handlers` 是一个由 `{ protocol, url }` 对象组成的数组，请求浏览器在安装时把这个 Web 应用
注册为操作系统中若干 URL 协议的处理程序。注册之后，在任何应用里点击 `web+note://` 或 `mailto:`
链接都会在 `url` 处打开该 Web 应用，`%s` 被替换为完整链接。

Chrome 96 与 Edge 96 为 Windows、macOS、Linux 上的桌面安装实现了该成员（BCD
`html.manifest.protocol_handlers`）。Android 上的 Chrome、Samsung Internet、Firefox 157 与 Safari 27
不读取它。运行时 API `navigator.registerProtocolHandler()` 解决的是另一个场景：它只在浏览器内部注册
当前站点，且每次访问都需要用户手势；清单成员则在安装时注册，并且触达操作系统。

## 成员

- **类型**：对象数组。每个对象有两个必填字符串：`protocol`，不带冒号的协议名；`url`，位于清单
  `scope` 内、包含字面量 `%s` 的 HTTPS URL。相对 `url` 按清单 URL 解析。
- **默认值**：空数组，不注册任何协议。
- **示例值**：`[{ "protocol": "web+note", "url": "/open?note=%s" }]`。

`protocol` 必须是以 `web+` 开头、后接小写 ASCII 字母的自定义协议，或是 HTML 标准安全列表中的协议：
`bitcoin`、`ftp`、`ftps`、`geo`、`im`、`irc`、`ircs`、`magnet`、`mailto`、`matrix`、`mms`、
`news`、`nntp`、`openpgp4fpr`、`sftp`、`sip`、`sms`、`smsto`、`ssh`、`tel`、`urn`、`webcal`、
`wtai` 与 `xmpp`。其他值（例如 `note` 或 `ms-word`）会被跳过。

条目格式不对时，Chromium 的解析器丢弃的是该条目而不是整个数组：
`protocol_handlers entry ignored, required property 'protocol' is invalid.` 与
`protocol_handlers entry ignored, required property 'url' is invalid.` 指出缺失的字段；`url` 里没有
占位符则产生 `The url provided ('/open?note=') does not contain '%s'.`。规范还会跳过 `url` 落在
`scope` 之外的条目，以及归一化后 `url` 与前面条目重复的条目。

注册以安装为单位：Chrome 在应用安装时把处理程序写入操作系统，卸载时移除。带该协议的链接第一次
启动应用时，Chrome 会先弹出一个写明应用名称的确认对话框再导航；多个应用声明同一协议时，操作系统
可能让用户选择默认程序。

:::observed
Chrome 155（macOS 26，英文界面）的 DevTools > Application > Manifest 中有 **Protocol Handlers**
一节，列出每个声明的协议，并提供一个文本框和 **Test protocol** 按钮。输入 `web+note://2026-10-03`
并按下按钮，已安装应用导航到 `/open?note=web+note://2026-10-03`：替换 `%s` 的是整条链接而不只是
载荷，仅 `#` 之类的字符被百分号编码（Manifest Incubations 的示例里 `web+music://#1234` 到达时写作
`web+music://%231234`）。
:::

## 示例

清单一侧只有几行；处理页面则要应付可能缺失、被编码或协议不符的值。

### 为笔记应用注册自定义 web+ 协议

一款笔记应用声明 `web+note`，让邮件和聊天里的 `web+note://2026-10-03` 链接打开对应条目。`url` 位于
应用的 `scope` 之下，并把 `%s` 放在查询参数里，这样路径的其余部分保持静态。

```json
{
  "name": "Notebook",
  "start_url": "/",
  "scope": "/",
  "display": "standalone",
  "protocol_handlers": [
    { "protocol": "web+note", "url": "/open?note=%s" }
  ]
}
```

每个协议只注册一个处理程序；同一个 `protocol` 写两次、URL 不同时，保留第一条。

### 在处理页面上解码被替换进来的链接

浏览器用整条链接替换 `%s`，所以页面收到的是 `web+note://2026-10-03` 而不是 `2026-10-03`，其中
`#` 之类的字符被百分号编码。自己读取原始查询值并解码：`URLSearchParams` 会把 `+` 变成空格，
从而破坏 `web+note`。值缺失或携带别的协议时走回退分支，渲染普通页面，这也就是直接访问 `/open`
时的样子。

```js
const raw = location.search.match(/[?&]note=([^&]*)/)?.[1];
let link = null;
try {
  link = raw ? decodeURIComponent(raw) : null;
} catch {
  link = null; // 百分号编码损坏：当作没有链接处理
}

if (link?.startsWith('web+note://')) {
  openNote(link.slice('web+note://'.length)); // "2026-10-03"
} else {
  showNoteList(); // 直接访问，或是本页不处理的协议
}
```

读取之后用 `history.replaceState()` 替换掉查询串，这样刷新或收藏不会再次触发处理逻辑。

### 在清单被忽略的环境回退到运行时 API

在没有该清单成员的浏览器里，`navigator.registerProtocolHandler()` 只在浏览器内部注册同一个协议，
而且必须在用户手势中调用。对两者都做特性检测，并为运行时路径提供一个按钮。

```js
export function offerProtocolRegistration(button) {
  const installed = matchMedia('(display-mode: standalone)').matches;
  if (installed) return; // Chrome/Edge 桌面端：清单已经注册了 web+note
  if (typeof navigator.registerProtocolHandler !== 'function') return; // Safari：没有可用路径
  button.hidden = false;
  button.addEventListener('click', () => {
    navigator.registerProtocolHandler('web+note', `${location.origin}/open?note=%s`);
  });
}
```

Firefox 157 接受 `web+` 协议的运行时注册；之后该处理程序只对在 Firefox 内部点击的链接生效，
对其他应用里的链接不生效。

## 另请参阅

- [Manifest protocol_handlers：为 PWA 注册自定义 URL 协议](/zh/reference/capabilities/protocol-handlers/)
- [scope 清单成员](/zh/reference/manifest/scope/)
- [launch_handler 清单成员](/zh/reference/manifest/launch-handler/)
- [Manifest Incubations: protocol_handlers member](https://wicg.github.io/manifest-incubations/#protocol_handlers-member)（wicg.github.io）
- [Handle protocols in PWAs](https://learn.microsoft.com/en-us/microsoft-edge/progressive-web-apps/how-to/handle-protocols)（learn.microsoft.com）
- [Navigator: registerProtocolHandler() method](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/registerProtocolHandler)（developer.mozilla.org）