# 可安装性条件

> 各浏览器在提供安装入口前检查什么：Chrome 的 manifest 与 HTTPS 门槛、iOS 26 上 Safari 的零要求、Firefox 143 的任务栏 web 应用。

可安装性条件是浏览器为页面提供安装入口（地址栏图标、菜单项或 `beforeinstallprompt` 事件）之前检查的一组条件。Chromium 浏览器（Chrome、Edge、Samsung Internet）要求安全源，外加带有名称、起始 URL、非 browser 显示模式与图标的 manifest；iOS 26 上的 Safari 与 Windows 上的 Firefox 143 完全没有 manifest 要求，任何页面都能从各自菜单安装（BCD `html.manifest`、Chrome "Changes to the installability criteria"、WebKit Safari 26.0 发布文章、Firefox 143 发行说明）。

## 工作原理

Chromium 在页面每次加载时评估，并把结果记录在 DevTools 的 Application › Manifest › "Installability" 下。按 Chrome 可安装性文章列出的顺序，检查项为：

- 页面来自安全上下文：`https:`，或开发期间的 `http://localhost` 与 `http://127.0.0.1`。
- 文档链接了一个可解析的 manifest（`<link rel="manifest">`），其中包含 `name` 或 `short_name`、`start_url`、取值为 `standalone`、`fullscreen` 或 `minimal-ui` 的 `display`（或解析到其中之一的 `display_override`），以及包含 192 px 与 512 px 条目的 `icons`；安装对话框还要求至少一个 144 px 以上、PNG、SVG 或 WebP 格式、设置了 `sizes` 且 `purpose` 包含 `any` 的图标。
- `start_url` 位于 `scope` 内并返回一个文档（重定向到其他源会使检查失败）。
- 该应用尚未在当前浏览器配置文件中安装。

带 `fetch` 处理器的已注册 service worker 曾是清单的一部分，直到 Android 上的 Chrome 108 与桌面端的 Chrome 112 取消了从浏览器菜单安装时的这一要求；Chrome 为没有 service worker 的已安装应用提供默认离线页。同一篇 Chrome 文章指出，`beforeinstallprompt` 背后的启发式仍然检查 `fetch` 处理器，所以页面可能通过菜单检查却始终收不到事件。Chrome 还会把事件（以及 Android 的 mini-infobar）推迟到参与度启发式满足之后，这就是 DevTools 面板报告"可安装"而事件不触发的原因。

其他浏览器采用不同的清单：

| 浏览器 | 提供安装之前要求什么 | 安装入口 |
|---|---|---|
| Edge 79+（桌面端） | 与 Chrome 相同的 manifest 与 HTTPS 检查，相同的 Chromium 版本门槛 | 地址栏图标、Apps 菜单、`beforeinstallprompt` |
| Samsung Internet 9.0+ | Chromium 清单的自有变体；兼容性数据集记录 `beforeinstallprompt` 自 9.0 起可用 | 地址栏安装按钮、`beforeinstallprompt` |
| Safari，iOS 与 iPadOS 26 | 无：从共享菜单添加的每个站点都以 web 应用打开，除非用户关闭 "Open as Web App" | 共享菜单 › "Add to Home Screen" |
| Safari，iOS 与 iPadOS 16.4 至 18 | 需要 manifest `display: standalone` 或 `fullscreen`（或旧的 `apple-mobile-web-app-capable` meta 标签）才进入 web 应用模式；否则为书签 | 共享菜单 › "Add to Home Screen" |
| Safari 17+，macOS 14+ | 无；File › "Add to Dock" 对任何页面可用，存在 manifest 时读取其取值 | File 菜单 |
| Firefox 143+，Windows | 无；地址栏图标把任何站点固定为任务栏 web 应用 | 地址栏图标 |
| Firefox for Android | 一个 manifest；安装为主屏幕快捷方式，无事件 | 三点菜单 › "Install" |

对作者而言，权衡在于只有 Chromium 的清单是可能不通过的，所以满足 Chrome 的 manifest 也满足其余各家浏览器的安装路径；反之不成立，只在 iOS 上测试的页面可能缺少 Chrome 对话框需要的图标就上线了。

:::observed
在 Chrome DevTools › Application › Manifest（英文界面）中，以纯 HTTP 提供的页面在 "Installability" 下报告 `Page is not served from a secure origin`；图标全部小于 144 px 的 manifest 报告 `Manifest does not contain a suitable icon - PNG, SVG or WebP format of at least 144px is required, the sizes attribute must be set, and the purpose attribute, if set, must include "any".`。两段文字都是 Chromium 的可安装性错误信息，由 Chrome "Debug Progressive Web Apps" 文档所描述的面板显示。
:::

## 示例

第一个示例是镜像 Chromium 清单的页面侧检查；第二个是一份能通过检查的 manifest。

### 按 Chromium 清单检查页面并据结果行动

页面无法运行 Chrome 的可安装性算法，但可以核对它自己控制的输入，并按浏览器决定提供哪种安装界面。回退分支覆盖那些无需任何这些输入就能安装的浏览器。

```js
async function installabilityReport() {
  const problems = [];
  if (!isSecureContext) problems.push('不是安全上下文：没有浏览器会提供安装');

  const link = document.querySelector('link[rel="manifest"]');
  if (!link) {
    problems.push('没有 <link rel="manifest">');
  } else {
    const manifest = await fetch(link.href).then((r) => r.json()).catch(() => null);
    if (!manifest) problems.push('manifest 无法解析');
    else {
      if (!manifest.name && !manifest.short_name) problems.push('manifest 需要 name 或 short_name');
      if (!manifest.start_url) problems.push('manifest 需要 start_url');
      if (!['standalone', 'fullscreen', 'minimal-ui'].includes(manifest.display)) problems.push('display 不能是 browser');
      const sizes = (manifest.icons ?? []).flatMap((i) => (i.sizes ?? '').split(' '));
      if (!sizes.includes('192x192') || !sizes.includes('512x512')) problems.push('icons 需要 192x192 与 512x512');
    }
  }
  if (!('serviceWorker' in navigator)) problems.push('不支持 service worker：离线页不可用');
  return problems;
}

const problems = await installabilityReport();
if (problems.length && 'onbeforeinstallprompt' in window) {
  console.warn('Chromium 不会触发 beforeinstallprompt：', problems);   // 修 manifest
} else if (!('onbeforeinstallprompt' in window)) {
  showMenuInstructions();   // Safari 与 Firefox 不看这份清单，从菜单安装
}
```

service worker 那一行只是信息提示：自 Chrome 112 起它的缺失不再阻止安装，但意味着已安装应用显示的是 Chrome 的默认离线页，而不是站点自己的。

### 一份满足所有浏览器安装路径的 manifest

下面的 manifest 满足 Chromium 清单；Safari 与 Firefox 会从中读取 `name`、`icons` 与 `display`，但并不要求它们。

```json
{
  "name": "Field Notes",
  "short_name": "Notes",
  "id": "/notes/",
  "start_url": "/notes/?source=pwa",
  "scope": "/notes/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#1f3a5f",
  "icons": [
    { "src": "/notes/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
    { "src": "/notes/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
    { "src": "/notes/icons/maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
  ]
}
```

把 `any` 与 `maskable` 两种 purpose 放在不同的图标条目上，对 Chrome 对话框很重要：它需要一个 `purpose` 包含 `any` 的图标；写成单条 `"purpose": "any maskable"` 也能通过检查，但两种场景会渲染同一张位图。

## 另请参阅

- [Changes to the installability criteria](https://developer.chrome.com/blog/update-install-criteria)（developer.chrome.com）
- [What does it take to be installable?](https://web.dev/articles/install-criteria)（web.dev）
- [WebKit features in Safari 26.0](https://webkit.org/blog/17333/webkit-features-in-safari-26-0/)（webkit.org）
- [beforeinstallprompt 事件](/zh/reference/installation/install-prompt/)
- [iOS 添加到主屏幕](/zh/reference/installation/ios-add-to-home-screen/)
- [桌面端安装](/zh/reference/installation/desktop-install/)
- [`icons` manifest 成员](/zh/reference/manifest/icons/)
- [Firefox](/zh/reference/platforms/firefox/)