如何在 Chrome 扩展中创建 Offscreen Documents:完整指南

发布: (2025年12月15日 GMT+8 21:40)
6 min read
原文: Dev.to

Source: Dev.to

介绍

如果你正在使用 Manifest V3 开发 Chrome 扩展,你可能已经遇到过 Service Worker 经常被终止的难题。Offscreen 文档提供了一个隐藏的 HTML 环境,能够保持持久状态并执行持续的操作,而不会出现在用户面前。

什么是 Offscreen 文档?

Offscreen 文档是一个隐藏的 HTML 页面,运行在扩展的后台。与为了节省资源而短命的 Service Worker 不同,Offscreen 文档可以保持存活,让你能够:

  • 保持持久的数据结构(例如 MapSet
  • 对大数据集进行重计算
  • 处理需要持续运行的音视频流

可以把它想象成一个不可见的网页,由你的扩展控制,为长时间运行的任务提供一个稳定的环境。

理想的使用场景

  • 持久数据处理 – 维护内存中的数据,防止 Service Worker 停止时丢失。
  • 重计算 – 运行 CPU 密集型算法而不中断。
  • 音视频处理 – 处理需要持续访问内存的媒体。

重要限制

  • 不能访问宿主网站的 DOM(没有按钮、内容等)。
  • 仅在 Chrome 的 Manifest V3 扩展中可用(Firefox 不支持)。
  • 在与内容脚本分离的隔离上下文中运行。
  • 只能使用 chrome.runtime API 进行消息传递;其他 API 如 storagetabs 不可用。

Manifest 声明

manifest.json 中添加 offscreen 权限:

{
  "manifest_version": 3,
  "name": "Your Extension Name",
  "version": "1.0.0",
  "description": "A short description of your extension.",
  "permissions": ["offscreen"],
  "background": {
    "service_worker": "background.js",
    "type": "module"
  }
}

Offscreen HTML 文件(offscreen.html

在扩展根目录创建此文件。它永远不会出现在用户面前,但提供了执行环境。

<!DOCTYPE html>
<html>
  <head>
    <title>Offscreen Document</title>
  </head>
  <body></body>
</html>

Offscreen 脚本(offscreen.js

处理持久操作并监听来自后台脚本的消息。

// Perform heavy processing or maintain persistent state
console.log('Offscreen document is live');

// Listen for messages from the background or content scripts
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'Blah_Blah') {
    // Perform your processing here
    // ...

    // Example response
    sendResponse({ result: 'processed' });
  }
  // Return true to keep the message channel open for async response
  return true;
});

Note: Offscreen console logs appear in the same inspector as the service worker. Open chrome://extensions/, enable Developer mode, and click the “service worker” link to view them.

创建 Offscreen 文档(background.js

let creatingOffscreen = null;

async function ensureOffscreen() {
  const offscreenUrl = chrome.runtime.getURL('offscreen.html');

  // Check if an offscreen document already exists
  if ('getContexts' in chrome.runtime) {
    const contexts = await chrome.runtime.getContexts({
      contextTypes: ['OFFSCREEN_DOCUMENT'],
      documentUrls: [offscreenUrl],
    });

    if (contexts.length > 0) {
      console.log('Offscreen document already exists');
      return;
    }
  } else {
    // Fallback for older Chrome versions
    const clients = await self.clients.matchAll();
    if (clients.some(client => client.url.includes(chrome.runtime.id))) {
      return;
    }
  }

  // Create the offscreen document if it doesn't exist
  if (!creatingOffscreen) {
    creatingOffscreen = chrome.offscreen.createDocument({
      url: 'offscreen.html',
      reasons: ['WORKERS'],
      justification: 'Maintain persistent data structures for extension functionality',
    });

    await creatingOffscreen;
    creatingOffscreen = null;
    console.log('Offscreen document created successfully');
  } else {
    // Wait for an ongoing creation to finish
    await creatingOffscreen;
  }
}

// Ensure offscreen document on startup
chrome.runtime.onStartup.addListener(() => {
  ensureOffscreen();
  console.log('Extension started, offscreen document ensured');
});

// Ensure offscreen document on installation
chrome.runtime.onInstalled.addListener(() => {
  ensureOffscreen();
  console.log('Extension installed, offscreen document created');
});

// Handle explicit requests to ensure the offscreen document exists
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
  if (msg && msg.type === 'ensure_offscreen') {
    ensureOffscreen()
      .then(() => sendResponse({ ok: true }))
      .catch(err => sendResponse({ ok: false, error: String(err) }));
    return true; // Keep channel open for async response
  }
});

ensureOffscreen 的工作原理

  1. 检查是否已存在 – 使用 chrome.runtime.getContexts()(首选)或 self.clients.matchAll()(回退)。
  2. 防止重复创建 – 共享的 creatingOffscreen Promise 可避免竞争条件。
  3. 必要时创建 – 调用 chrome.offscreen.createDocument() 并提供所需的 urlreasonsjustification

Offscreen 创建参数

  • url – 指向你的 Offscreen HTML 文件的路径。
  • reasons – 来自 Chrome API 的一个或多个有效原因(例如 WORKERSAUDIO_PLAYBACKDOM_PARSER 等)。
  • justification – 对文档为何必需的清晰说明;在扩展提交时会被审查。

关闭 Offscreen 文档

当文档不再需要时,可以关闭它以释放资源:

async function closeOffscreen() {
  try {
    await chrome.offscreen.closeDocument();
    console.log('Offscreen document closed');
  } catch (error) {
    console.error('Error closing offscreen document:', error);
  }
}

后台与 Offscreen 之间的消息传递

使用 Chrome runtime 消息 API 进行通信。

从后台到 Offscreen:

chrome.runtime.sendMessage(
  { type: 'Blah_Blah' },
  response => {
    console.log('Response from offscreen:', response);
  }
);

浏览器支持与特性

  • Manifest 版本 – Offscreen 文档仅在 Manifest V3(Chrome 及其他基于 Chromium 的浏览器)中可用。Firefox 不支持。
  • DOM 访问 – 不能直接与宿主页面的 DOM 交互,所有数据必须通过消息传递。
  • 持久性 – 状态在操作之间保持,不像 Service Worker 那样会被清除。
  • 可见性 – 完全对用户隐藏,不会显示任何 UI 元素。

其他资源

  • 官方 Chrome 文档关于 Offscreen 文档的说明。
  • 欢迎探索 extFast boilerplate ,它提供了一个包含认证、订阅处理等功能的即用型扩展脚手架。
Back to Blog

相关文章

阅读更多 »