Search code examples
javascriptgoogle-chrome-extensionchrome-extension-manifest-v3

In extension the callbacks are accumulating and persisting until browser restart


I am porting a chess game parser to new manifest v3 specification. When clicking context menu on a selected text, it plays visually the selected text as PGN chess game notation.
The problem is that background script on each game execution adds one more chrome.runtime.onMessage listener, and all the listeners persists until browser closed. So, all listeners are invoked and only first game is played each time.
There is the code:

//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);
         }
       );
   });
}

The above code is invoked in following context:

//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;
   }
}

There is the popup window startup code

document.addEventListener('DOMContentLoaded',
   (event) =>
   {
      chrome.runtime.sendMessage({ chessObject: { gametype : "PGN_OR_FEN_board" } },
          (request) => //in fact is response, but is requested response
          {
             board_doc_main(request.chessObject);
          });

   }
);

I am trying to apply similar approach as I did for firefox, here Firefox extension, identification of context. Somehow for firefox this works. I am trying to implement like this: Popup open on click from background worker. Then popup window sends message to background worker to receive the required context, including the selection game text.
###################
UPDATE:
As suggested in response from @wOxxOm, this is exactly how I changed now

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);
            }
         }
      );
   });

}

Solution

  • Unregister the listener immediately when it runs by using a named function:

    async function playBoard(info, imgPath, windowAttributes) {
      let gameObject = {
        // .........
      };
      const wnd = await chrome.windows.create(windowAttributes);
      const tabId = wnd.tabs[0].id;
      chrome.runtime.onMessage.addListener(function _(msg, sender, sendResponse) {
        if (sender.tab?.id === tabId) {
          chrome.runtime.onMessage.removeListener(_);
          sendResponse(gameObject);
        }
      });
    }