从后台页面迁移到 Service Worker

自推出以来,后台页面(Background pages)一直是 Chrome 扩展平台的基本组成部分。简而言之,背景页面为扩展作者提供了一个独立于任何其他窗口或选项卡的环境。这允许扩展观察事件并采取行动以响应事件。

在 Manifest V3 中,Chrome 扩展平台从后台页面迁移到 Service Worker。正如 Service Workers: an Introduction 中所述,“Service Workers 是一个脚本,您的浏览器在后台运行,与网页分开,为不需要网页或用户交互的功能打开了大门。”这项技术可以在开放的网络上实现类似原生的体验,例如推送通知、丰富的离线支持、后台同步和“添加到主屏幕”。Service Worker 部分受到 Chrome 扩展中的背景页面的启发,但他们通过针对网络规模进行调整来迭代和改进此模型。

在迁移到这个新的背景环境时,您需要牢记两个主要事项。首先,服务工作者在不使用时终止,需要时重新启动(类似于事件页面)。其次,服务工作者无权访问 DOM。我们将分别在下面的 Thinking with EventsWorking with Workers 部分探讨如何适应这些挑战。

# Update your manifest

扩展在“background”字段下的清单(manifest)中注册其后台服务工作者。此字段使用“service_worker”键,该键指定单个 JavaScript 文件。在 Manifest V2 中,这个字段被称为“scripts”并且允许多个脚本。

{
  "name": "Awesome Test Extension",
  ...
  "background": {
    "service_worker": "background.js"
  },
  ...
}

在使用 Service Worker 参考页面管理事件上了解更多信息。

# 用事件思考(Thinking with events)

与事件页面一样,服务工作者是一个特殊的执行环境,它开始处理他们感兴趣的事件,并在不再需要时终止。以下部分提供了在短暂的事件执行上下文中编写代码的建议。

Manifest V2 页面迁移到事件驱动的后台脚本中涵盖了其中的几个概念Migrate to Event Driven Background Scripts

# Top-level event listeners(顶级事件监听器)

为了让 Chrome 成功地将事件分派给适当的侦听器,扩展程序必须在事件循环的第一轮注册侦听器。实现此目的最直接的方法是将事件注册移至 Service Worker 脚本的顶层。

下面的代码片段显示了现有扩展如何在持久性后台页面中初始化其浏览器操作侦听器。

// background.js
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
  chrome.action.setBadgeText({ text: badgeText });

  // 监听器被异步注册 
  // 这不能保证在 MV3/服务工作者中工作!不要这样做!
  chrome.action.onClicked.addListener(handleActionClick);
});

虽然这种方法适用于持久的后台页面,但由于 Storage APIs 的异步性质,它不能保证在 Service Worker 中工作。当 Service Worker 终止时,与之关联的事件侦听器也会终止。并且由于事件是在 service worker 启动时分派的,异步注册事件会导致它们被删除,因为在它第一次启动时没有注册监听器。

要解决此问题,请将事件侦听器注册移至脚本的顶层。这确保 Chrome 能够立即找到并调用您的操作的点击处理程序,即使您的扩展程序尚未完成其异步启动逻辑的执行。

// background.js
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
  chrome.action.setBadgeText({ text: badgeText });
});

// Listener is registered on startup
chrome.action.onClicked.addListener(handleActionClick);

Manifest V3 将 chrome.browserActionchrome.pageAction 整合到一个 chrome.action API 中。

# Persisting state with storage APIs(使用存储 API 保持状态)

采用 Service Worker 时要习惯的主要事情之一是它们是短暂的执行环境。在更实际的情况下,扩展的 service worker 将在用户的浏览器会话中反复启动、执行一些工作和终止。这对习惯于长期存在的后台页面的扩展开发人员提出了挑战,因为应用程序数据在全局变量中不是立即可用的。

以下 Manifest V2 示例从内容脚本接收名称并将其保留以备后用:

// background.js

// 不要这样做! Service Worker 将在你的生命周期内被创建和销毁 
// 扩展,这个变量将被重置。
let savedName = undefined;

chrome.runtime.onMessage.addListener(({ type, name }) => {
  if (type === "set-name") {
    savedName = name;
  }
});

chrome.browserAction.onClicked.addListener((tab) => {
  chrome.tabs.sendMessage(tab.id, { name: savedName });
});

如果我们将此代码直接移植到 MV3,需要服务工作者,则代码可能会在设置名称和用户单击浏览器操作之间终止。如果发生这种情况,集合名称将丢失——并且savedName 将再次未定义。

我们可以通过将存储 API(Storage APIs) 作为我们的真实来源来修复这个错误:

// background.js
chrome.runtime.onMessage.addListener(({ type, name }) => {
  if (type === "set-name") {
    chrome.storage.local.set({ name });
  }
});

chrome.action.onClicked.addListener((tab) => {
  chrome.storage.local.get(["name"], ({ name }) => {
    chrome.tabs.sendMessage(tab.id, { name });
  });
});

# 从定时器到闹钟(Moving from timers to alarms)

Web 开发人员使用 setTimeoutsetInterval 方法执行延迟或定期操作是很常见的。但是,这些 API 在 Service Worker 中可能会失败,因为调度程序会在 Service Worker 终止时取消计时器。

// background.js

// This worked in MV2.
const TIMEOUT = 3 * 60 * 1000; // 3 minutes in milliseconds
setTimeout(() => {
  chrome.action.setIcon({
    path: getRandomIconPath(),
  });
}, TIMEOUT);

相反,我们可以使用警报 API(Alarms API)。与其他侦听器一样,警报侦听器应在脚本的顶层注册。

// background.js
chrome.alarms.create({ delayInMinutes: 3 });

chrome.alarms.onAlarm.addListener(() => {
  chrome.action.setIcon({
    path: getRandomIconPath(),
  });
});

# Working with workers(与工人一起工作)

Service workers 是一种特殊的 web worker,它与大多数 Web 开发人员习惯使用的网页截然不同。在典型的网页(或扩展后台页面)上,JavaScript 的全局执行上下文是 Window 类型的。该对象公开了 Web 开发人员习惯使用的功能:windowelementIndexedDBcookielocalStorage 等。

Service Worker 的全局范围明显受到限制,并且没有许多这些功能。最值得注意的是,服务工作者无权访问 DOM。 Worker 不再提供 XMLHttpRequest,而是支持更现代的 fetch()

以下部分涵盖了受转向 Service Workers 影响的一些主要用例以及有关如何适应的建议。

# Parsing and traversing with XML/HTML(使用 XML/HTML 解析和遍历)

由于 Service Worker 无权访问 DOM,因此扩展的 Service Worker 无法访问 DOMParser API 或创建 <iframe> 来解析和遍历文档。扩展开发人员有两种方法可以解决此限制:创建新选项卡或使用库。您选择哪个取决于您的用例。

jsdom 等库可用于模拟典型的浏览器窗口环境,包括 DOMParser、事件传播和 requestAnimationFrame等其他功能。像 undom 这样的轻量级替代方案提供了足够的 DOM 来支持许多前端框架和库。

需要完整原生浏览器环境的扩展可以使用 service worker 内部的 chrome.windows.create()chrome.tabs.create() API 来创建真正的浏览器窗口。此外,扩展的弹出窗口仍然提供完整的(临时)窗口环境。

# Audio/video playback and capture(音频/视频播放和捕获)

目前无法直接在 Service Worker 中播放或捕获媒体。为了使 Manifest V3 扩展能够利用网络的媒体播放和捕获功能,该扩展需要使用 chrome.windows.create()chrome.tabs.create() 创建一个窗口环境。创建后,扩展可以使用消息传递(message passing)在播放文档和服务工作者之间进行协调。

# Rendering to a canvas(渲染到画布)

在某些情况下,开发人员使用后台页面来呈现内容以在其他上下文中显示或创建和缓存资产。虽然服务工作者无权访问 DOM,因此不能使用 <canvas> 元素,但服务工作者确实可以访问 OffscreenCanvas API

// background.js
// for MV2 background pages
function buildCanvas(width, height) {
  const canvas = document.createElement("canvas");
  canvas.width = width;
  canvas.height = height;
  return canvas;
}

在上面的块中,我们正在构建一个画布元素。要迁移到屏幕外画布,请将 document.createElement('canvas') 替换为new OffscreenCanvas(width, height)

// background.js
// for MV3 service workers
function buildCanvas(width, height) {
  const canvas = new OffscreenCanvas(width, height);
  return canvas;
}

有关使用 OffscreenCanvas 的其他指导,请参阅 OffscreenCanvas — 使用 Web Worker 加速您的画布操作

By.一粒技术服务

results matching ""

    No results matching ""