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
相关产品推荐
相关产品推荐

