I keep forgetting how communication between a main window and an iframe works because I don't use it so often. Most articles go in depth of how and why, rather than serve as a quick recap, so I'll try to do that here.
- main window and an iframe can exchange data using postMessages.
- data that is sent over those messages needs to be serialized
- it's tricky to start communication because most of the time your aren't sure what loaded first: main window or an iframe. If you control both, it's much easier and this example will cover such use-case.
- never just blindly act upon received message without checking it's origin first. Create a whitelist for allowed origins. Otherwise any website could iframe yours and try to manipulate it by sending it bogus messages
Example consists of 2 HTML files:
- index.html(main window)
- iframe.html(embedded page)
index.html — acts only for messages received from trusted origins, fully controls the rendering of an iframe. First sets event handlers and then sets and iframe src to load it.
<!DOCTYPE html>
<html>
<head>
<title>main</title>
</head>
<body>
<iframe></iframe>
<script>
const trustedOrigins = ["http://localhost:5000"];
const iframe = document.querySelector("iframe");
iframe.addEventListener("load", () => {
iframe.contentWindow.postMessage(
JSON.stringify({ message: "this should be delivered to an iframe" })
);
});
iframe.setAttribute("src", "iframe.html");
function onMsg(msg) {
if (!trustedOrigins.includes(msg.origin)) return;
console.log(`Message from an iframe`, msg);
}
window.addEventListener("message", onMsg, false);
</script>
</body>
</html>
iframe.html — very similar to index.html. Logic behind trusted origins is the same, and since we definitely know this page will be the 2nd to load it only needs to listen for first message from main window and respond to it.
<!DOCTYPE html>
<html>
<head>
<title>iframe</title>
<meta charset="utf-8" />
<script>
const trustedOrigins = ["http://localhost:5000"];
function onMsg(msg) {
if (!trustedOrigins.includes(msg.origin)) return;
console.log(`Message from main window`, msg);
parent.postMessage(
JSON.stringify({
message: "this should be delivered to main window"
})
);
}
window.addEventListener("message", onMsg, false);
</script>
</head>
<body>
<h1>Iframe body</h1>
</body>
</html>
To test this out you could just paste the code to index.html and iframe.html files located in the same folder and use simple HTTP server tool. Run npm install http-server -g
to install it. After that, open up a terminal instance, position yourself in the above mentioned folder containing 2 HTML files and run:
http-server -p 5000 .
This starts a static HTTP server on port 5000 and you should be able to open up http://localhost:5000, enter the browser's dev tools console and see logged out postMessages.
Note: some browser extensions might also want to talk with your iframe and it can be harder to filter them out, since they can inject code to a website and try to access your iframe from a legitimate domain. Easiest circumvention, but not perfect would be passing some kind of token or key to confirm message is coming from the right source.
Top comments (1)
Kudos on being security-oriented and including trustedOrigins.
For future reference I wrote a library to simplify communication between frames - it’s called iFramily (github.com/EkoLabs/iframily).
Basically it has a simpler API than postMessage, which includes Promise-based responses, message queuing, and managing the connection until both frames are ready to talk. It also takes a responsible approach to security...
Would love to hear your thoughts!