基于Plasmo的Chrome扩展:如何从Service Worker向Content Script发定时消息?
问题
我正在使用Plasmo开发Google Chrome扩展,需求是定期调用Content Script中的函数。尝试使用setInterval()但可靠性不足,了解到在Service Worker中使用chrome.alarms服务是更佳方案。
我的Service Worker中有如下代码:
// background/ports/timer.ts import type { PlasmoMessaging } from "@plasmohq/messaging" async function registerInitAlarm() { // Create an alarm that is invoked a second from now, for every 1.5 seconds await chrome.alarms.create("initialize", { when: Date.now(), periodInMinutes: 1 }) } const handler: PlasmoMessaging.PortHandler = async (req, res) => { const { type, message } = req.body if (type === "register") { await registerInitAlarm() } } chrome.alarms.onAlarm.addListener((alarm) => { if (alarm.name === "initialize") { // How do I send a message here to the content script? } }) export default handler
根据Plasmo文档,需使用Ports API,但该API基于请求/响应模式,不清楚在alarm触发时如何向Content Script发送消息。另外,若不使用Plasmo框架/API,常规Chrome扩展文档要求先指定标签页再发消息,但可能存在多个加载了扩展Content Script的标签页,该如何处理?
解决方案
一、使用Plasmo的实现方式
Plasmo的Ports API虽以请求/响应为主,但可通过持久化端口连接实现主动推送:
在Content Script中建立长连接
在Content Script里主动和Service Worker建立端口连接,并监听消息:// content.ts import { Port } from "@plasmohq/messaging" const timerPort = new Port({ name: "timer" }) // 监听来自Service Worker的触发消息 timerPort.onMessage.addListener((message) => { if (message.type === "trigger-task") { // 调用你需要定期执行的Content Script函数 runYourContentScriptFunction() } }) // 页面卸载时断开连接 window.addEventListener("beforeunload", () => { timerPort.disconnect() }) function runYourContentScriptFunction() { console.log("定期执行的Content Script逻辑") }在Service Worker中维护连接并触发消息
修改Service Worker代码,保存所有活跃端口,alarm触发时遍历发送消息:// background/ports/timer.ts import type { PlasmoMessaging } from "@plasmohq/messaging" // 存储所有活跃的端口连接 const activePorts = new Set<PlasmoMessaging.Port>() async function registerInitAlarm() { await chrome.alarms.create("initialize", { when: Date.now(), periodInMinutes: 1 }) } const handler: PlasmoMessaging.PortHandler = async (req, res) => { // 保存新建立的端口 activePorts.add(req.port) // 端口断开时自动移除 req.port.onDisconnect.addListener(() => { activePorts.delete(req.port) }) if (req.body.type === "register") { await registerInitAlarm() } } chrome.alarms.onAlarm.addListener((alarm) => { if (alarm.name === "initialize") { // 遍历所有活跃端口,发送触发指令 activePorts.forEach(port => { port.postMessage({ type: "trigger-task" }) }) } }) export default handler
二、原生Chrome扩展实现方式(不依赖Plasmo)
直接使用Chrome原生API,通过查询目标标签页批量发送消息:
Service Worker中批量发送消息
修改alarm监听逻辑,查询所有匹配的标签页并逐个发送消息:// background.js async function registerInitAlarm() { await chrome.alarms.create("initialize", { when: Date.now(), periodInMinutes: 1 }) } chrome.alarms.onAlarm.addListener(async (alarm) => { if (alarm.name === "initialize") { // 查询所有注入了Content Script的标签页(URL规则需和manifest中一致) const targetTabs = await chrome.tabs.query({ url: ["http://*/*", "https://*/*"] }) targetTabs.forEach(tab => { if (tab.id) { // 向标签页发送消息,忽略未注入脚本的标签页错误 chrome.tabs.sendMessage(tab.id, { type: "trigger-task" }) .catch(err => console.log(`标签页${tab.id}未注入脚本`, err)) } }) } }) // 处理来自Content Script的注册请求 chrome.runtime.onMessage.addListener((req, sender, sendResponse) => { if (req.type === "register") { registerInitAlarm() sendResponse({ status: "success" }) } })Content Script中监听消息
在Content Script里监听来自Service Worker的消息,触发目标函数:// content.js chrome.runtime.onMessage.addListener((message) => { if (message.type === "trigger-task") { runYourContentScriptFunction() } }) function runYourContentScriptFunction() { console.log("定期执行的Content Script逻辑") }
关键注意事项
- Plasmo方案中,持久端口会随标签页关闭自动断开,手动监听
beforeunload可避免残留连接。 - 原生方案中,
chrome.tabs.query的URL规则必须和manifest.json中Content Script的注入规则完全匹配。 - 两种方案都需要在权限声明中添加
alarms(原生扩展还需tabs权限):"permissions": ["alarms", "tabs"]
内容的提问来源于stack exchange,提问作者callmekatootie
相关产品推荐
相关产品推荐

