javascriptgoogle-chrome-extensionchrome-extension-manifest-v3browser-extensionmessage-passing

How can I pass messages in a Chrome Extension from content.js to background.js to popup.js?


I want to write a Chrome extension that essentially has to pass an information (1) from the content.js script to background.js, where it sends the information to an external Node.js server, and then (2) background.js has to forward the server's response to popup.js, so it can be shown in popup.html. This process should happen each time a page is visited.

I achieved that the information is passed all the way to popup.js but I use chrome.runtime.sendMessage each time to pass the information from content to background and from there to popup and I use chrome.runtime.onMessage.addListener to receive the message in each case. However, popup.js receives now both the message content.js passes to background.js and the message passed correctly from background.js to popup.js. I just want the latter message received by the popup.js.

This is my background.js code:

chrome.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {

        console.log("received from content.js:\n" + request.content_message);
    
        sendResponse({check: "to content.js: background recieved content_message"});

        fetch('http://localhost:3000/message', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ message: request.content_message})
        })
        .then(response => response.json())
        .then(data => {
            console.log(data.response);

            const to_popup = data.response;

            chrome.runtime.sendMessage({res: to_popup }, function(response) {
                console.log(response.popup_answer);
            });
        })
        .catch(error => {
            console.error('Error:', error);
        });
    }
);

And this is my popup.js code:

chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) {
  alert(JSON.stringify({request}));

  sendResponse({popup_answer: "to background: popup recieved message"});
});

I compared my code with the answer provided herehere. I looked up message passing in the docks and it says:

If multiple pages are listening for onMessage events, only the first to call sendResponse() for a particular event will succeed in sending the response. All other responses to that event will be ignored.

This seems to only address the case of responding back, but no receiving the messages.

I thought about

  1. having popup.js filter the sender of each message and only do something with it if the sender is background,
  2. creating a long-lived connection between background and popup.

It seems to me that there has to be a better and smarter way. Do you have any suggestions for me? Thanks!


Solution

  • You can use something like this:

    // constants.js
    export const backgroundScriptCommands = {
      FETCH_DATA_FROM_SERVER: 'fetch-data-from-server',
      ...
    };
    export const popupCommands = {
      PRINT_DATA: 'print-data',
      ...
    };
    
    // Your content-script.js
    chrome.runtime.sendMessage({ type: backgroundScriptCommands.FETCH_DATA_FROM_SERVER });
    
    // Your background-script.js
    chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
      switch (request.type) {
        case backgroundScriptCommands.FETCH_DATA_FROM_SERVER:
          // do something
      }
    });
    
    // Your popup.js
    chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
      switch (request.type) {
        case popupCommands.PRINT_DATA:
          // do something
      }
    });
    

    This way you can understand who the message is addressed to and simply not react in case the message is intended for example background-script.

    However, there are a few things to draw your attention to here, chrome.runtime.onMessage listener in your popup.js will only work after the user clicks on the popup icon. And will stop working if the popup is closed. Perhaps you can just use popup.js to do some work, as @woxxom wrote in the comments, or just send a message to service-workerchrome.runtime.sendMessage({ type: backgroundScriptCommands.FETCH_DATA_FROM_SERVER }). And more, if you try to send a message from background-script to popup.js if it's inactive, you'll get an exception.