Intended Audience
This article is written by a software developer for anyone who is interested in the technical aspects of modern web browsers. Readers do not need any pre-knowledge to understand the content of the article. All you need to know is overview of browser (Browser Fundamentals | Part-2) and basics of HTML & CSS.
Content
- What is a Document?
- What is Cross-Document?
- Why we need to communicate between documents?
- What are the ways to communicate?
- Browser API: postMessage
- Browser API: MessageChannel
- Browser API: BroadcastChannel
What is Document?
In a browser, Document is an object that represents whole web page loaded in it. Each document has a lot of properties associated with it. Few of which are:
- document.URL : gives the associated URL, which this document represents (for new tab, it is 'about:blank').
- document.doctype : represents the type of the document, XML or HTML (default is HTML)
- document.contentType : represents the type of content it holds (default-'application/xml', 'text/html', 'text/plain')
- document.inputEncoding : represents the character encoding of the document (default is 'utf-8')
- and many more...
![Browser Document View](/images/blogs/browser_document.png)
What is Cross-Document?
All documents (web pages) in a browser is self contained and isolated from one another. Any kind of linkage from one document to another will be cross-document. Two web pages loaded from same origin but different URLs, creates different documents.
Why we need to communicate between documents?
Lets say I have an e-commerce website, www.mystore.com. On the home page, I have embedded a product recommendation web page, loaded from another URL. Notice we have two different documents. Now if I want to access (from my parent website), what are the recommended product - how would I do it?
Or lets say I want to open a small pop up window from www.mystore.com to access payment information from my another website www.pay.mystore.com - how would I do it?
What are the ways to communicate?
Now it is clear that we have a need to communicate between documents. Lets see what are the ways to do it.
Directly Access Target Document
This method requires source document to open the target document either through, iFrames or window.open(). An iFrame, opens the target site within the page itself and window.open() opens in another tab or window (as we have seen in previous section).
iFrames
If we open the target document using iframes as below:
<iframe src="https://www.mystore.com/product-recommendation" id="myIframe" />From the parent document (https://www.mystore.com) we can access target document (https://www.mystore.com/product-recommendation) as:
const targetDocument = document.getElementById('myIframe').contentWindow.documentThe document exposes different properties on different types of elements. The .contentWindow property is exposed on iframe element to access the content (document) of the web page loaded by the iframe. Also from the target document (https://www.mystore.com/product-recommendation) we can access parent document (https://www.mystore.com) as:
const parentDocument = window.parent
window.open()
If we open the target document using window.open() as below:
var popup = window.open("https://www.mystore.com/product-recommendation","mypopup","width=500,height=300");From the parent document (https:s̄//www.mystore.com) we can access target document (https://www.mystore.com/product-recommendation) as:
const targetDocument = popup.documentWhenever we opens a pop-up, it returns a javascript object which contains document property, referencing the actual document of the opened webpage in the pop-up. Also from the target document (https://www.mystore.com/product-recommendation) we can access parent document (https://www.mystore.com) as:
const parentDocument = window.openerThis give the reference to the document of a web page which opened this pop-up.
Direct Access Document Limitation - Same Origin Policy
Since accessing the target document by simply embedding it in a iframe or open in a pop-up window, raises a concern for privacy and security. For example, if I embed any banking site into my site and start accessing the sensitive content. This is a big security concern. For this reason browser has a security policy - Same Origin Policy. Under this policy, browser permits scripts contained in a first web page to access data in a second web page, but only if both web pages have the same origin. In our example, the parent document (https://www.mystore.com) can access target document (https://www.mystore.com/product-recommendation) because the origin is same (protocol, domain and the port).
Send/Receive Events
To overcome the limitation of direct document access, browser allowed a special event named as "message" to communicate between documents. Using this event, any document can send data to any other document without limiting to the origin. This is the true and the only way to communicate cross-document, and that is the purpose of this article. For this, browser has an API (postMessage) to send this event to a document (any origin) with a payload of data. Lets understand more about this API and other wrapper around it (MessageChannel & BroadcastChannel) in next section.
Browser API: postMessage
This method also requires source document to open the target document either through, iFrames or window.open(). We can get the document reference through any of the below method (also discussed in previous sections):
- HTMLElement.contentWindow.document
- window.parent
- window.open().document
- window.opener
![Browser Document Post Message API](/images/blogs/browser_post_message.png)
Sender
We can use the targetDocument reference for sending the "message" event to the targeted document. For example:
targetDocument.postMessage(dataPayload, targetOrigin)The .postMessage() function will dispatch the message event on the targetDocument only if targetOrigin (passed as argument) matches the targetDocument's origin. In this way the sender can control whom to send the event. The value of targetOrigin can be wildcard as well "*" (This matches all origins). Therefore it is always advisable to provide proper targetOrigin.
Reciever
At the recieving end we just need to register an event listener for the "message" event. For example:
window.addEventListener("message", (event) => { // event.origin - origin of the document from which this event is received // Reciever controlling whether to accept the event from this origin or not if (event.origin !== "https://www.mystore.com") return; }, false);Here, the recieved data "event" is everything for us. It is an object that contains all the information about the event. event.data gives the dataPayload passed from parentDocument. event.origin gives the origin (the url) of the parentDocument. Here we can check the intended origin to prevent unwanted event handling from other web pages (targetOrigin as "*") event.source gives reference to the window object of parentDocument. Using this we can establish two-way communication (event.source.postMessage()).
Browser API: MessageChannel
The postMessage api is quite a powerful tool. But there is a more secure way of doing the same through MessageChannel. At the reciever end, instead of manually checking the correct origin, the validation of the origin and the messages are done through a concept of ports. When we create a MessageChannel, it creates two connected MessagePort (port1 and port2). One of the ports is sent to the target window or frame, and messages can be sent and received without repeated origin checking "if (event.origin !== "https://www.mystore.com")".
![Browser Document Message Channel](/images/blogs/browser_message_channel.png)
Sender
Here also, we can use the targetDocument reference for sending the "message" event to the targeted document, but this time we pass port along with the event. For example:
var channel = new MessageChannel(); var port1 = channel.port1; var port2 = channel.port2; targetDocument.postMessage(dataPayload, targetOrigin, [port2])The port1 & port2 are created in a pair. when we send port2 to the target document, we send the origin information as well. This transfer of port eliminates the need to manual repeated checking of origin at the target document.
Reciever
At the recieving end we just need to register an event listener for the "message" event, but in slightly different way. For example:
window.addEventListener("message", (event) => { // event.ports - contains pair of ports var port2 = e.ports[0]; port2.onmessage = (e) => { // processing port2.postMessage('Message received by IFrame'); // reply back }; // No need for below now // if (event.origin !== "https://www.mystore.com") // return; }, false);Everything else is same as postMessage api.
Browser API: BroadcastChannel
The postMessage and MessageChannel APIs provided us one-to-one messaging machanism, where any document can communicate to any other document (irrespective of origin). Therefore, the usage is very generic. BroadcastChannel, on the other hand, has a specific usage. The usage which involves better communication within a browsing context. Browsing context is a context of same origin opened in different windows/tabs, iframes, web workers, and service workers. This API does not require any reference to the target document (iframe or window.open()). It can we visualised as a communication bus that connects all the browsing context (windows/tabs, iframes, web workers, and service workers).
![Browser Document Broadcast Channel](/images/blogs/browser_broadcast_channel.png)
Sender
Here we do not need any targetDocument reference for sending the "message" event to the targeted document. But there is a mutual agreement between documents of same origin to use a common communication channel. For example:
// Connects or creates a channel named 'my_channel' const channel = new BroadcastChannel('my_channel'); channel.postMessage('This is a test message.');This connects to a broadcast channel named 'my_channel' within the browsing context. If 'my_channel' does not exists within the browsing context, it creates one.
Reciever
At the recieving end we just need to register an event listener for the "message" event, and thats it. For example:
// Connects or creates a channel named 'my_channel' const channel = new BroadcastChannel('my_channel'); channel.onmessage = function (ev) { console.log(ev); }
Links & References
- https://dom.spec.whatwg.org/#interface-document
- https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
- https://developer.mozilla.org/en-US/docs/Web/API/Channel_Messaging_API/Using_channel_messaging
- https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API
About Author
I love to shape my ideas into a reality. You can find me either working on a project or having a beer with my close friend. :-)