安装中的 API
Web Install API(navigator.install)
发布于
navigator.install() 是一个基于 Promise 的方法,用来请求浏览器安装一个 Web 应用:不带参数调用时目标是当前文档所链接的 manifest,带参数时目标是调用方给出的 manifest URL。解释文档把它定位为 beforeinstallprompt 的扩展,后者无法安装当前已加载的 Web 应用之外的内容。
BCD api.Navigator.install 记录 Chrome 154 起支持,需要把名为 #web-app-installation-api 的偏好开关设为 Enabled,并镜像到该记录列出的 Chromium 系浏览器;Firefox 与 Safari 均无支持。该记录标记为实验性且处于标准轨道。
Chrome Platform Status feature 5183481574850560(chromestatus.com)把 Web Install API 列为在 Chrome 157 中默认启用,并这样概括它:“Starting in Chrome 157, the new Web Install API allows websites to prompt users to install a web app, given a manifest URL and optional manifest ID.”同一页面把规范成熟度记为 “Specification being incubated in a Community Group”,把 Mozilla 的标准立场记为 “No signal”,把 WebKit 的记为 “Oppose”,并分别链接到对应的 standards-positions issue。
await navigator.install();await navigator.install({ manifest: 'https://suite.example/mail/manifest.json' });await navigator.install({ manifest: 'https://music.example/manifest.webmanifest', manifestId: 'https://music.example/app',});草案规范在 Navigator 的 partial 接口上声明了两个重载,都返回 Promise<WebInstallResult>:一个不带参数,另一个接受 InstallParams 字典,其中 manifest 成员为必填,manifestId 成员是可选且可为 null 的 USVString。WebInstallResult 被声明为空字典。
字典可以整体省略,此时安装目标就是当前已加载页面的 manifest。
| 参数 | 类型 | 含义 |
|---|---|---|
options.manifest |
USVString,必填 |
标识目标 Web 应用的应用 manifest 的 URL。相对值按发起文档的 URL 解析。 |
options.manifestId |
USVString?,可选 |
一个绝对的 manifest ID 断言:调用方预期的处理后 manifest id。草案说明 null 与省略含义相同。 |
解释文档说明:当且仅当 manifest 指向的 JSON 中含有 id 时,开发者才可以省略 manifestId。当 manifest 没有声明 id 成员时,Web App Manifest(w3.org)会由 start_url 计算出 id,而解释文档指出这个计算出的 id 可以在 Chromium DevTools 的 Application 面板中读到。
Promise 兑现为一个 WebInstallResult,草案把它定义为一个为未来兼容性预留的空字典。
该方法声明的返回类型是 Promise<WebInstallResult>,所以下面每一种失败都以 Promise 拒绝的形式到达调用方:请在 try/catch 中 await 它,或者给返回的 Promise 挂一个拒绝处理器。草案的方法步骤对前三种使用规范算法的 throw 措辞,而对于返回 Promise 的操作,Web IDL 会把它转成被拒绝的 Promise;解释文档自己的步骤也把这几个名称描述为 Promise 拒绝时使用的名称。草案指出用户激活在参数校验之前就被消耗,这与 Chromium 一致,所以一个无法解析的 URL 同样会花掉这次激活。
| 拒绝 | 发生于 | 何时发生 |
|---|---|---|
InvalidStateError |
方法步骤中,任何请求开始之前 | 文档不是合格的发起文档。草案要求文档完全活跃、是某个顶级可遍历对象的活跃文档、不是 fenced frame 文档、活跃沙箱标志集合为空,且 web-app-installation 策略受控特性处于启用状态。 |
NotAllowedError |
方法步骤中,任何请求开始之前 | 文档没有瞬态激活。 |
TypeError |
方法步骤中,任何请求开始之前 | options.manifest 无法按文档 URL 解析,或 options.manifestId 无法解析为绝对 URL。 |
DataError |
请求的结果 | 结果为无效的安装数据:按草案的说法,所提供的 URL、manifest、身份断言或必需的安装元数据无效或不可用。解释文档列出的情形包括无效的 manifest URL、manifest 抓取失败、格式错误的 manifest、manifest 未声明 id 而调用方又省略了 manifestId,以及 manifestId 与计算出的 id 不匹配。 |
AbortError |
请求的结果 | 其他所有未完成的情形。草案的注释写道:“Permission denial, unsupported profiles, navigation, concurrency, user dismissal, and internal failure all map to AbortError.” |
解释文档还把 NotFoundError(文档缺失)列入 Promise 可能拒绝的名称,而草案的临时方法步骤把每一个不合格的发起文档都归到 InvalidStateError;草案自己把这套名称分类标记为尚未定论。
还有一条拒绝路径没有专属名称:草案为每个顶级可遍历对象只保留一个活跃的 Web 安装请求,因此在对话框已打开时再次调用,会以安装中止收尾。
每个示例都从 click 处理器中发起,因为草案在没有瞬态激活时会以 NotAllowedError 拒绝。字典形式还要额外经过 web-app-installation 权限。
用一个按钮安装当前页面
Section titled “用一个按钮安装当前页面”不带参数的形式以文档所链接的 manifest 为目标。在草案的“obtain web install permission”步骤中,来源为当前文档时直接返回 true 而不请求 web-app-installation 权限,于是流程直接走到浏览器的确认界面。没有 id 成员的 manifest 会由 start_url 计算出一个,但解释文档中不带参数的安装步骤在 manifest 文件未声明 id 时以 DataError 拒绝,所以请显式声明它。
const button = document.querySelector('#install');
button.addEventListener('click', async () => { try { await navigator.install(); button.hidden = true; } catch (err) { if (err.name === 'DataError') { // manifest 缺失、无法抓取,或未声明 `id`。 reportInstallBlocked('manifest'); } else if (err.name === 'AbortError') { // 被关闭、被策略拒绝,或配置不受支持。 button.textContent = 'Install'; } }});请按 err.name 分支,而不要按消息字符串:上面这些名称是草案与解释文档定义的,而消息文本并未被规定。
从目录页安装同一套件中的另一个应用
Section titled “从目录页安装同一套件中的另一个应用”字典形式指名另一个应用的 manifest。解释文档的步骤以凭据模式 "omit" 抓取该 manifest,其开放问题记录了当前的决定:manifest URL 必须与 start_url 同源,manifest 内部的相对 URL 按该 manifest URL 解析。当目标 manifest 自身未声明 id 时,请传入 manifestId。
const CATALOG = [ { name: 'Mail', manifest: 'https://suite.example/mail/manifest.json' }, { name: 'Calendar', manifest: 'https://suite.example/calendar/manifest.json' },];
async function installFromCatalog(entry) { try { await navigator.install({ manifest: entry.manifest }); return { ok: true }; } catch (err) { if (err.name === 'TypeError' || err.name === 'DataError') { return { ok: false, reason: 'catalog-entry-stale' }; } return { ok: false, reason: 'not-installed' }; // AbortError 以及其他情形 }}过期的目录条目正是 DataError 存在的场景:解释文档的开放问题把带版本号的 manifest 文件名问题留作未决,并依靠 DataError 告诉调用方某个已发布的 URL 已经失效。
检测支持并回退到 beforeinstallprompt
Section titled “检测支持并回退到 beforeinstallprompt”权限查询让页面在渲染按钮之前就知道跨源安装是否会被拒绝,而 'install' in navigator 告诉它这个方法是否存在。在方法不存在的地方,被延迟保存的 beforeinstallprompt 事件是 Chromium 仍然提供的路径,其余浏览器则各有自己的安装入口。
let deferredPrompt = null;addEventListener('beforeinstallprompt', (event) => { event.preventDefault(); deferredPrompt = event;});
async function installTarget(manifestUrl) { if (!('install' in navigator)) { if (deferredPrompt) return deferredPrompt.prompt(); // Chromium 更早的入口 return { ok: false, reason: 'use-browser-menu' }; // 这里没有脚本化安装 } const status = await navigator.permissions .query({ name: 'web-app-installation' }) .catch(() => null); // 名称不受支持:按 Permissions 规范以 TypeError 拒绝 if (status?.state === 'denied') { return { ok: false, reason: 'permission-denied' }; } return navigator.install({ manifest: manifestUrl });}BCD api.Permissions.permission_web-app-installation 记录该权限名称自 Chrome 154 起可用,需要同一个偏好开关,所以 catch 分支覆盖的是尚未注册该名称、query() 以 TypeError 拒绝的浏览器。
- Web Install(wicg.github.io)
- Web Install API explainer(github.com)
- beforeinstallprompt 事件
- 可安装性判定条件
规范
| 规范 | 状态 |
|---|---|
| Web Install: the navigator.install() API | WICG 草案 |
| Web Install: common request preconditions | WICG 草案 |
| Web Install: permission and user consent | WICG 草案 |