I am developing a Chrome extension with a manifest that, for now, enables access to all hosts. The background script injects content scripts into all frames. After the DOM is loaded, the content script in the top page/frame begins to walk the DOM tree. When the walker encounters an iframe, it needs to message the specific content script associated with that iframe's window (possibly cross-origin) to begin it's work and includes some serialized data with this message. The parent window suspends execution and waits for the child to complete it's walk and send a message back that it is done along with serialized data. The parent then continues its work. I have tried two approaches to this problem:
frameElement.contentWindow.postMessage
: this works most of the time, but not always. Sometimes the message is never received by the content script message event listener associated with the iframe window. I have not been able to confirm the cause but I think it is listeners attached before my listener calling event.stopImmediatePropagation()
. For example, on the yahoo home page (https://www.yahoo.com), when posting a message to my content script associated with iframe source https://s.yimg.com/rq/darla/2-9-9/html/r-sf.html, the message is never received. This is an ad-related iframe. Maybe the blocking of messages is intentional. There is no error when the message is posted and I use a targetOrigin of "*".chrome.runtime.sendMessage
: I can send a message to the background page but cannot figure out how to tell the background page to which frame to relay the message. The parent window content script does not know the chrome extension frameId associated with the child frame element it encountered in the DOM walk. So it cannot tell the background page how to direct the message.For point 2, I have tried two techniques that I found here on stackoverflow:
window.frames
array and post a message to the background page with this index. The background page posts a message to all frames with the desired index in the message data. Only the iframe that finds it's window object position in the window.parent.frames array matches the index received from the message proceeds with it's walk. This works OK but is vulnerable to changes in the window.frames
array during the asynchronous messaging process (if an iframe is deleted after message is sent, the index value may no longer match the desired frame).frameElement.name
in the parent window. With same messaging technique, send name to child iframe for comparison to its window.name
value. I believe window.name
gets it's value from the frameElement.name
at the time of the iframe element creation. However, since I don't control the frame element creation, the name attribute is often an empty string and can't be relied on to uniquely match iframe elements to their windows.Is there a way for me to reliably send a message to a content script associated with an iframe element found in walking a DOM tree?
When you call chrome.runtime.sendMessage
from a content script, the second parameter of the chrome.runtime.onMessage
listener ("sender") includes the properties url
and frameId
.
You can send a message (from an extension page, e.g. the background page) to a specific frame using chrome.tabs.sendMessage
with the given frameId
.
If you want to know the list of all frames (and their frame IDs) at any time, use the chrome.webNavigation.getAllFrames
. If you do that, then you can construct a tree of the frames in a tab, and then send this information to all frames for further processing.
postMessage
/ onMessage
frameElement.contentWindow.postMessage
: this works most of the time, but not always. Sometimes the message is never received by the content script message event listener associated with the iframe window. I have not been able to confirm the cause but I think it is listeners attached before my listener callingevent.stopImmediatePropagation()
This can be countered by running your script at "run_at":"document_start"
and immediately register the message
event listener. Then your handler will always be called first and the page cannot cancel it via event.stopImmediatePropagation()
. However, do not blindly trust the information from other frames and always verify the message (e.g. by communicating with the other frames via the background page).
The first method offers a secure way to exchange data between frames, but does not offer a general way to link the frame to a specific DOM element.
The second method allows you to target a specific (i)frame element, but any web page can do that and therefore the method on its own is not reliable.
By combining both, you get a secure communication channel that is linked to a DOM element.
This is a basic example that applies the above methods to communicate between frames A and B:
Content script in A:
Background page:
crypto.getRandomValues
).frameId
(and optionally other information that was included in the message from A).Content script in A:
postMessage
on frame B and pass R.Content script in B:
Note: For a rock-solid application, you need to account for the fact that the frame is removed during any of those steps. If you neglect the asynchronous nature of this process, you may leave your application in an inconsistent state.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With