# file_handlers 清单成员

> file_handlers 把已安装的 PWA 注册为操作系统中指定 MIME 类型与扩展名的打开程序，随后由 window.launchQueue 把被打开的文件交给页面处理。

`file_handlers` 是一个处理器对象数组。应用安装后，它把 PWA 注册为操作系统中所列 MIME 类型与
扩展名的打开程序：在 Finder、资源管理器或"文件"应用中打开匹配的文件，会以处理器的 `action` URL
启动应用，页面再通过 `window.launchQueue` 读取文件。清单成员只负责建立注册关系，文件本身仍要由
页面用 JavaScript 消费。

Chrome 102 与 Edge 102 在 Windows、macOS、Linux 与 ChromeOS 上处理该成员（BCD
`html.manifest.file_handlers`）。Android 版 Chrome、Safari 27、Firefox 157 与 Samsung Internet
会丢弃它，因此 PWA 的文件关联只存在于桌面 Chromium。

## 成员

- **类型**：对象数组。每个对象包含 `action`（字符串 URL）、`accept`（把 MIME 类型映射到扩展名
  数组的对象）、可选的 `icons`（操作系统为该类型文件显示的图像资源数组）以及可选的
  `launch_type`（`"single-client"` 或 `"multiple-clients"`）。
- **默认值**：不注册。成员缺失，或用户在系统层面移除了关联时，打开文件不会启动应用。
- **示例值**：`[{ "action": "/open", "accept": { "text/markdown": [".md"] } }]`。

Chromium 逐个校验处理器。`action` 必须是清单 `scope` 内的 URL；越界或无法解析的 `action` 会让
该处理器被丢弃，解析器消息为 `FileHandler ignored. Property 'action' is invalid.`；`accept` 不是
"MIME 类型到扩展名数组"的对象时，消息为 `FileHandler ignored. Property 'accept' is invalid.`
（Chromium `manifest_parser.cc`）。扩展名必须以点开头；操作系统注册的是 MIME 类型键，所以
`text/markdown` 配 `[".md", ".markdown"]` 会同时认领两个后缀。

`launch_type` 默认为 `"single-client"`：一次打开多个文件时，它们作为一个 `LaunchParams` 交给同一个
窗口，`files` 里有多个条目。设为 `"multiple-clients"` 时，每个文件各自启动一次，`files` 只含一个句柄。
Windows 不区分这两种设置，无论如何都按文件逐个启动应用（developer.chrome.com，"Handle files in
Progressive Web Apps"）。

首次通过处理器打开文件时，Chromium 会在页面拿到文件之前弹出权限对话框。授权按应用和文件类型记录；
清单更新改动了 `file_handlers` 时授权会被重置，用户也可以在该应用的网站设置里撤销。在 Windows 与
macOS 上，这个关联要和原生应用争夺同一扩展名，用户可能仍需从"打开方式"菜单中选择该 PWA。

:::observed
Chrome 155（macOS 26，英文界面）：安装一个清单声明 `"accept": { "text/markdown": [".md"] }` 的
PWA 后，Finder 右键菜单的 **Open With** 会以清单 `name` 列出该应用。删除 `file_handlers` 成员并
等待每日清单更新后，该条目消失，无需重新安装。在 DevTools > Application > Manifest 中，`action`
位于 `scope` 之外的处理器会出现在 **Errors and warnings** 下，内容为
`FileHandler ignored. Property 'action' is invalid.`。
:::

## 示例

下面两份清单注册了一个 Markdown 编辑器；脚本展示页面如何接收句柄，以及没有文件到达时怎么办。

### 为两个扩展名注册 Markdown 编辑器

一个处理器同时认领 `.md` 与 `.markdown`，把每次启动都路由到 `/editor`，并提供一个操作系统可
显示在该类型文件上的图标。`scope` 显式写出，以确保 `action` URL 明确位于其内。

```json
{
  "name": "Draft",
  "start_url": "/",
  "scope": "/",
  "display": "standalone",
  "file_handlers": [
    {
      "action": "/editor",
      "accept": {
        "text/markdown": [".md", ".markdown"],
        "text/plain": [".txt"]
      },
      "icons": [{ "src": "/icons/markdown-file.png", "sizes": "256x256", "type": "image/png" }],
      "launch_type": "single-client"
    }
  ]
}
```

在 Chrome 102+ 上安装后，操作系统会把 Draft 列为 `.md`、`.markdown` 与 `.txt` 的打开程序。忽略
该成员的浏览器上什么都不会变：清单依旧有效，应用照常安装。

### 消费启动参数并回退到文件选择器

`/editor` 页面应尽早设置 `launchQueue` 消费者：消费者设置之前到达的启动会被排队，但如果等到用户
操作之后再设置，就赶不上第一个文件了。`launchQueue` 不存在时（桌面 Chromium 之外的任何浏览器），
把同一个函数接到一个打开 `<input type="file">` 的按钮上。

```js
async function openHandle(handle) {
  const file = await handle.getFile();
  editor.value = await file.text();
  document.title = `${file.name} - Draft`;
}

if ('launchQueue' in window) {
  window.launchQueue.setConsumer(async (launchParams) => {
    if (!launchParams.files.length) return; // 从图标启动，而不是从文件启动。
    for (const handle of launchParams.files) await openHandle(handle);
  });
} else {
  // 这里不可能发生文件处理启动，改为提供选择器。
  openButton.hidden = false;
  openButton.addEventListener('click', () => picker.click());
  picker.addEventListener('change', async () => {
    const [file] = picker.files;
    if (file) editor.value = await file.text();
  });
}
```

`launchParams.files` 中是 `FileSystemFileHandle` 对象，同一个句柄之后可以传给 `createWritable()`
直接写回被打开的文件，无需保存对话框；`<input type="file">` 回退得到的是普通 `File` 对象，无法写回。

### 从操作系统侧核对注册

没有任何 JavaScript API 能报告系统关联是否存在。安装后到操作系统里核对：Windows 11（英文界面）
打开 **Settings > Apps > Default apps**，搜索扩展名，PWA 会以清单 `name` 出现；macOS 在 Finder 中
选中匹配文件，用 **File > Get Info > Open with** 查看。若应用不在列表里，去 DevTools > Application >
Manifest 看 `Errors and warnings` 里有没有关于该处理器的一行。

```json
{
  "file_handlers": [
    {
      "action": "https://other.example/open",
      "accept": { "text/markdown": [".md"] }
    }
  ]
}
```

这个处理器被丢弃，因为 `action` 指向的源与清单 `scope` 不同；控制台显示
`Manifest: FileHandler ignored. Property 'action' is invalid.`，操作系统也永远不会得知这个类型。

## 另请参阅

- [处理文件](/zh/guides/file-handling/)
- [File System Access API：读写本地文件](/zh/reference/capabilities/file-system-access/)
- [launch_handler 清单成员](/zh/reference/manifest/launch-handler/)
- [scope 清单成员](/zh/reference/manifest/scope/)
- [Manifest Incubations: file_handlers member](https://wicg.github.io/manifest-incubations/#file_handlers-member)（wicg.github.io）
- [Let installed web applications be file handlers](https://developer.chrome.com/docs/capabilities/web-apis/file-handling)（developer.chrome.com）
- [Handle files in Progressive Web Apps](https://learn.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/how-to/handle-files)（learn.microsoft.com）