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

Chrome扩展Manifest V3中chrome.runtime.onMessage监听器累积问题求助

Chrome扩展Manifest V3下PGN解析器监听器累积问题解决

我正在把一款国际象棋游戏解析器移植到Chrome扩展Manifest V3规范。右键点击选中的文本触发上下文菜单时,程序会将选中的文本作为PGN(可移植游戏记谱法)进行可视化播放。

问题在于:每次执行游戏时,后台脚本都会新增一个chrome.runtime.onMessage监听器,这些监听器会一直保留到浏览器关闭。导致每次触发时所有监听器都会被调用,但只会播放第一个游戏。


相关代码

后台脚本核心函数(playBoard)

//background.js
function playBoard (info, imgPath, windowAttributes)
{
   console.log("play board with: " + info.selectionText);
   let gameObject = {
                     chessObject:
                     {
                        gametype : "PGN_OR_FEN_board",
                        content : info.selectionText,
                        imgPath : imgPath
                     }
                  };
   let windowPromise = chrome.windows.create(windowAttributes);
   windowPromise.then((value) => {
     chrome.runtime.onMessage.addListener(
         (request, sender, sendResponse) =>
         {
           console.log("backg: chrome.runtime.onMessage()> " + gameObject.chessObject.content);
           sendResponse(gameObject);
         }
       );
   });
}

后台脚本调用上下文

//background.js

console.log("enter global>");
var idParser     = "parser parent";
var idMiniBoard  = "board mini";
var idMediuBoard = "board medium";
chrome.runtime.onInstalled.addListener(

   (details)=>
   {
      console.log("on installing");
      idParser      = chrome.contextMenus.create({"id":idParser,     "title": "Chess Parser", "contexts":["selection"]});
      idMiniBoard   = chrome.contextMenus.create({"id":idMiniBoard,  "title": "Play Small",   "contexts":["selection"], "parentId": idRoatta});
      idMediuBoard  = chrome.contextMenus.create({"id":idMediuBoard, "title": "Play Medium",  "contexts":["selection"], "parentId": idRoatta});
      chrome.contextMenus.onClicked.addListener(menuItemClick);
      console.log("on installed");
   }
)
function menuItemClick(clickData, tab)
{
   switch (clickData.menuItemId)
   {
   case idMiniBoard:
      playBoard   (clickData, "mini18", {url: "board.html", type:"popup", height : 350, width : 350});
      return;
   case idMediuBoard:
      playBoard   (clickData, "medium35", {url: "board.html", type:"popup", height: 450, width: 450});
      return;
   }
}

弹窗启动代码

document.addEventListener('DOMContentLoaded',
   (event) =>
   {
      chrome.runtime.sendMessage({ chessObject: { gametype : "PGN_OR_FEN_board" } },
          (request) => //实际是响应,变量名沿用了请求的命名
          {
             board_doc_main(request.chessObject);
          });

   }
);

已尝试的修改

我沿用了Firefox扩展的实现逻辑(后台打开弹窗后,弹窗主动发消息获取上下文),并按建议修改了playBoard函数:

function playBoard (clickData, imgPath, windowAttributes)
{
   let gameObject = {
                  chessObject:
                  {
                     gametype : "PGN_OR_FEN_board",
                     content : clickData.selectionText,
                     imgPath : imgPath
                  }
               };

   let windowPromise = chrome.windows.create(windowAttributes);
   windowPromise.then((wnd) => {
      chrome.runtime.onMessage.addListener (
         function __playBoardCallback__ (request, sender, sendResponse)
         {
            if (sender.tab?.id === wnd.tabs[0].id)
            {
               chrome.runtime.onMessage.removeListener(__playBoardCallback__);
               sendResponse(gameObject);
            }
         }
      );
   });

}

现在需要彻底解决监听器累积的问题,确保每次只有当前弹窗对应的监听器生效。


解决方案

方案1:优化监听器匹配与清理逻辑

你的思路方向正确,可以补充消息类型判断,并添加弹窗关闭时的监听器清理:

function playBoard(clickData, imgPath, windowAttributes) {
  const gameObject = {
    chessObject: {
      gametype: "PGN_OR_FEN_board",
      content: clickData.selectionText,
      imgPath: imgPath
    }
  };

  chrome.windows.create(windowAttributes).then(wnd => {
    const popupTabId = wnd.tabs[0].id;
    const popupWindowId = wnd.id;

    // 命名函数方便后续移除
    function messageListener(request, sender, sendResponse) {
      // 同时匹配弹窗Tab ID和消息类型,避免无关消息触发
      if (sender.tab?.id === popupTabId && request.chessObject?.gametype === "PGN_OR_FEN_board") {
        // 响应后立即移除监听器
        chrome.runtime.onMessage.removeListener(messageListener);
        chrome.windows.onRemoved.removeListener(windowCloseListener);
        sendResponse(gameObject);
      }
    }

    // 监听弹窗关闭事件,清理残留监听器
    function windowCloseListener(closedWindowId) {
      if (closedWindowId === popupWindowId) {
        chrome.runtime.onMessage.removeListener(messageListener);
        chrome.windows.onRemoved.removeListener(windowCloseListener);
      }
    }

    chrome.runtime.onMessage.addListener(messageListener);
    chrome.windows.onRemoved.addListener(windowCloseListener);
  });
}

方案2:用URL参数传递数据(推荐)

完全可以避免消息传递,直接把PGN内容和图片路径作为URL参数传给弹窗,彻底消除监听器累积问题:

// 后台playBoard函数修改
function playBoard(clickData, imgPath, windowAttributes) {
  // 对PGN内容进行URL编码,避免特殊字符报错
  const encodedPGN = encodeURIComponent(clickData.selectionText);
  // 构造带参数的弹窗URL
  const popupUrl = `board.html?pgn=${encodedPGN}&imgPath=${imgPath}`;
  chrome.windows.create({...windowAttributes, url: popupUrl});
}

// 弹窗代码修改
document.addEventListener('DOMContentLoaded', () => {
  // 解析URL中的参数
  const params = new URLSearchParams(window.location.search);
  const gameObject = {
    chessObject: {
      gametype: "PGN_OR_FEN_board",
      content: decodeURIComponent(params.get('pgn')),
      imgPath: params.get('imgPath')
    }
  };
  board_doc_main(gameObject.chessObject);
});

这个方案无需后台监听器,性能更优,也不会出现监听器残留问题。

内容的提问来源于stack exchange,提问作者armagedescu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 16:00:52