Search code examples
google-chrome-extensionmessaging

Chrome Extension: Sending a message to the page loaded in a specific iframe


I'm working on a Chrome extension to (among other things) support a page with multiple iframes, each of which loads a page from some other domain. I need to send a msg to the page loaded a specific one of those iframes. The top-level page and the pages in the iframe each have their own content scripts, so the full messaging API is available.

From the top page, when I do chrome.runtime.sendMessage(), all the iframes get it (as does the top window, but it's easy for its content script to know that that particular msg isn't intended for it). Is there any way to target a specific one of those iframes, or for the desired iframe page to know that the msg is for it?

Note that...

  • The top page can't access anything in iframe pages directly, because they're from other domains.

  • The top page knows the URL that was originally loaded in each frame, but the user may have navigated from there, so including the target URL as a msg parameter for the receiving script to check won't work.

Is there something obvious I'm missing here?


UPDATE: @wOxxOm's answer was very helpful, but I'm still stuck on how to get the frameIds I need.

More specifically, I need to do two things with those iframes, both of which need that frameId:

  • Inject a script into each iframe
  • Send msgs to a specific iframe in response to user actions on the top-level page

All of this is complicated by the fact that the iframes are created and removed dynamically as the user works.

One idea I had is to initially load each new iFrame with the URL "about:blank?id=nnn", where nnn is the DOM id of the corresponding iframe element. That way, when I call getAllFrames(), I can recognize the new iframes by that URL, and build a lookup of frameIds for each DOM id. Once that's done, I can load the real URL, inject the script once it's loaded.

That seems so roundabout, I'm hoping I've missed some supporting API or other straightforward approach.


Solution

  • I did find a solution, but it's pretty indirect. I hope this is clear; all these moving parts are the nature of the beast as I understand it.

    Here's what I ended up doing:

    1. Added a name attribute to each iframe, the same as its DOM id.
    2. When the page in each iframe loads, a global content script calls chrome.runtime.sendMessage(), passing that name, which it can access as window.name.
    3. The background script gets that msg, with the frameId of that iframe as sender.frameId, and calls chrome.tabs.sendMessage(), passing the frameId and window name.
    4. The top-level page's content script builds a lookup object from those window-name (AKA iframe DOM id) / frameId pairs.
    5. When the top-level page's content script wants to send a message to any of the iframe pages, it looks up the target's frameId in that lookup object, then calls chrome.runtime.sendMessage(), with a message type that indicates it's for a specific iframe, and including that frameId.
    6. In response, the background script sends it on to the requested iframe's content script with chrome.tabs.sendMessage(), passing {frameId: request.frameId} as the 3rd parameter, as wOxxOm suggested.

    This is working here, but by all means let me know if there's a simpler way to do this.