Chrome扩展:弹窗切换按钮控制Content脚本及驻留问题
Chrome扩展:解决标签页闲置后content.js失效及事件重复绑定问题
需求与已实现功能
我正在开发首个Chrome扩展,目标是通过弹窗中的切换按钮,控制当前域名下content.js的执行(而非完全禁用扩展)。目前已实现以下功能:
- 从弹窗切换当前域名的扩展启用/禁用状态;
- 通过
chrome.storage.sync存储状态,重装扩展后无需重复设置; - 切换状态时同步更新同域名其他标签页;
- 实时切换扩展图标(禁用时显示灰色图标);
- 重新打开网站时自动恢复状态与对应图标。
现有代码
popup.js(初始版本)
const toggleBtn = document.querySelector(".toggle"); toggleBtn.addEventListener("click", function(){ if(toggleBtn.checked === true){ console.log('inject content.js'); }else{ console.log('remove content.js'); } });
注入脚本方式
chrome.scripting.executeScript({ target: {tabId}, files: ['content.js'], });
background.js
const icons = { enabled: { '16': 'icon-16.png', '19': 'icon-19.png', '38': 'icon-38.png', '48': 'icon-48.png', '128': 'icon-128.png' }, disabled: { '16': 'icon-16-off.png', '19': 'icon-19-off.png', '38': 'icon-38-off.png', '48': 'icon-48-off.png', '128': 'icon-128-off.png' } }; const getExtensionStatus = host => new Promise((resolve, reject) => { chrome.storage.sync.get(host, result => { if (result[host] !== undefined) { resolve(result[host]); } else { setExtensionStatus(host, true); } }); }); const setExtensionStatus = (host, toggleBtnStatus) => { const data = { [host]: toggleBtnStatus }; chrome.storage.sync.set(data, () => {}); }; chrome.runtime.onInstalled.addListener((details) => { if (details.reason === "install") { chrome.action.setIcon({ path: icons.enabled }); } }); const init = async tab => { const url = new URL(tab.url); if (url.protocol !== "chrome-extension:") { const host = url.host; const status = await getExtensionStatus(host); const icon = status ? icons.enabled : icons.disabled; chrome.action.setIcon({ path: icon }); } }; chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { if (changeInfo.status === "complete") init(tab); }); chrome.tabs.onActivated.addListener(info => { chrome.tabs.get(info.tabId, init); }); chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => { if (request.type === "getExtensionStatus") { const status = await getExtensionStatus(request.host); sendResponse({ status }); return true; } else if (request.type === "setExtensionStatus") { setExtensionStatus(request.host, request.status); const icon = request.status ? icons.enabled : icons.disabled; chrome.action.setIcon({ path: icon }); } });
popup.js(功能实现版本)
document.addEventListener("DOMContentLoaded", function () { const toggleBtn = document.querySelector(".toggle"); chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { const host = new URL(tabs[0].url).host; chrome.runtime.sendMessage({ type: "getExtensionStatus", host }, (response) => { toggleBtn.checked = response.status; }); toggleBtn.addEventListener("click", () => { chrome.runtime.sendMessage({ type: "setExtensionStatus", host, status: toggleBtn.checked }); }); }); });
content.js
document.addEventListener("keydown", listenEnterkey); chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.type === "setExtensionStatus") { console.log(request.status); if (request.status) { // Add event listener here document.addEventListener("keydown", listenEnterkey); } else { // Remove event listener here document.removeEventListener("keydown", listenEnterkey); } } });
当前问题
标签页闲置一段时间后content.js失效,如何重新注入脚本且避免事件重复绑定?
解决方案
1. 强化background.js的脚本注入与状态同步
修改background.js中的init函数,在标签页加载/激活时自动检查并注入content.js,同时同步当前域名的状态:
const init = async tab => { const url = new URL(tab.url); if (url.protocol !== "chrome-extension:") { const host = url.host; const status = await getExtensionStatus(host); const icon = status ? icons.enabled : icons.disabled; chrome.action.setIcon({ path: icon }); // 强制注入content.js,确保脚本存在 chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ['content.js'], injectImmediately: true // 跳过文档加载等待,立即注入 }).catch(err => console.log('注入失败:', err)); // 向content.js发送当前状态 chrome.tabs.sendMessage(tab.id, { type: "syncStatus", status: status }).catch(err => console.log('同步状态失败:', err)); } };
2. 改造content.js,避免重复绑定事件
在content.js中添加初始化标记,确保事件只绑定一次,同时通过消息监听动态同步状态:
let isInitialized = false; let isEnabled = true; function listenEnterkey(e) { // 替换为你的实际按键处理逻辑 console.log('Enter键触发:', e); } // 初始化函数:仅执行一次事件绑定 function init(status) { if (!isInitialized) { isEnabled = status; if (isEnabled) document.addEventListener("keydown", listenEnterkey); isInitialized = true; } else { updateStatus(status); } } // 更新状态:动态添加/移除事件监听 function updateStatus(status) { isEnabled = status; if (isEnabled) { document.addEventListener("keydown", listenEnterkey); } else { document.removeEventListener("keydown", listenEnterkey); } } // 接收外部消息 chrome.runtime.onMessage.addListener((request) => { switch(request.type) { case "syncStatus": init(request.status); break; case "setExtensionStatus": updateStatus(request.status); break; } }); // 页面加载时主动请求当前状态 chrome.runtime.sendMessage( { type: "getExtensionStatus", host: window.location.host }, (response) => init(response.status) );
3. 优化状态同步逻辑,覆盖所有同域名标签页
修改background.js中的消息监听,当切换状态时,自动同步所有同域名标签页,若脚本失效则重新注入:
chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => { if (request.type === "getExtensionStatus") { const status = await getExtensionStatus(request.host); sendResponse({ status }); return true; } else if (request.type === "setExtensionStatus") { setExtensionStatus(request.host, request.status); const icon = request.status ? icons.enabled : icons.disabled; chrome.action.setIcon({ path: icon }); // 同步所有同域名标签页的状态 chrome.tabs.query({ url: `*://${request.host}/*` }, (tabs) => { tabs.forEach(tab => { chrome.tabs.sendMessage(tab.id, { type: "setExtensionStatus", status: request.status }).catch(() => { // 若消息发送失败,重新注入脚本后再同步 chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ['content.js'], injectImmediately: true }).then(() => { chrome.tabs.sendMessage(tab.id, { type: "setExtensionStatus", status: request.status }); }); }); }); }); } });
4. 利用Chrome标签页事件自动恢复
Chrome标签页闲置唤醒时会触发tabs.onActivated事件,此时init函数会自动执行,重新注入脚本并同步状态,确保content.js恢复正常工作。
内容的提问来源于stack exchange,提问作者SkyRar
相关产品推荐
相关产品推荐

