We might need communication between popups, tabs, or iframes on a web page or application.
In my article titled Google Tag Manager Scroll Depth Handling, I wrote: “when an iframe tag is used within a page, the browser creates a separate window object for the HTML document and a separate window object for the iframe.” In the continuation of my article HTML5 and API Usage, I will examine this topic from a different perspective.
Window Object
The window object can also be described as the browser window. It is an object that contains properties and methods related to the browser. Fundamentally, if we consider the browser’s structure, the window object resides at the top level of the object hierarchy. All other objects are defined as sub-objects of this one. Additionally, the content of the window object is accessible as the global JavaScript object. On the Node.js side, it is referred to as global. Although there is no universal standard for the window object, all modern browsers support it.
The window object has sub-objects such as window, history, location, navigator, frames, screen, and document, each of which contains methods targeting the entire current window1. Each of these sub-objects also has its own properties, methods, and sub-objects.
You can view the properties associated with the window object by typing window into your browser’s console area. In any execution environment (window/tab), only a single JavaScript window object exists.
Let’s move on to the iframe topic. If a page (represented by a document object) uses an iframe, the browser creates a window object for the HTML document and an additional window object for each iframe2. Using the frames property, you can access iframe elements3, and you can access the specific iframe element defined by the frameElement property4.
In short, the windows and iframes inside our browser each have their own separate window objects.
The question now is: how do we establish communication between these objects? For instance, how can we send information from one page to a popup, or from an iframe to its container (or vice versa)?
window.postMessage API
The window.postMessage method enables JavaScript to send and receive message data between two windows/frames (cross-window communication). Thanks to this method, we can also securely transfer data across domains (based on domain names). However, for the method to work, the domain, protocol, and port must match5. I’ll provide some examples shortly. First, let’s take a look at the arguments defined for this method6.
targetWindow.postMessage(message, domain, transfer);
targetWindow
: Points to the window object where the message will be sent. It can be defined via window.open, window.opener, HTMLIFrameElement.contentWindow, window.parent, or window.frames. You can see examples related to window.open and HTMLIFrameElement.contentWindow below.
message
: Contains the message to be sent between windows/frames.
domain
: Specifies the target window. This allows you to verify the origin of the data and perform appropriate actions. The "*" definition can be used in development environments. However, during actual usage, a source must be specified as a URL or URI7.
transfer
: An optional argument. A list of Transferable objects transferred along with the message.
So, how do we receive these messages?
MessageEvent
The MessageEvent interface represents a message received by a target object (target)8.
window.addEventListener("message", event => {
if (event.origin !== "http://example.org:8000") return;
}, false);
We can access the source of the message via event.origin, the message content via event.data, and the window object that sent the message via event.source. The event.source can be used to establish bidirectional communication between two windows having different origins9.
Now let’s move on to some practical examples. Our first example will be implemented using a popup window for pages under the same domain.
First, let’s create our main page, index.htm.
<script>
let childwin = null;
let openChild = () => {
childwin = window.open('./popup.htm', "popup", 'height=300px, width=500px');
}
let sendMessage = () => childwin
.postMessage(document
.getElementById('txt')
.value, '*');
</script>
<button onclick="openChild()">Open Popup</button>
<input type="text" id="txt" value="take a deep breath!" />
<button onclick="sendMessage()">send it</button>
With the above code, the basic operation performed is opening a popup window and sending the value from the text input field (value) to the popup as a message via postMessage. Now let’s examine the code of the popup page that will receive this message, popup.htm.
<div id="data">
<strong>my todo list</strong>
</div>
<script>
const list = document.createElement('ul');
document.getElementById('data').appendChild(list).setAttribute('id', 'myList');
window.addEventListener("message", event => {
const item = document.createElement('li');
const itemVal = document.createTextNode(event.data);
document.getElementById('myList').appendChild(item);
item.appendChild(itemVal);
});
</script>
An div and our JavaScript code are present. On the JavaScript side, for every new message received, a new li element is created inside the div, using the input value from index.htm. The interaction between pages will resemble the following view.
In this example, we used pages under the same domain name. In the second example, let’s examine the process using pages located under different domain names in a bidirectional manner. This time, instead of using a popup, I’ll use an iframe element. The name of our first file will still be index.htm and it will be published under the example.com domain. Finally, we’ll also verify the source of the message being sent.
<iframe src="http://subdomain.webstore.com/frame.htm" id="ifrm" width="400" height="250"></iframe>
<input type="text" id="txt" value="take a deep breath!" />
<button onclick="sendMessage()">send it</button>
<script>
let elIfrm = document.getElementById('ifrm').contentWindow;
let sendMessage = () => elIfrm
.postMessage(document
.getElementById('txt')
.value, 'http://subdomain.webstore.com');
</script>
The content inside the iframe will be loaded after the page has been created. In the example above, we’re using JavaScript to send the value from the text input as a message inside the iframe. Very little has changed within the frame.htm content.
<div id="data">
<strong>my todo list</strong>
</div>
<script>
const list = document.createElement('ul');
document.getElementById('data').appendChild(list).setAttribute('id', 'myList');
window.addEventListener("message", event => {
if (event.origin !== "http://example.com") return;
const item = document.createElement('li');
const itemVal = document.createTextNode(event.data);
document.getElementById('myList').appendChild(item);
item.appendChild(itemVal);
});
</script>
Now, after the addition operation, let’s send an acknowledgment message back to origin. This acknowledgment message should be the number of child elements in the list created inside the iframe. For your comparison, I’m re-sharing the code for the relevant pages. Our first code is for index.htm.
<iframe src="http://subdomain1.dnomia.com/frame.htm" id="ifrm" width="400" height="250"></iframe>
<input type="text" id="txt" value="take a deep breath!" />
<button onclick="sendMessage()">send it</button>
<div id="status"></div>
<script>
let elIfrm = document.getElementById('ifrm').contentWindow;
let sendMessage = () => elIfrm
.postMessage(document
.getElementById('txt')
.value, 'http://subdomain1.dnomia.com');
window.addEventListener("message", event => {
if (event.origin !== "http://subdomain1.dnomia.com") return;
document.getElementById('status').textContent = event.data
});
</script>
Now let’s take a look at the content of frame.htm.
<div id="data">
<strong>my todo list</strong>
</div>
<script>
const list = document.createElement('ul');
document.getElementById('data').appendChild(list).setAttribute('id', 'myList');
window.addEventListener("message", event => {
if (event.origin !== "http://differentsubdomain.ezza.work") return;
const item = document.createElement('li');
const itemVal = document.createTextNode(event.data);
document.getElementById('myList').appendChild(item);
item.appendChild(itemVal);
event.source.postMessage(document.getElementById('myList').childElementCount + ' item(s)', 'http://differentsubdomain.ezza.work');
});
</script>
Yes, we’ve implemented the origin checks, verified the return message via the previous message source, and now we have pages and frames capable of exchanging messages with each other.
We now know that data can be passed between windows and frames. This can also be used for different purposes. I’ll also discuss various usage examples related to this topic in the future.
Footnotes
- Bedri Doğan Emir. (2017). Document Parser Object Model (Browser Object Model / BOM) ↩
- The Window Object. w3schools.com ↩
- Window frames Property. w3schools.com ↩
- Window frameElement Property. w3schools.com ↩
- Window postMessage(). MDN web docs ↩
- How do you use window.postMessage across domains?. StackOverflow ↩
- JavaScript.info. (2020). Cross-window communication ↩
- MessageEvent MDN web docs ↩
- David Walsh. (2010) HTML5’s window.postMessage API ↩