JavaScript Cross-window Communication

The Same Origin policy restricts the windows access to one another. That means, if a user has opened two pages: the first one from daivd-brown.com and the second one - gmail.com, then they couldn't want a script from daivd-brown.com for reading a mail from gmail.com. The purpose of such a policy is to protect users from information theft.

Describing the Same Origin

Two URLs are considered to have the same origin when their protocol, domain, and port are the same.

Here are examples of same-origin URLs:

  • http://oursite.com
  • http://oursite.com/
  • http://oursite.com/my/page.html

Now, let’s see examples of URLs that don’t have the same origin:

  • http://site.org
  • http://www.site.com
  • https://site.com
  • http://site.com:8080

According to the Same Origin policy:

  • In case of having a reference to another window ( for example, a popup created with or a window inside <iframe>, and the window is from the same origin, then complete access to that window is provided.
  • In another way, in case of coming from another origin, then the content of the window can’t be accessed. Only the location can be an exception: it can be modified. But, the location can’t be read.

iFrame

The <iframe> tag can host an independent embedded window that has its window and document objects. They can be accessed with the following properties:
  • iframe.contentWindow for accessing the window within the <iframe>.
  • iframe.contentDocument for getting the document within <iframe>. It’s a shorthand for iframe.contentWindow.document.

While accessing something inside the embedded window, the browser inspects whether the iframe has the same origin. Otherwise, access will be denied. In the example below, you can see an attempt to read and write to <iframe> from another origin:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <iframe id="iframe" src="https://example.com"></iframe>
    <script>
      iframe.onload = function() {
        // get the reference to the inner window
        let iframeWindow = iframe.contentWindow; // OK
        try {
          //but not to the document inside it
          let doc = iframe.contentDocument; // ERROR
        } catch(err) {
          alert(err); // Security Error, another origin
        } 
        // also we cannot READ the page  URL in the iframe
        try {
          // Cannot read URL from Location object
          let href = iframe.contentWindow.location.href; // ERROR
        } catch(err) {
          alert(err); // Security Error
        } 
        //can be WRITE to a location (and therefore load something else in the iframe)
        iframe.contentWindow.location = '/'; // OK 
        iframe.onload = null; // clear the handler so that it does not start after changing the location
      };
    </script>
  </body>
</html>

But this code will lead to errors in all circumstances except:

  • Receiving the reference to the inner window iframe.contentWindow .
  • While writing to the location .

Differently, when the <iframe> has the same origin, anything can be done with it, like here:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <!-- iframe from the same site -->
    <iframe id="iframe" src="/"></iframe> 
    <script>
      iframe.onload = function() {
        // just do anything
        iframe.contentDocument.body.prepend("Welcome to W3Docs");
      };
    </script>
  </body>
</html>

Windows on Subdomains: document.domain

So, by default two URLs that have different domains have different origins. But, when windows have the same second-level domain, it is possible to make the browser ignore the difference.

The following code should be run to make it work:

document.domain = 'oursite.com';

Iframe: wrong document pitfall

If the iframe is from the same origin with a possibility of accessing its document, then there exists a pitfall. It is an essential thing to know but doesn’t relate to cross-origin requests.

So, doing something with the document at once will be lost:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <iframe id="iframe" src="/" ></iframe>
    <script>
      let oldDocs = iframe.contentDocument;
      iframe.onload = function() {
        let newDocs = iframe.contentDocument;
        // the loaded document is different from the original
        alert(oldDocs == newDocs); // false
      };
    </script>
  </body>
</html>

That’s the wrong document. Setting any event handlers on it will be ignored. The right document is where iframe.onload occurs. However, it occurs only when the total iframe with all the resources is loaded.

With setInterval the moment can be caught earlier, like here:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <iframe id="iframe" src="/" ></iframe>
    <script>
      let oldDocs = iframe.contentDocument;
      // every 100 ms  we check if the document is new 
      let timer = setInterval(() => {
        let newDocs = iframe.contentDocument;
        if (newDocs == oldDocs) return;
        alert("New document is here");
        clearInterval(timer); // cancel setInterval, no longer needed
      }, 500);
    </script>
  </body>
</html>

Window.frames

There is an alternative option of getting a window object for <iframe>.

You can get it from the named collection window.frames :

  1. By number window.frames[0] .
  2. By name window.frames.iframeName.

Let’s take a look at an example:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <iframe id="iframe" src="/" style="height:100px" name="win"></iframe>
    <script>
      alert(iframe.contentWindow == frames[0]); // true
      alert(iframe.contentWindow == frames.win); // true
    </script>
  </body>
</html>

Cross-window Messaging

Windows can talk to each other no matter what origin they have, with the help of the postMessage interface.

So, with it, a window from david-brown.com can exchange information with gmail.com, but only when they both agree and call the matching JavaScript functions.

It makes the operation safe for users.

The interface includes two parts that are postMessage and onmessage.

PostMessage

The psotMessage method is called when the window intends to send a message. That is, when you need to send a message to win, you need to call win.postMessage(data, targetOrigin).

It includes the following arguments:

  • data: it includes the data to send. A structured cloning algorithm is used to clone data.
  • targetOrigin: it indicates the target window origin. So, only a window from a particular origin will receive the message.

Let’s check out an example:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <iframe src="http://example.com" name="example">
    <script>
      let win = window.frames.example;
      win.postMessage("message", "http://example.com");
    </script>
  </body>
</html>

Also, you can set targetOrigin to * if the check is not necessary for you:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
  </head>
  <body>
    <iframe src="http://example.com" name="example">
    <script>
      let win = window.frames.example;
      win.postMessage("message", "*");
    </script>
  </body>
</html>

Onmessage

The target window must have a handler on the message event to get a message. It occurs once postMessage is called.

The properties of the event object are as follows:

  • data: the postMessage data.
  • origin: the sender origin. For instance, https://www.w3docs.com/.
  • source: a reference to the window of the sender.

Here, addEventListener should be applied for assigning the handler.

The window.onmessage syntax will not operate.

The example will look like this:

window.addEventListener("message", function (event) {
  if (event.origin != 'http://javascript.info') {
    // something from an unknown domain, let's ignore it
    return;
  }
  alert("received: " + event.data);
  // can message back using event.source.postMessage(...)
});

Summary

For calling methods and accessing another window content, a reference is necessary.

The following references are used for popups:

  • window.open for the opener window.
  • window.opener from the popup.

In case the windows have the same origin, they can do anything to one another. Otherwise, it’s only possible to change the location and post a message to it.

The postMessage interface helps two windows with different origins to interact.

  1. The targetWin.postMessage(data, targetOrigin) is called by the sender.
  2. If targetOrigin is not '*', then the browser checks whether there is targetOrigin in the targetWin window.
  3. If it’s there, the message event is triggered by the targetWin .

Practice Your Knowledge

What is true about cross-window communication in JavaScript?

Quiz Time: Test Your Skills!

Ready to challenge what you've learned? Dive into our interactive quizzes for a deeper understanding and a fun way to reinforce your knowledge.

Do you find this helpful?