# iOS 主屏 App 通知实测笔记

> 复现单台 iOS 18.6.2 主屏 App 中测得的权限请求、Push Manager 暴露与 notificationclick 处理。

iOS 主屏 Web App 的通知流程涉及用户授权、Push 订阅入口、Service Worker 和通知事件。需要复现 iOS 18.6.2 的行为、排查权限或点击路径，或与后续系统 build 对照时，可以按这些步骤留下可核验记录；单台设备结果不能替代通用兼容性结论。

原始实验在 2026-10-20 使用一台 iPhone 16 Pro，系统为 iOS 18.6.2（22G100）。准备一个 HTTPS App：manifest 设为 `display: standalone`，注册 Service Worker，提供权限按钮、IndexedDB 事件日志和创建本地通知的入口。开始前先把 App 添加到主屏幕。WebKit 记载，iOS 与 iPadOS 16.4 起为主屏 Web App 提供 Web Push，并要求由直接用户互动触发通知权限请求（[Web Push for Web Apps on iOS and iPadOS](https://webkit.org/blog/13878/web-push-for-web-apps-on-ios-and-ipados/)，webkit.org）。这次实验没有发送真实的远程 Push。测量范围是权限、订阅对象、本地显示的通知、通知点按和 Worker 状态。[真机研究](/zh/news/testing-home-screen-web-apps-on-ios-18-6-2/)与[公开数据包](/zh/research/2026-10-ios18-home-screen-web-apps/data/)保存了完整记录。

## 支持范围

WebKit 记载 iOS 与 iPadOS 16.4 起为主屏 Web App 提供 Web Push。下文行为只对应一台 iOS 18.6.2 设备上安装后的主屏 App，不能用于判断 Safari 标签页、其他系统版本或远程 Push 投递；这次实验没有测试远程投递。

## 检查主屏通知入口

从主屏图标打开 App，检查流程会用到的接口；如果处在 Safari 标签页或接口缺失，则进入明确的回退分支：

```js
function notificationSurface() {
  const installed = matchMedia('(display-mode: standalone)').matches
    || navigator.standalone === true;
  const supported = 'Notification' in window
    && 'serviceWorker' in navigator
    && 'PushManager' in window;

  if (!installed || !supported) {
    return { enabled: false, action: 'Open the Home Screen app' };
  }
  return { enabled: true, action: 'Show permission control' };
}
```

回退分支属于功能流程。原始 build 的 Safari 标签页没有 `Notification` 和 `PushManager` 构造器，安装后的 App 则有。iOS 18.4 还加入 Declarative Web Push 的 `window.pushManager`；实验在两个上下文都读到这个属性，所以只检查它不能判断页面是否处于主屏 App。

## 从点按请求权限

使用从未请求过权限的 App。把 `Notification.requestPermission()` 直接放进按钮处理器，调用前记录 `performance.now()`，Promise 结束后记录结果。不要在页面加载时触发。WebKit 文档明确要求直接用户互动。

```js
permissionButton.addEventListener('click', async () => {
  const started = performance.now();
  permissionButton.disabled = true;
  const permission = await Notification.requestPermission();
  const elapsed = Math.round(performance.now() - started);
  eventLog.textContent = JSON.stringify({ permission, elapsed });
  if (permission !== 'granted') {
    permissionButton.disabled = false;
    fallbackPanel.hidden = false;
    return;
  }
  subscribeButton.hidden = false;
});
```

归档运行中，操作者点按钮后出现系统提示，Promise 一直等待用户作答。其中一次授权记录在 2068 ms 后结束。这个数值只描述当次互动，不能用作应用超时值（[通知权限提示](/zh/lab/behaviour/notification-permission-prompt/)）。

结果为 `denied` 或 `default` 时，保留 App 内回退界面，不要立刻再次请求。如果测试中 Promise 一直不结束，先保存录屏、请求前权限状态和系统 build，再改代码。

## 对照两个 Push Manager

iOS 与 iPadOS 18.4 起，WebKit 通过 `window.pushManager` 暴露 Declarative Web Push。WebKit 说明，作用域位于站点根路径的 Service Worker 会与 window 对象共享订阅；18.6.2 实验的 Worker 位于子路径，因此不符合共享条件（[Meet Declarative Web Push](https://webkit.org/blog/16535/meet-declarative-web-push/)，webkit.org）。

权限获准后，只读取两个入口，不要新建第二份订阅：

```js
const registration = await navigator.serviceWorker.ready;
const workerSubscription = await registration.pushManager.getSubscription();
const windowSubscription = window.pushManager
  ? await window.pushManager.getSubscription()
  : null;

console.table({
  worker: workerSubscription?.endpoint ? 'present' : 'missing',
  window: windowSubscription?.endpoint ? 'present' : 'missing',
  permission: Notification.permission,
});
```

原实验主屏 App 的 Service Worker 订阅端点主机为 `web.push.apple.com`，`window.pushManager.getSubscription()` 返回 `null`。一次授权令两个 Manager 的权限状态检查都返回 `granted`。测试 Worker 使用演示应用或设备实验室的非根作用域，不是 `/`，因此结果符合 WebKit 的根作用域条件（见[订阅作用域记录](/zh/lab/behaviour/declarative-web-push-subscription-scope/)）。实验没有测试根作用域 Worker，也没有投递远程 Push。

## 分别记录两种点击处理器

用 `addEventListener` 注册事件，同时给 `self.onnotificationclick` 赋值，把它们作为两个独立探针。聚焦客户端前，先把每次调用写入 IndexedDB，方便页面再次启动后显示结果。

```js
// sw.js
self.addEventListener('notificationclick', (event) => {
  event.notification.close();
  event.waitUntil(recordClick('listener').then(() => focusClient()));
});

self.onnotificationclick = (event) => {
  event.notification.close();
  event.waitUntil(recordClick('property').then(() => focusClient()));
};

async function focusClient() {
  const clients = await self.clients.matchAll({ type: 'window' });
  return clients[0]?.focus();
}
```

分别点横幅和通知中心中的通知，然后在重新启动 App、从多任务界面划走 App 后重复。研究记录的六次点按中，两个处理器每次都运行；`clients.matchAll()` 找到已打开窗口，`focus()` 成功。`clients.openWindow()` 分支没有执行（[主屏 App 的 notificationclick](/zh/lab/behaviour/notificationclick-in-home-screen-apps/)）。

真机结果与 BCD 描述的上下文不同。BCD 把 Safari on iOS 的 `api.ServiceWorkerGlobalScope.notificationclick_event` 记为不支持。Issue #22959 说明采集器会在 Service Worker 中检查处理器属性；18.6.2 实验发现，同一个 Worker 由 Safari 标签页控制时，`'onnotificationclick' in self` 为 false，由主屏 App 控制时为 true（[Worker 处理器暴露记录](/zh/lab/behaviour/worker-event-handler-exposure-tab-vs-app/)；[BCD issue #22959](https://github.com/mdn/browser-compat-data/issues/22959)，github.com）。

## 保留有效的失败记录

若点按没有进入处理器，先确认通知属于已安装 App、包含处理器的 Worker 已激活，并验证页面重新启动后仍能读取事件日志。还要记下当时是否已有 App 窗口。WebKit 的错误报告包含“没有页面存活时不触发”的情况；本次运行找到了现有客户端，没有覆盖 `openWindow()`。

日志缺失也可能来自写入失败，不能直接证明事件没有触发。原研究运行期间曾发现观察器错误，并以新笔记保留修正过程。失败记录应包括通知来源、处理器注册方式、App 生命周期状态、Worker 脚本 URL、系统 build 与 IndexedDB 原始日志。

:::observed
OpenPWA device test, 2026-10-20, iPhone 16 Pro, iOS 18.6.2 (22G100)：点按触发的权限请求显示系统提示，并在用户作答后结束；主屏 App 中两种 `notificationclick` 注册方式在六次记录点按中均运行；同一 Worker 在安装上下文暴露 `onnotificationclick`，在 Safari 标签页中不暴露。记录见[研究数据包](https://openpwa.net/research/2026-10-ios18-home-screen-web-apps/data/)的 N35 至 N40、N53 至 N59（openpwa.net）。
:::

## 另请参阅

- [Web Push for Web Apps on iOS and iPadOS](https://webkit.org/blog/13878/web-push-for-web-apps-on-ios-and-ipados/)（webkit.org）
- [Meet Declarative Web Push](https://webkit.org/blog/16535/meet-declarative-web-push/)（webkit.org）
- [iOS 18.6.2 主屏 Web App 真机研究](/zh/news/testing-home-screen-web-apps-on-ios-18-6-2/)
- [iOS 18.6.2 通知权限提示](/zh/lab/behaviour/notification-permission-prompt/)
- [iOS 主屏 App 的 notificationclick](/zh/lab/behaviour/notificationclick-in-home-screen-apps/)
- [iOS 与 Safari Web Push](/zh/reference/notifications/ios-safari-push/)