构建跨浏览器扩展

浏览器扩展 API 的引入为浏览器扩展的开发创造了一个统一的环境。但是,在使用扩展 API 的浏览器(主要包括 Chrome、Edge、Firefox、Opera 和 Safari)之间,API 实现和覆盖范围存在差异。

最大限度地扩大浏览器扩展的覆盖范围意味着至少要为两个浏览器(甚至更多)开发它。本文探讨了创建跨浏览器扩展时面临的主要挑战,并提出了解决这些挑战的方法。

注意:主要浏览器已采用清单 V3。此清单版本提供了浏览器扩展环境之间更好的兼容性,例如用于处理异步事件的 Promise。除了本指南中的信息外,请参阅清单 V3 迁移指南,了解 FirefoxChrome

跨平台扩展编码障碍

API 命名空间

主要浏览器中使用了两个 API 命名空间

  • browser.*,Firefox 和 Safari 使用的扩展 API 的提议标准。
  • chrome.*,Chrome、Opera 和 Edge 使用。

Firefox 还支持 chrome.* 命名空间用于与 Chrome 兼容的 API,主要用于辅助 移植。但是,建议使用 browser.* 命名空间。除了是提议的标准外,browser.* 还使用 Promise,这是一种用于处理异步事件的现代且便捷的机制。

只有在最简单的扩展中,命名空间才可能是唯一需要解决的跨平台问题。因此,单独解决此问题很少(如果有的话)会有帮助。最佳方法是结合异步事件处理一起解决。

API 异步事件处理

随着清单 V3 的引入,所有主要浏览器都采用了从异步方法返回Promise的标准。Firefox 和 Safari 完全支持所有异步 API 上的 Promise。从 Chrome 121 开始,所有异步扩展 API 都支持 Promise,除非另有说明。devtools API 是唯一一个没有 Promise 支持的 API 命名空间(Chromium 错误 1510416)。

在清单 V2 中,Firefox 和 Safari 支持异步方法的 Promise。同时,Chrome 方法调用回调函数。为了兼容性,所有主要浏览器在所有清单版本中都支持回调函数。有关详细信息,请参阅 回调函数和 Promise

预期某些扩展 API 事件的处理程序将通过Promise或回调函数异步响应。例如,runtime.onMessage事件的处理程序可以使用使用Promise发送异步响应或使用回调函数。事件处理程序返回值中的Promise在 Firefox 和 Safari 中受支持,但在 Chrome 中尚不受支持。

Firefox 还支持支持chrome.*命名空间的 API 的回调函数。但是,建议使用 Promise。Promise 极大地简化了异步事件处理,尤其是在需要将事件链接在一起的情况下。这意味着使用 polyfill 或类似方法,以便您的扩展在 Firefox 和 Safari 中使用browser.*命名空间,在 Chrome、Opera 和 Edge 中使用chrome.*

注意:如果您不熟悉这两种方法之间的区别,请查看 了解异步 JavaScript:回调函数、Promise 和 Async/Await或 MDN 使用 Promise 页面。

WebExtension 浏览器 API Polyfill

那么,如何轻松利用 Promise 呢?解决方案是使用 Promise 为 Firefox 编写代码,并使用 WebExtension 浏览器 API Polyfill 解决 Chrome、Opera 和 Edge 的问题。

此 polyfill 解决了 Firefox、Chrome、Opera 和 Edge 之间的 API 命名空间和异步事件处理问题。

要使用 polyfill,请使用 npm 将其安装到您的开发环境中,或直接从 GitHub 发行版 下载。

然后,在以下位置引用browser-polyfill.js

  • manifest.json 以使其可供后台和内容脚本使用。
  • HTML 文档,例如browserAction弹出窗口或选项卡页面。
  • tabs.executeScript加载的动态注入内容脚本中的executeScript调用,在未使用manifest.json中的content_scripts声明加载的情况下。

例如,此manifest.json代码使 polyfill 可供后台脚本使用

json
{
  // …
  "background": {
    "scripts": ["browser-polyfill.js", "background.js"]
  }
}

您的目标是确保 polyfill 在您的扩展中执行,然后再执行任何其他期望browser.*API 命名空间的脚本。

注意:有关使用模块打包器与 polyfill 的更多详细信息和信息,请参阅 GitHub 上的项目自述文件。

还有其他 polyfill 选项。但是,在撰写本文时,其他选项均未提供 WebExtension 浏览器 API Polyfill 的覆盖范围。因此,如果您未将 Firefox 作为首选,则可以选择接受备用 polyfill 的限制、移植到 Firefox 并添加跨浏览器支持,或开发您自己的 polyfill。

API 函数覆盖范围

每个主要浏览器提供的 API 函数差异分为三大类

  1. 缺乏对整个函数的支持。例如,在撰写本文时,Edge 不支持 browserSettings 函数。
  2. 对函数内功能的支持存在差异。例如,在撰写本文时,Firefox 不支持通知函数方法 notifications.onButtonClicked,而 Firefox 是唯一支持 notifications.onShown 的浏览器。
  3. 专有函数,支持特定于浏览器的功能。例如,在撰写本文时,容器是 Firefox 的一项特定功能,由 contextualIdentities 函数支持。

有关主要浏览器、Firefox for Android 和 Safari on iOS 对扩展 API 支持的详细信息,请参阅 Mozilla 开发者网络 JavaScript API 的浏览器支持 页面。浏览器兼容性信息也包含在 Mozilla 开发者网络 JavaScript API 参考页面中每个函数及其方法、类型和事件中。

处理 API 差异

解决 API 差异的一种简单方法是将扩展中使用的函数限制为在目标浏览器范围内提供相同功能的函数。在实践中,这种方法对于大多数扩展来说可能过于严格。

相反,在 API 之间存在差异的情况下,您应该提供替代实现或回退功能。(请记住:您可能还需要这样做才能适应相同浏览器不同版本之间 API 支持的差异。)

建议使用运行时检查函数功能的可用性来实现替代或回退功能。执行运行时检查的好处是,当某个函数可用时,您无需更新和重新分发扩展即可利用该函数。

以下代码使您可以执行运行时检查

js
if (typeof fn === "function") {
  // safe to use the function
}

内容脚本执行上下文

内容脚本可以访问和修改页面的 DOM,就像页面脚本一样。它们还可以查看页面脚本对 DOM 所做的任何更改。但是,内容脚本会获得 DOM 的“干净”视图。

Firefox 和 Chrome 使用根本不同的方法来处理此行为:在 Firefox 中,它被称为 X 射线视觉,而在 Chrome 中则使用隔离的世界。有关更多详细信息,请参阅内容脚本概念文章的 内容脚本环境 部分。

但是,Firefox 提供了一些 API,使内容脚本能够访问页面脚本创建的 JavaScript 对象,并将它们的 JavaScript 对象公开给页面脚本。有关详细信息,请参阅 与页面脚本共享对象

内容安全策略 (CSP) 的内容脚本之间也存在差异。

后台页面和扩展服务工作线程

作为其 Manifest V3 实现的一部分,Chrome 使用扩展服务工作线程替换了后台页面。Firefox 保留了后台页面的使用,而 Safari 支持后台页面和服务工作线程。

有关更多信息,请参阅 "background" 清单键页面上的 浏览器支持 部分。这包括一个关于如何实现跨浏览器脚本的简单示例。

清单键

主要浏览器支持的 manifest.json 文件键的差异大致分为三类

  1. 扩展信息属性。例如,在撰写本文时,Firefox 和 Opera 包含 developer 键,用于提供有关扩展开发人员的详细信息。
  2. 扩展功能。例如,在撰写本文时,Chrome 不支持 browser_specific_settings 键。
  3. 键的可选性。在撰写本文时,通常,只有 "manifest_version""version""name" 是必填键。

浏览器兼容性信息包含在 Mozilla 开发者网络 manifest.json 键参考页面中的每个键中。

由于 manifest.json 文件变化不大——除了版本号可能在各个浏览器之间有所不同——因此为每个浏览器创建和编辑静态版本通常是最简单的方法。

扩展打包

通过浏览器扩展商店分发扩展的打包过程相对简单。Firefox、Chrome、Edge 和 Opera 都使用简单的 zip 格式,该格式要求 manifest.json 文件位于 zip 包的根目录。Safari 要求扩展以类似于应用程序的方式进行打包。

有关打包的详细信息,请参阅各个扩展开发者门户网站上的指南。

扩展发布

每个主要浏览器都维护浏览器扩展商店。每个商店还会审查您的扩展,以检查是否存在安全漏洞。

因此,您需要分别处理为每个商店添加和更新扩展的过程。在某些情况下,您可以使用实用程序上传扩展。

此表总结了每个商店的方法和功能

浏览器 注册费 上传实用程序 出版前审查流程 帐户双因素身份验证

Chrome

自动,不到一小时

Edge

未提供 SLA

Firefox

web-ext

自动,几秒钟。

发布后会对扩展进行人工审查,如果发现需要修复的问题,可能会导致扩展被暂停。

Opera

手动,未提供 SLA

Safari

是,根据 Apple 的说法,平均而言,24 小时内审查了 50% 的应用程序,48 小时内审查了 90% 以上的应用程序。

其他注意事项

版本编号

Firefox、Chrome 和 Edge 商店要求每个上传的版本都有不同的版本号。这意味着如果您在发布过程中遇到问题,则无法恢复到以前的版本号。

结论

在进行跨平台扩展开发时,可以通过以 Firefox 为目标并使用 WebExtension 浏览器 API Polyfill 来解决扩展 API 实现之间的差异。

您的跨平台工作的大部分内容可能都集中在处理主要浏览器支持的 API 功能之间的差异。您可能还需要考虑内容脚本和后台脚本实现之间的差异。创建 manifest.json 文件应该相对简单,您可以手动完成。然后,您需要考虑提交到每个扩展商店的过程中的差异。

按照本文中的建议,您应该能够创建一个在所有四个主要浏览器上都能很好运行的扩展,从而能够为更多人提供扩展功能。