# Windows 上的 Firefox 任务栏 Web 应用

> Firefox 143 如何把网站固定到 Windows 任务栏、taskbartabs 组件覆盖哪些平台，以及固定窗口从配置文件继承了什么。

Windows 上的 Firefox 143 可以把网站固定到任务栏并在简化窗口中运行，Mozilla 把这个功能命名为 web apps，在自己的源码里称之为 taskbar tabs。固定操作由地址栏发起，而不是由网站发起；打开的窗口保留创建它时所用的配置文件与容器。

## 工作原理

这个功能是一个浏览器组件，在 Firefox 源码树中的文档为 [Web Apps in Firefox](https://firefox-source-docs.mozilla.org/browser/components/taskbartabs/docs/index.html)。该页面指出功能由首选项 `browser.taskbarTabs.enabled` 启用，并把入口描述为 URL 栏上的一个按钮，而不是某个 manifest 成员或页面 API。架构文档把该按钮归到它称为 page action 的模块，该模块依据页面是否为 HTTP 或 HTTPS 协议来显示或隐藏自己。

### 创建与移除固定项

组件文档给出了流程的两半。点击 URL 栏上的 "Add tab to taskbar" 按钮会创建一个新窗口，并提示用户把它固定到任务栏。点击 "Remove tab from taskbar" 会把该窗口变回普通标签页，如果它是最后一个窗口，则从任务栏取消固定。Firefox 把每个 web 应用的信息保存在用户配置文件内的 `taskbartabs/taskbartabs.json`，同一页面标注该文件仅供 Firefox 内部使用。

图标在 web 应用首次创建时选定：依次遍历来源（manifest、favicon）直到找到有效图标；已存在的 web 应用会把图标保存在文件系统上并在需要时加载。因此 manifest 与 favicon 就是网站能影响任务栏显示内容的地方。

:::note
Mozilla 不用 PWA 这个名字来称呼该功能，文档给出两个理由：这个词含义模糊，既指支持 web 应用的那些 API，也指安装它们的能力；而 Firefox web 应用在设计上仍然看起来像浏览器，而不是看起来像一个完全独立的应用程序。
:::

### 固定窗口继承了什么

固定窗口仍然是同一个浏览器。[Web Apps in Firefox](https://firefox-source-docs.mozilla.org/browser/components/taskbartabs/docs/index.html) 的 "External Interactions" 一节记录了在那里测试应用时需要注意的结果：

- Web 应用与创建它们时所用的配置文件绑定，因此 web 应用页面拥有和普通标签页相同的访问权限、控制项与附加组件。[Firefox 143.0 发行说明](https://www.firefox.com/en-US/firefox/143.0/releasenotes/)（2025 年 9 月 16 日）把这一点描述为固定后的网站运行时"不会失去对已安装附加组件的访问"。
- Web 应用与创建它们时所用的容器（源码中称为 contextual identity）绑定，通过任务栏启动会在该容器中重新打开。文档把这一点呈现为用户隔离站点的一种方式，无需为每个 web 应用准备一个新配置文件。
- Web 应用窗口不受普通浏览器会话恢复的生命周期约束。对启用了 "Open previous windows and tabs" 的用户，它们不会被恢复，同时也不会阻止会话恢复处理的那些窗口被恢复。

### 从任务栏快捷方式启动

点击任务栏快捷方式会以一条命令行启动 Firefox，[架构文档](https://firefox-source-docs.mozilla.org/browser/components/taskbartabs/docs/architecture.html) 给出的形式是：

```text
firefox -taskbar-tab <id> -new-window <start url> -profile <profile path> -container <usercontextid>
```

同一文档记录了被引用的 web 应用不存在时会发生什么：会创建一个新的，但不会使用那个 ID。当 web 应用存在时使用它的起始 URL，否则改为打开快捷方式中的起始 URL 作为回退。

## 平台可用性

组件文档把 web 应用列为在 Windows 上可用（MSIX 与非 MSIX 均可，默认启用），在 Linux 上可用（非沙箱与 Flatpak 下均可，默认关闭）。文档指出 macOS 支持尚不可用，并给出原因：macOS 假定两个不同的可见应用程序会处于两个不同的进程中，而 Firefox web 应用与浏览器其余部分处于同一配置文件和同一进程中，因此 Dock 无法区分它们。架构文档补充说 Snap 下的 Linux 不受支持。

Firefox 143.0 发行说明对已发布功能的描述比组件本身的范围更窄：它把这个功能描述为 Windows 功能，并排除了从 Microsoft Store 安装的 Firefox。想知道 Firefox 143 做了什么就看发行说明，想知道组件后来走到了哪里就看组件文档。

## 示例

网站无法自己创建固定项，所以值得写的代码是让简化窗口成为一个合理落点的代码。

### 在窗口不再是 web 应用时做出响应

架构文档把 web 应用"弹出"回原标签页的逻辑放在 `TaskbarTabsWindowManager` 中，因此一个窗口可以在页面已加载的情况下从应用界面变回标签页。依赖 [`display-mode` 媒体特性](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/display-mode) 的布局因此应该监听，而不是只读一次。

Firefox 任务栏 Web 应用报告的模式是 `minimal-ui`，而不是 `standalone`：[`TaskbarTabsWindowManager.sys.mjs`](https://searchfox.org/firefox-main/source/browser/components/taskbartabs/TaskbarTabsWindowManager.sys.mjs) 对它打开的窗口中每个标签页设置 `browser.browsingContext.displayMode = "minimal-ui";`，并在应用被弹出时设置为 `"browser"`。只检测 `standalone` 的代码因此会在它本想识别的窗口里得到浏览器界面的结论，所以要把已安装的几种模式作为一个集合来检测。

```js
// Firefox 任务栏 Web 应用报告 minimal-ui。其他已安装显示模式取决于
// manifest 请求的模式与浏览器支持情况。
const APP_MODES = ['minimal-ui', 'standalone', 'window-controls-overlay'];

function trackAppSurface(onChange) {
  if (!('matchMedia' in window)) {
    // 没有 matchMedia：渲染浏览器布局并就此停止。
    onChange('browser');
    return () => {};
  }
  const queries = APP_MODES.map((mode) => window.matchMedia(`(display-mode: ${mode})`));
  const apply = () => onChange(queries.some((query) => query.matches) ? 'app' : 'browser');
  apply();
  queries.forEach((query) => query.addEventListener('change', apply));
  return () => queries.forEach((query) => query.removeEventListener('change', apply));
}

const stop = trackAppSurface((surface) => {
  document.documentElement.dataset.surface = surface;
});
```

当这个区别对布局有影响时，读取单个查询而不是 `some()` 的结果：`minimal-ui` 是 Firefox 任务栏窗口，其他环境中的实际模式取决于 manifest 请求的模式与浏览器支持情况。保留返回的清理函数，否则在每次路由切换都重新运行一遍的单页应用会每次导航叠加一个监听器。

### 提供固定项将要使用的图标与名称

Firefox 会使用 manifest 中的名称、起始 URL、scope 与图标。没有 manifest 图标时，它还会尝试 favicon。

```json
{
  "name": "Example Reader",
  "short_name": "Reader",
  "start_url": "/app/",
  "scope": "/app/",
  "display": "standalone",
  "icons": [
    { "src": "/icons/app-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icons/app-256.png", "sizes": "256x256", "type": "image/png" },
    { "src": "/icons/app-512.png", "sizes": "512x512", "type": "image/png" }
  ]
}
```

注册表项携带的是 `name` 而不是 `short_name`：[`TaskbarTabsRegistry.sys.mjs`](https://searchfox.org/firefox-main/source/browser/components/taskbartabs/TaskbarTabsRegistry.sys.mjs) 用 `name: manifest.name ?? generateName(aUrl)` 创建它，manifest 没有名称时从 URL 推导一个。为使用 `short_name` 的平台保留它，同时把 `name` 写得短到能在图标下读完。

图标选择按 256 px 来找：[`TaskbarTabs.sys.mjs`](https://searchfox.org/firefox-main/source/browser/components/taskbartabs/TaskbarTabs.sys.mjs) 中的 `findBestManifestIcon` 丢弃 `purpose` 不包含 `any` 的图标，把其余按宽度排序，返回尺寸至少为 256 的第一个候选项，在这次查找找不到时回退到最大的候选项。同一函数把空值或 `any` 的 `sizes` 替换为 `Infinity`，因此这样的条目排在所有像素尺寸之后。在上面的 manifest 中，这次查找到达的是 256 px 的图标，所以要真的提供该尺寸的素材，而不是依赖一个很小的 favicon。

## 实测行为

控制该功能的首选项在 Firefox 自己的默认首选项文件中按平台设置，上面那些按平台的默认值正是来自这里。

:::observed
Firefox 默认首选项（firefox-main，`browser/app/profile/firefox.js`）在 `#if defined(XP_WIN)` 中包含 `pref("browser.taskbarTabs.enabled", true);`，在 `#else` 分支中包含 `pref("browser.taskbarTabs.enabled", false);`（[searchfox](https://searchfox.org/firefox-main/source/browser/app/profile/firefox.js)）。
:::

地址栏入口由 page action 模块绘制，因此读取到 `#else` 默认值的构建在改动该首选项之前没有按钮可点。这就是组件文档中 Linux "默认关闭"那一行背后的机制。

## 另请参阅

- [Firefox 上的 PWA](/zh/reference/platforms/firefox/)
- [Windows 上的 PWA](/zh/reference/platforms/windows/)
- [桌面上的 PWA](/zh/reference/platforms/desktop/)
- [display 清单成员](/zh/reference/manifest/display/)
- [Web Apps in Firefox](https://firefox-source-docs.mozilla.org/browser/components/taskbartabs/docs/index.html)（firefox-source-docs.mozilla.org）
- [Firefox 143.0 release notes](https://www.firefox.com/en-US/firefox/143.0/releasenotes/)（firefox.com）
- [@media display-mode](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/display-mode)（developer.mozilla.org）