如何在 Chrome 扩展中创建 Offscreen Documents:完整指南
Source: Dev.to
介绍
如果你正在使用 Manifest V3 开发 Chrome 扩展,你可能已经遇到过 Service Worker 经常被终止的难题。Offscreen 文档提供了一个隐藏的 HTML 环境,能够保持持久状态并执行持续的操作,而不会出现在用户面前。
什么是 Offscreen 文档?
Offscreen 文档是一个隐藏的 HTML 页面,运行在扩展的后台。与为了节省资源而短命的 Service Worker 不同,Offscreen 文档可以保持存活,让你能够:
- 保持持久的数据结构(例如
Map、Set) - 对大数据集进行重计算
- 处理需要持续运行的音视频流
可以把它想象成一个不可见的网页,由你的扩展控制,为长时间运行的任务提供一个稳定的环境。
理想的使用场景
- 持久数据处理 – 维护内存中的数据,防止 Service Worker 停止时丢失。
- 重计算 – 运行 CPU 密集型算法而不中断。
- 音视频处理 – 处理需要持续访问内存的媒体。
重要限制
- 不能访问宿主网站的 DOM(没有按钮、内容等)。
- 仅在 Chrome 的 Manifest V3 扩展中可用(Firefox 不支持)。
- 在与内容脚本分离的隔离上下文中运行。
- 只能使用
chrome.runtimeAPI 进行消息传递;其他 API 如storage或tabs不可用。
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 的工作原理
- 检查是否已存在 – 使用
chrome.runtime.getContexts()(首选)或self.clients.matchAll()(回退)。 - 防止重复创建 – 共享的
creatingOffscreenPromise 可避免竞争条件。 - 必要时创建 – 调用
chrome.offscreen.createDocument()并提供所需的url、reasons与justification。
Offscreen 创建参数
url– 指向你的 Offscreen HTML 文件的路径。reasons– 来自 Chrome API 的一个或多个有效原因(例如WORKERS、AUDIO_PLAYBACK、DOM_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 ,它提供了一个包含认证、订阅处理等功能的即用型扩展脚手架。