Chrome扩展开发:WhatsApp Web场景下如何断开所有Mutation Observer?
解决Chrome扩展中Mutation Observer无法断开的问题
问题核心
当前代码存在两个关键问题导致无法断开Observer:
- content.js中的
observer是立即执行函数的局部变量,外部无法访问调用disconnect() - 每次点击激活按钮都会重新注入content.js,创建多个独立的Observer实例,缺乏统一管理
解决方案
通过消息通信实现Popup对Content Script中Observer的控制,同时避免重复注入脚本,具体步骤如下:
1. Popup添加清除按钮及逻辑
修改popup.js,添加清除按钮的事件处理(假设HTML中已存在class为clear-btn的按钮):
// 在popup.js末尾追加代码 const clearBtn = document.querySelector(".clear-btn"); clearBtn.addEventListener("click", async () => { // 获取当前标签页并发送停止Observer的消息 const tab = await getCurrentTab(); chrome.tabs.sendMessage(tab.id, { action: "stopObserver" }); // 清空存储与UI重置 chrome.storage.sync.clear(); keywordSet.clear(); tbody.innerHTML = ""; list.innerHTML = ""; table.style.display = "none"; activateBtn.style.display = "none"; }); // 迁移getCurrentTab函数到popup.js async function getCurrentTab() { let queryOptions = { active: true, lastFocusedWindow: true }; let [tab] = await chrome.tabs.query(queryOptions); return tab; }
2. 改造content.js,统一管理Observer实例
修改content.js,将Observer实例暴露到全局,并添加消息监听处理停止请求,同时避免重复创建:
// 全局存储Observer实例,防止重复创建 let messageObserver = null; (async () => { // 已有Observer则直接返回,避免重复注入 if (messageObserver) return; const keywords = (await chrome.storage.sync.get("keywords")).keywords || []; const config = { childList: true }; const contactListContainer = document.querySelector( "div.n5hs2j7m.oq31bsqd.gx1rr48f.qh5tioqs" ); if (!contactListContainer) return; // 避免元素不存在报错 const messages = []; const callback = (mutationList, observer) => { const mutation = mutationList[0]; if (!mutation.target.matches("div.n5hs2j7m.oq31bsqd.gx1rr48f.qh5tioqs")) return; const latestMessage = mutation.target.lastElementChild; const div = latestMessage.querySelector("div.focusable-list-item"); if (div?.classList.contains("message-out")) return; const author = latestMessage .closest('div[id="main"]') ?.querySelector('span[data-testid="conversation-info-header-chat-title"]') ?.innerText; const newMessage = latestMessage.querySelector( "span.selectable-text.copyable-text > span" )?.innerText; if (!author || !newMessage) return; // 关键词匹配检查 const foundKeyword = keywords.some(keyword => newMessage.includes(keyword)); if (foundKeyword) { messages.push({ author, message: newMessage }); // 消息去重 const uniqueValues = {}; const uniqueMessages = messages.filter(obj => { const key = `${obj.author}|${obj.message}`; if (!uniqueValues[key]) { uniqueValues[key] = true; return true; } return false; }); chrome.storage.sync.set({ messages: uniqueMessages }); } }; messageObserver = new MutationObserver(callback); messageObserver.observe(contactListContainer, config); })(); // 监听Popup发送的消息 chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.action === "checkObserver") { sendResponse({ isRunning: !!messageObserver }); } else if (message.action === "stopObserver" && messageObserver) { messageObserver.disconnect(); messageObserver = null; // 重置实例,允许重新激活 sendResponse({ success: true }); } });
3. 优化background.js,避免重复注入脚本
修改background.js,注入前先检查Content Script是否已运行Observer:
chrome.runtime.onMessage.addListener(async (message, sender) => { if (!message.isActivated) return; const tab = await getCurrentTab(); // 先询问标签页是否已有Observer在运行 chrome.tabs.sendMessage(tab.id, { action: "checkObserver" }, (response) => { if (chrome.runtime.lastError || !response?.isRunning) { // 未运行则注入脚本 chrome.scripting.executeScript({ target: { tabId: tab.id, allFrames: true }, files: ["/src/js/content.js"], }); } }); }); async function getCurrentTab() { let queryOptions = { active: true, lastFocusedWindow: true }; let [tab] = await chrome.tabs.query(queryOptions); return tab; }
核心逻辑说明
- 用全局变量
messageObserver统一管理Observer实例,确保外部能访问并调用disconnect() - 通过Chrome消息API实现Popup与Content Script的通信,控制Observer启停
- 增加重复注入检查,避免创建多个Observer实例导致资源浪费和逻辑冲突
内容的提问来源于stack exchange,提问作者elab4d1
相关产品推荐
相关产品推荐

