messaging between content script and background page in a chrome extension is not working as it is supposed to be

You send just one message with a direct callback so naturally Chrome can use this response callback just one time (it's a one-time connection to one entity, be it a page or an iframe).

  • Solution 1: send multiple messages to each iframe explicitly:

    manifest.json, additional permissions:

    "permissions": [
        "webNavigation"
    ],
    

    background.js

    chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
        .............
        // before Chrome 49 it was chrome.webNavigation.getAllFrames(tabId, .....
        // starting with Chrome 49 tabId is passed inside an object
        chrome.webNavigation.getAllFrames({tabId: tabId}, function(details) {
            details.forEach(function(frame) {
                chrome.tabs.sendMessage(
                    tabId,
                    {text: 'hey_cs'},
                    {frameId: frame.frameId},
                    function(response) { console.log(response) }
                );
            });
        });
    });
    
  • Solution 2: rework your background script logic so that the content script is the lead in communication and let it send the message once it's loaded.

    content.js

    chrome.runtime.sendMessage({text: "hey"}, function(response) {
        console.log("Response: ", response);
    });
    

    background.js

    chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
        console.log("Received %o from %o, frame", msg, sender.tab, sender.frameId);
        sendResponse("Gotcha!");
    });
    

Instead of messaging, you can use executeScript for your purposes. While the callback's argument is rarely used (and I don't think many know how it works), it's perfect here:

chrome.tabs.executeScript(tabId, {file: "script.js"}, function(results) {
  // Whichever is returned by the last executed statement of script.js
  //   is considered a result.
  // "results" is an Array of all results - collected from all frames
})

You can make sure, for instance, that the last executed statement is something like

// script.js
/* ... */
result = { someFrameIdentifier: ..., data: ...};
// Note: you shouldn't do a "return" statement - it'll be an error,
//   since it's not a function call. It just needs to evaluate to what you want.

Make sure you make script.js able to execute more than once on the same context.

For a frame identifier, you can devise your own algorithm. Perhaps a URL is enough, perhaps you can use the frame's position in the hierarchy.


Here is a clear way how to communicate bidirectionally in chrome extension [ContentBackground ]:

Inside Content script:

Sends info to background:

chrome.extension.sendRequest({message: contentScriptMessage});

Receives info from background:

chrome.runtime.onMessage.addListener(function(request, sender) {
  console.log(request.message);
});




Inside Background script:

Sends info to content script:

chrome.extension.onRequest.addListener(function(request, sender) {
  console.log(request.message);
});

Receives info from content script:

chrome.tabs.getSelected(null, function(tab) {
  chrome.tabs.sendMessage(tab.id, { message: "TEST" });
});