Chrome 不兼容性

WebExtension API 旨在提供所有主流浏览器的兼容性,因此扩展程序应该能在任何浏览器上以最小的改动运行。

然而,Chrome(以及基于 Chromium 的浏览器)、Firefox 和 Safari 之间存在显著差异。特别是:

  • WebExtension API 在不同浏览器上的支持程度不同。详情请参阅JavaScript API 浏览器支持

  • manifest.json 键在不同浏览器上的支持程度不同。详情请参阅 manifest.json 页面上的“浏览器兼容性”部分

  • 扩展 API 命名空间

    • 在 Firefox 和 Safari 中:通过 browser 命名空间访问扩展 API。为了与 Chrome 兼容,也支持 chrome 命名空间。
    • 在 Chrome 中:通过 chrome 命名空间访问扩展 API。(参见 Chrome bug 798169
  • 异步 API

    • 在 Firefox 和 Safari 中:异步 API 使用 Promise 实现。
    • 在 Chrome 中:在 Manifest V2 中,异步 API 使用回调实现。在 Manifest V3 中,大多数适当的方法都支持 Promise。(参见 Chrome bug 328932)Manifest V3 中支持回调是为了向后兼容。

本页的其余部分将详细介绍这些和其他不兼容性。

JavaScript API

chrome.* 和 browser.* 命名空间

  • 在 Firefox 和 Safari 中:通过 browser 命名空间访问 API。

    js
    browser.browserAction.setIcon({ path: "path/to/icon.png" });
    
  • 在 Chrome 中:通过 chrome 命名空间访问 API。

    js
    chrome.browserAction.setIcon({ path: "path/to/icon.png" });
    

回调和 Promise

  • 在 Firefox 和 Safari(所有版本)以及 Chrome(从 Manifest V3 开始)中:异步 API 使用 Promise 返回值。

    js
    function logCookie(c) {
      console.log(c);
    }
    
    function logError(e) {
      console.error(e);
    }
    
    let setCookie = browser.cookies.set({
      url: "https://mdn.org.cn/",
    });
    setCookie.then(logCookie, logError);
    
  • 在 Chrome 中:在 Manifest V2 中,异步 API 使用回调返回值,并使用 runtime.lastError 传递错误。在 Manifest V3 中,支持回调以实现向后兼容性,同时在大多数适当的方法上支持 Promise

    js
    function logCookie(c) {
      if (chrome.runtime.lastError) {
        console.error(chrome.runtime.lastError);
      } else {
        console.log(c);
      }
    }
    
    chrome.cookies.set({ url: "https://mdn.org.cn/" }, logCookie);
    

Firefox 同时支持 chrome 和 browser 命名空间

作为移植辅助,WebExtensions 的 Firefox 实现支持使用回调的 chrome 和使用 Promise 的 browser。这意味着许多 Chrome 扩展程序在 Firefox 中无需更改即可工作。

注意:Firefox 和 Safari 支持 browser 命名空间。在 Chrome bug 798169 解决之前,Chrome 不提供 browser 命名空间。

如果您选择编写使用 browser 和 Promise 的扩展程序,Firefox 提供了一个 polyfill,应该能让它在 Chrome 中运行:https://github.com/mozilla/webextension-polyfill

部分支持的 API

JavaScript API 浏览器支持页面包含所有在 Firefox 中有任何支持的 API 的兼容性表格。如果 API 方法、属性、类型或事件的支持存在注意事项,这些表格中会用星号 "*" 表示。选择星号会展开表格以显示解释注意事项的注释。

这些表格是根据存储在 GitHub 中的 JSON 文件中的兼容性数据生成的。

本节的其余部分描述了在构建跨浏览器扩展程序时可能需要考虑的主要兼容性问题。此外,请记住查看浏览器兼容性表格,因为它们可能包含额外的兼容性信息。

Notifications API

对于 notifications.create(),当 type 为 "basic" 时

  • 在 Firefox 中iconUrl 是可选的。
  • 在 Chrome 中iconUrl 是必需的。

当用户点击通知时

  • 在 Firefox 中:通知会立即清除。
  • 在 Chrome 中:情况并非如此。

如果您在快速连续调用 notifications.create() 多次

  • 在 Firefox 中:通知可能不会显示。在 notifications.create() 回调函数中等待后续调用不足以防止这种情况。

代理 API

Firefox 和 Chrome 都包含代理 API。然而,这两个 API 的设计是不兼容的。

Firefox 和 Chrome 提供了不兼容的 API 来处理侧边栏

  • 在 Firefox(和 Opera)中:侧边栏使用 sidebar_action manifest 键指定,并使用 sidebarAction API 进行操作。
  • 在 Chrome 中:可以使用 side_panel manifest 键指定初始侧边栏。然后,sidePanel API 可以操作面板。

Tabs API

使用 tabs.executeScript()tabs.insertCSS()

  • 在 Firefox 中:传入的相对 URL 相对于当前页面 URL 解析。
  • 在 Chrome 中:相对 URL 相对于扩展程序的基 URL 解析。

要实现跨浏览器工作,您可以将路径指定为绝对 URL,从扩展程序的根目录开始,如下所示:

/path/to/script.js

调用 tabs.remove()

  • 在 Firefox 中tabs.remove() Promise 在 beforeunload 事件之后完成。
  • 在 Chrome 中:回调不等待 beforeunload

WebRequest API

  • 在 Firefox 中

    • 请求只能在其原始 URL 使用 http:https: 方案时进行重定向。

    • activeTab 权限不允许拦截当前选项卡中的网络请求。(参见 bug 1617479

    • 系统请求(例如,扩展程序升级或搜索栏建议)不会触发事件。

    • 如果扩展程序想要将公共(例如,HTTPS)URL 重定向到扩展程序页面,扩展程序的 manifest.json 文件必须包含一个 web_accessible_resources 键,其中包含扩展程序页面的 URL。

      注意: 任何网站都可以链接或重定向到该 URL,扩展程序应将任何输入(例如 POST 数据)视为来自不受信任的来源,就像普通网页一样。

    • 一些 browser.webRequest.* API 允许返回异步解析 webRequest.BlockingResponse 的 Promise。

  • 在 Chrome 中:只有 webRequest.onAuthRequired 通过提供 'asyncBlocking' 来支持异步 webRequest.BlockingResponse,通过回调而不是 Promise。

Windows API

  • 在 Firefox 中:windows API 的 onFocusChanged 在焦点更改时会触发多次。

不支持的 API

DeclarativeContent API

其他不兼容性

CSS 中的 URL

  • 在 Firefox 中:注入的 CSS 文件中的 URL 相对于 CSS 文件解析。
  • 在 Chrome 中:注入的 CSS 文件中的 URL 相对于 它们被注入的页面解析。

后台页面中对话框的支持

web_accessible_resources

  • 在 Firefox 中:资源被分配一个随机的 UUID,该 UUID 在每个 Firefox 实例中都会更改:moz-extension://«random-UUID»/«path»。这种随机性可能会阻止您执行某些操作,例如将扩展程序的 URL 添加到其他域的 CSP 策略中。
  • 在 Chrome 中:当资源列在 web_accessible_resources 中时,它可以通过 chrome-extension://«your-extension-id»/«path» 访问。扩展程序 ID 对于扩展程序是固定的。

Manifest "key" 属性

  • 在 Firefox 中:由于 Firefox 对 web_accessible_resources 使用随机 UUID,因此不支持此属性。Firefox 扩展程序可以通过 browser_specific_settings.gecko.id manifest 键(参见 browser_specific_settings.gecko)固定其扩展程序 ID。
  • 在 Chrome 中:使用未打包的扩展程序时,manifest 可能包含一个 "key" 属性,以在不同机器上固定扩展程序 ID。这主要在使用 web_accessible_resources 时有用。

内容脚本 HTTP(S) 请求

  • 在 Firefox 中:当内容脚本发出 HTTP(S) 请求时,您必须提供绝对 URL。
  • 在 Chrome 中:当内容脚本向相对 URL(例如 /api)发出请求(例如,使用 fetch())时,它会发送到 https://example.com/api

内容脚本环境

  • 在 Firefox 中:内容脚本环境的全局作用域不严格等于 windowFirefox bug 1208775)。更具体地说,全局作用域(globalThis)像往常一样由标准 JavaScript 特性组成,并且 window 作为全局作用域的原型。大多数 DOM API 都通过 window 从页面继承,通过 Xray vision 来保护内容脚本不受网页修改的影响。内容脚本可能会遇到来自其全局作用域的 JavaScript 对象或来自网页的 Xray 包装版本。
  • 在 Chrome 中:全局作用域是 window,并且可用的 DOM API 通常独立于网页(除了共享底层 DOM)。内容脚本不能直接访问网页中的 JavaScript 对象。

内容脚本页面事件处理程序

  • 在 Firefox 中:每个世界不维护单独的事件处理程序。这意味着最近请求 element.onclick = xxx 的内容脚本会覆盖页面或其他扩展程序的事件处理程序。
  • 在 Chrome 中:每个世界维护单独的事件处理程序,因此 Chrome 为页面和每个请求的扩展程序维护事件处理程序。

要解决这种不一致性,请使用 addEventListener() 注册事件监听器。有关更多信息,请参阅 Firefox bug 1965975

从内容脚本在网页中执行代码

  • 在 Firefox 中:eval 在内容脚本的上下文中运行代码,而 window.eval 在页面的上下文中运行代码。请参阅在内容脚本中使用 eval
  • 在 Chrome 中:evalwindow.eval 始终在内容脚本的上下文中运行代码,而不是在页面的上下文中。

在内容脚本之间共享变量

  • 在 Firefox 中:您无法通过在一个脚本中将变量分配给 this.{variableName},然后在另一个脚本中尝试使用 window.{variableName} 访问它们来在内容脚本之间共享变量。这是 Firefox 沙盒环境造成的限制。此限制可能会被移除;请参阅 Firefox bug 1208775

导航期间内容脚本的生命周期

  • 在 Firefox 中:用户导航离开后,内容脚本仍然注入在网页中。但是,窗口对象属性会被销毁。例如,如果内容脚本设置了 window.prop1 = "prop",然后用户导航离开并返回到该页面,window.prop1 将变为 undefined。此问题在 Firefox bug 1525400 中跟踪。

    要模拟 Chrome 的行为,请监听 pageshowpagehide 事件。然后模拟内容脚本的注入或销毁。

  • 在 Chrome 中:当用户导航离开网页时,内容脚本会被销毁。如果用户点击后退按钮通过历史记录返回到该页面,内容脚本会再次注入到网页中。

“每标签页”缩放行为

  • 在 Firefox 中:缩放级别在页面加载和标签页内的导航中保持不变。
  • 在 Chrome 中:缩放更改在导航时重置;导航标签页始终以其每来源缩放因子加载页面。

参见 tabs.ZoomSettingsScope

manifest.json 键

manifest.json 页面包含一个描述 manifest.json 键的浏览器支持的表格。如果给定键的支持存在注意事项,表格中会用星号 "*" 表示。选择星号会展开表格以显示解释注意事项的注释。

这些表格是根据存储在 GitHub 中的 JSON 文件中的兼容性数据生成的。

原生消息传递

基于连接的消息传递参数

在 Linux 和 Mac 上:Chrome 将一个参数传递给原生应用程序,该参数是启动它的扩展程序的来源,形式为 chrome-extension://«extensionID/»(需要尾部斜杠)。这使得应用程序能够识别扩展程序。

在 Windows 上:Chrome 传递两个参数

  1. 扩展程序的来源
  2. 启动应用程序的 Chrome 原生窗口的句柄

allowed_extensions

  • 在 Firefox 中:manifest 键名为 allowed_extensions
  • 在 Chrome 中:manifest 键名为 allowed_origins

应用程序 manifest 位置

  • 在 Chrome 中:应用程序 manifest 预期在不同的位置。请参阅 Chrome 文档中的原生消息主机位置

应用程序持久性

  • 在 Firefox 中:当原生消息连接关闭时,如果子进程没有分离,Firefox 会终止它们。在 Windows 上,浏览器将原生应用程序的进程放入 作业对象 并终止该作业。假设原生应用程序启动了其他进程并希望它们在原生应用程序终止后仍然保持打开。在这种情况下,原生应用程序必须使用 CreateProcess 而不是 ShellExecute,并使用 CREATE_BREAKAWAY_FROM_JOB 标志启动附加进程。

数据克隆算法

一些扩展 API 允许扩展程序将数据从扩展程序的一部分发送到另一部分,例如 runtime.sendMessage()tabs.sendMessage()runtime.onMessageruntime.portpostMessage() 方法和 tabs.executeScript()

结构化克隆算法支持比 JSON 序列化算法更多的类型。一个值得注意的例外是带有 toJSON 方法的 (DOM) 对象。DOM 对象默认不可克隆也不可 JSON 序列化,但通过 toJSON() 方法,它们可以 JSON 序列化(但仍无法使用结构化克隆算法进行克隆)。不可结构化克隆但可 JSON 序列化的对象示例包括 URLPerformanceEntry 的实例。

依赖 JSON 序列化算法的 toJSON() 方法的扩展程序可以使用 JSON.stringify() 后跟 JSON.parse() 来确保消息可以交换,因为解析后的 JSON 值总是可以结构化克隆的。