如何修复Chrome扩展报错:仅可创建一个离屏文档?
我的扩展使用chrome.offscreen API,目标是在浏览器启动时自动运行离屏文档。但浏览器启动时会偶尔随机出现报错Only a single offscreen document may be created,此时扩展完全停止工作——内容脚本无法向网站注入元素,且网站控制台没有任何报错,只有重新加载扩展或重启浏览器才能解决。
我尝试了三种方式提前检查离屏文档是否已创建:全局isOffscreenCreating Promise、chrome.offscreen.hasDocument()原生函数,以及官方文档示例中的自定义hasOffscreenDocument()函数。但实际情况是,自定义的hasOffscreenDocument()始终返回false,我不清楚原因。
请问这个报错出现的原因是什么?
// background.js chrome.runtime.onStartup.addListener(setupOffscreenDocument) setupOffscreenDocument() const OFFSCREEN_DOCUMENT_PATH = 'assets/html/offscreen.html' let isOffscreenCreating export async function setupOffscreenDocument() { if (isOffscreenCreating) { await isOffscreenCreating; } else if ((chrome.offscreen.hasDocument && !(await chrome.offscreen.hasDocument())) || !(await hasOffscreenDocument())) { try { isOffscreenCreating = chrome.offscreen.createDocument({ url: OFFSCREEN_DOCUMENT_PATH, reasons: [chrome.offscreen.Reason.IFRAME_SCRIPTING], justification: 'Listening WebSocket messages', }) await isOffscreenCreating; } catch (error) { console.error(error) } finally { isOffscreenCreating = null } } } async function hasOffscreenDocument() { const matchedClients = await clients.matchAll(); for (const client of matchedClients) { if (client.url.endsWith(OFFSCREEN_DOCUMENT_PATH)) { return true; } } return false; }
报错原因分析
重复调用触发竞态问题
你同时在代码最外层直接调用了setupOffscreenDocument(),又通过chrome.runtime.onStartup.addListener注册了该函数。浏览器启动时,这两个调用可能几乎同时触发:第一次创建流程还未完成时,第二次检查可能因为isOffscreenCreating赋值延迟或异步检查的时间差,错误地认为文档未创建,进而尝试再次创建,触发单实例限制报错。自定义检查函数失效的本质原因
clients.matchAll()默认只返回当前上下文可见的客户端,而Offscreen文档属于隐藏的扩展上下文,原函数未传入必要参数,无法匹配到它。需要添加{includeUncontrolled: true, type: 'all'}参数才能获取到Offscreen文档对应的client,原函数缺少这些参数,所以始终返回false。原生API的兼容性漏洞
chrome.offscreen.hasDocument()是较新的API,部分旧版本Chrome可能不支持,导致你的条件判断中(chrome.offscreen.hasDocument && !(await chrome.offscreen.hasDocument()))逻辑失效,错误进入创建流程。
修复建议
- 移除代码最外层直接调用的
setupOffscreenDocument(),仅保留chrome.runtime.onStartup.addListener(setupOffscreenDocument),避免重复触发。 - 修复自定义检查函数,添加正确参数:
async function hasOffscreenDocument() { const matchedClients = await clients.matchAll({includeUncontrolled: true, type: 'all'}); for (const client of matchedClients) { if (client.url.includes(OFFSCREEN_DOCUMENT_PATH)) { return true; } } return false; } - 优先使用原生
chrome.offscreen.hasDocument()检查,同时保留修复后的自定义检查作为兼容兜底,确保判断逻辑准确。
内容的提问来源于stack exchange,提问作者Viewed

