You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 22:44:56