13

I post the code below:

manifest.json

{
  "manifest_version": 2,

  "name": "Demo",
  "description": "all_frames test",
  "version": "1.0",

  "background": {
    "scripts": ["background.js"]
  },

  "content_scripts": [{
        "matches":    ["*://*/*"],
        "js":         ["content.js"],
        "all_frames": true
    }],

  "permissions": [
          "tabs",
          "*://*/*"
   ]
}

background.js

chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
    var tabStatus = changeInfo.status;

    if (tabStatus == 'complete') {

        function return_msg_callback() {
            console.log('Got a msg from cs...')
        }

        chrome.tabs.sendMessage(tabId, {
            text: 'hey_cs'
        }, return_msg_callback);
    }

});

content.js

/* Listen for messages */
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
    /* If the received message has the expected format... */
    if (msg.text && (msg.text == 'hey_cs')) {
        console.log('Received a msg from bp...')
        sendResponse('hey_bp');
    }
});

Then, if I go to a site that includes multiples cross-origin iFrames, e.g., http://www.sport.es/ you would see that all the iFrames within the page receive the message from the background page but only one of them is able to response back. Is this a normal behavior?

Thanks in advance for your answer.

jack
  • 227
  • 1
  • 3
  • 11
  • As for whether it is a normal situation, a quote from the docs: _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._ – Xan Sep 25 '15 at 15:31
  • @Xan So here we are talking about all the iFrames in one page...so as per docs, all the iFrames must respond. no? – jack Sep 25 '15 at 16:57
  • So what I want to know is if one iFrame is considered as a single page? – jack Sep 25 '15 at 17:19
  • Without specifying the frame ID, all frames are separate "receivers" of the message. For every message, only one reply can be made. – Xan Sep 25 '15 at 20:24

3 Answers3

21

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!");
    });
    
wOxxOm
  • 65,848
  • 11
  • 132
  • 136
  • I don't understand how your first proposed solution would solve the situation as I send a message to content script only after the page is completely loaded and this means that, for sure, the content script is loaded in the page. Btw, I tested your proposed solution and it doesn't change anything. – jack Sep 25 '15 at 10:26
  • About your second solution, I don't care how much time the iframes take to load; actually if you run my example code posted in the question, you never receive back the response. Otherwise, what you proposed is not the right fit for what I want to do as described in the problem. This is because we want to do something in the content script, not when the content script is loaded but when the loading of the page is complete, i.e., when we receive the message that tabs.onUpdated status is complete. This implicitly means that all iFrames are loaded. – jack Sep 25 '15 at 10:32
  • @wOxxOm what version of chrome you are running? as far as I'm concerned, I'm running 45.0.2454.99 (64-bit) and I only see one response back from the content script to the background page in the background console even if multiple messages are sent from background page to the content scripts and i see those logs in the webpage developer tools console window. – jack Sep 25 '15 at 14:30
  • 1
    The signature of ```getAllFrames``` has changed. Now it needs to be: ```chrome.webNavigation.getAllFrames({tabId: tabId}, function(details)...```. – Tim Scott Mar 21 '16 at 18:25
4

Communicating between a content script and the background page in a Chrome extension

Content script to background page

Send info to background page

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

Receive info from content script

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

Background page to content script

Send info to content script

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

Receive info from background page

 chrome.runtime.onMessage.addListener(function(request, sender) {
  console.log(request.message);
});
RedGuy11
  • 344
  • 6
  • 14
V. Sambor
  • 12,361
  • 6
  • 46
  • 65
3

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.

Community
  • 1
  • 1
Xan
  • 74,770
  • 16
  • 179
  • 206
  • can't we get the frameId as frame identifier? – jack Sep 28 '15 at 07:27
  • I don't think a content script can find out the `frameId` in the format used by other Chrome APIs. I'm not 100% sure though. – Xan Sep 28 '15 at 07:29