Steps to reproduce: I have two sites with different origins. Let it be foo.com and bar.com. Site foo.com fires messages through BroadcastChannel with 'payment-info' name. I also have an iframe, hosted on foo.com which is built in bar.com. Here's iframe code: <html><head><script type="text/javascript"> (function () { const bc = new BroadcastChannel('payment-info'); bc.addEventListener('message', (m) => { const data = JSON.parse(m.data); data.channel = 'payment-info'; if (window.top !== window) { window.top.postMessage(JSON.stringify(data), '*'); } }); })(); </script></head><body></body></html> So iframe subscribes to 'payment-info' broadcast channel and when it fires, iframe post a message to it's parent window aka bar.com with some information. On bar.com side I just listen to 'message' event and call 'receiveMessage' function with JSON.parse window.addEventListener('message', function (message) { receiveMessage(message); }); It's expected to work on my site bar.com in all browsers. It doesn't work in Safari and Firefox but works in Chrome. I combined code from several files of index.html, index2.html and iframe.html which I used to test it locally. index.html and iframe.html are considered to be from one origin and index2.html is from another. Unfortunately it's impossible to test it just as plain HTML/JS without two different servers with different domains <!--INDEX CODE--> <!doctype html> <html lang=""> <head> <meta charset="utf-8"> <title></title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta property="og:title" content=""> <meta property="og:type" content=""> <meta property="og:url" content=""> <meta property="og:image" content=""> <meta name="theme-color" content="#fafafa"> </head> <body> <p>index page</p> <a href="/page.html">link</a> <script> let i = 0; const bc = new BroadcastChannel('payment-page'); function sendItem() { i+=1; const message = '{"message":"Hello from the new window!"}'; if (window.opener) window.opener.postMessage(message, '*'); const data = { message: `Hello from broadcast ${i}` }; console.log(JSON.stringify(data)); bc.postMessage(JSON.stringify(data)) console.log(message) } </script> <button onclick="sendItem()">send item</button> </body> </html> <!--END OF INDEX CODE--> <!--IFRAME CODE--> <html><head><script type="text/javascript"> (function () { const bc = new BroadcastChannel('payment-page'); console.log('broadcast channel is created', bc) bc.addEventListener('message', (m) => { console.log('proxy: receive message ', m); const data = JSON.parse(m.data); data.channel = 'payment-page'; if (window.top !== window) { // Inside iframe. Proxy to top. console.log('proxy: send to parent'); window.top.postMessage(JSON.stringify(data), '*'); } }); })(); </script></head><body></body></html> <!--END OF IFRAME--> <!--INDEX2 CODE--> <!doctype html> <html class="no-js" lang=""> <head> <meta charset="utf-8"> <title></title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <iframe width="100" height="100" src="http://domain-1.com:3000//iframe.html" ></iframe> <script> window.addEventListener('message', function (message) { if (message.data && typeof message.data === 'string') { console.log(JSON.parse(message.data)) } }); </script> </body> </html> <!--END OF INDEX2 CODE-->
This is intentional origin partitioning. In privacy-preserving browsers like Safari, iframe foo.com under bar.com is not allowed to communicate with top-level foo.com.