browser.runtime.sendMessage not working for injected iframe

I’m trying to send a message from a content script to an injected iframe that’s located in the extension’s file tree (using the polyfill). This is the extension’s layout
image
It goes like this:

  • content.js adds ui/bookpanel.html as an iframe and waits for it to load
  • bookpanel.html includes the browser polyfill js (dist/browser-polyfill.min.js) and starts listening for runtime messages once loaded (browser.runtime.onMessage.addListener(...)
  • content.js then sends data using browser.runtime.sendMessage to the iframe

Here’s where I get the error
Error: Could not establish connection. Receiving end does not exist.

So it looks like the listener is unreachable. Funny thing is that it works in Chrome but not in Firefox. I’ve tried to add the page to the manifest as a background page, but is not a solution as all messages go to that instance instead of the ones loaded in the iframes.

Are both in the same tab? I think the source tab is excluded. What you can do is send it to the background script which will foward it back (since you have source tab in the callback).

Another thing that comes to mind is be aware of “async” callbacks or returning a Promise in the “onMessage” listener - because returning a Promise counts as “replying to message” so other listener won’t fire once one of them returns a Promise.

1 Like

Thanks for the reply :slightly_smiling_face: yes, you’re right, the iframe is in the same tab and the docs are clear about it event will be fired in each page in your extension, except for the frame that called runtime.sendMessage. I guess I was fooled by Chrome’s behavior. I’ll try to use a background script as a “message broker”

You can also specify target frame ID.
I use something like this in one of my projects background script:

browser.runtime.onMessage.addListener((data, sender): RuntimeMessageResult => {
  switch (data.type) {
    // from IFRAME to parent ONLY:
    case 'forwardToParent': return browser.tabs.sendMessage(sender.tab.id, data.data, {frameId: 0});
    // from parent to all frames (including parent):
    case 'forwardToChildren': return browser.tabs.sendMessage(sender.tab.id, data.data);
  }
});

Actually it turned out there’s a simpler way to send data to the iframe, by iframeElement.contentWindow.postMessage(), especially when I don’t really need a background script for now :man_dancing:

If you will be using postMessage on a 3rd party page, make sure to read Security concerns in the MDN and make sure to follow those bold statements there.

Otherwise you may have issues with reviewer :slight_smile:.

1 Like