Documentation Archive

Developer

WebKit DOM Programming Topics

On This Page

Cross-Document Messaging

As a general rule, scripts loaded by web content served from one origin (host and domain) cannot access web content served by a different origin. This is an important security feature that prevents a multitude of different security attack vectors. However, it also makes it difficult for scripts to interact with one another across these boundaries.

To make communication between documents from different origins easier, the HTML 5 specification adds cross-document messaging. This feature is supported in Safari 4.0 and later.

Posting a Message to a Window

To post a message, you must first obtain the Window object of the document you want to message. In effect, this means that you can post messages only to:

  • other frames or inline frames within your document window (or their descendants if all intermediate frames or inline frames were served from the same origin).

    1. var iFrameObj = document.getElementById('myId');
    2. var windowObj = iFrameObj.contentWindow;
  • windows that your document explicitly opened through JavaScript calls.

    1. var windowObj = window.open(...);
  • the window that contains your document window, the window that contains that window, and so on up to the root window.

    1. var windowObj = window.parent;
  • the window that opened your document.

    1. var windowObj = window.opener;

Once you have obtained the Window object for the target document, you can send it a message with the following code:

  1. windowObj.postMessage('test message', 'http://example.com');

The first parameter is an arbitrary message.

The second parameter is the target origin value. An origin value is just a URL with the path part removed. For example, the origin of a local file is file://. By specifying a target origin, you are saying that that your message should only be delivered if the target window’s current contents came from that origin.

Although you may specify an asterisk (*) wildcard for the target origin (to allow the message to be sent regardless of where the contents of the target window came from), you should do so only if you are certain that it would not be harmful if your message were received by content originating from a different website.

Receiving a Message Posted to a Window

To receive messages, you must add an event listener for the message event type to your document’s window object. To do this, use the following code:

  1. function messageReceive(evt) {
  2. if (evt.origin == 'http://example.com') {
  3. // The message came from an origin that
  4. // your code trusts. Work with the message.
  5. alert('Received data '+evt.data);
  6. // Send a message back to the source:
  7. evt.source.postMessage('response', evt.origin);
  8. } else {
  9. alert('unexpected message from origin '+evt.origin);
  10. }
  11. }
  12. window.addEventListener('message', messageReceive, false);

The message event you receive has three properties of interest:

  • data—the message contents.

  • origin—the domain from which the message was sent (http://example.com in this case).

  • source—the window from which the message was sent.

A Service Discovery Example: Message Boxes

This section contains two code listings: index.html and msg_contents.html that, when combined, implement basic service discovery and data relaying on top of the cross-document messaging architecture.

To use this code, you must first do the following things:

  • Install Safari 4.0 or later, or install a recent WebKit nightly.

  • Save the contents of the two code listings in separate files.

  • In the file msg_contents.html, modify the variable allowed_origins to contain a list of origins from which you plan to serve the index.html or msg_contents.html file.

    For example, if you intend to place this file at http://www.example.org/message_test/msg_contents.html, you should make sure that 'http://www.example.org' (enclosed in quotes) is a key in the allowed_origins object.

    If you only have one machine, turn on web sharing, then use http://localhost as one origin and file:// as the other.

  • In the file msg_contents.html, optionally change the variable root_origin to the actual expected origin of the top level HTML page.

  • Place a copy of the modified msg_contents.html file on the desired server or servers.

  • In the file index.html, replace the allowed_origins declaration with the one from your msg_contents.html file. Then, update the boxes object to provide the URLs for the msg_contents.html files you just put on your servers.

    If you only have one machine, use a file:// URL pointing to the path of the msg_contents.html file. For example, if you placed the file in /Library/WebServer/Documents/message_test/msg_contents.html, the local URL would be file:///Library/WebServer/Documents/message_test/msg_contents.html.

  • Place this modified index.html file on one of the servers and navigate to the URL, or open it as a local file on disk.

Once you have completed these steps, you should see several boxes, one per entry in the boxes object. Each of these boxes should contain a small form with a text input box, a series of checkboxes (one for each of the outer boxes), and a submit button.

If you type something into the text box, check one of the checkboxes, and click submit, the text should appear at the bottom of the window whose name corresponds with the checkbox.

Listing 8-1Cross-document messaging example: index.html
  1. <html><head>
  2. <script language='javascript' type='text/javascript'><!--
  3. /*global alert, navigator, document, window */
  4. var window_list = [];
  5. var origin_list = [];
  6. var boxes = {
  7. local: "http://host1.domain1.top/messages2/msg_contents.html",
  8. remote: "http://host2.domain2.top/messages2/msg_contents.html",
  9. third: "http://host3.domain3.top/messages2/msg_contents.html"
  10. };
  11. var allowed_origins = {
  12. 'http://host1.domain1.top': 1,
  13. 'http://host2.domain2.top': 1,
  14. 'http://host3.domain3.top': 1
  15. };
  16. function smartsplit(string, pattern, count)
  17. {
  18. // alert('string '+string);
  19. // alert('pattern "'+pattern+'"');
  20. // alert('count '+count);
  21. var lastpos = count - 1;
  22. var arr = string.split(/ /);
  23. // alert('AC: '+arr.length+" "+string);
  24. if (arr.length > lastpos) {
  25. var temparr = [];
  26. for (var i=lastpos; i<arr.length; i++) {
  27. temparr[i-lastpos] = arr[i];
  28. arr[i] = undefined;
  29. }
  30. arr[lastpos] = temparr.join(pattern);
  31. }
  32. return arr;
  33. }
  34. function listWindows()
  35. {
  36. var retstring = "";
  37. for (var i in origin_list) {
  38. if (origin_list.hasOwnProperty(i)) {
  39. // alert('UUID: '+i+' Origin: '+origin_list[i]);
  40. retstring += i+' '+origin_list[i]+'\n';
  41. }
  42. }
  43. return retstring;
  44. }
  45. function messageReceive(evt) {
  46. var windowlist;
  47. if (evt.origin === null || evt.origin in allowed_origins) {
  48. var arr = smartsplit(evt.data, " ", 2);
  49. if (arr[0] == 'sendto') {
  50. // usage: sendto UUID remote_origin message
  51. arr = smartsplit(evt.data, " ", 4);
  52. var remote_window = window_list[arr[1]];
  53. remote_window.postMessage('sendto_output '+arr[3], arr[2]);
  54. } else if (arr[0] == 'register') {
  55. // usage register UUID
  56. var name = arr[1];
  57. if (window_list[name]) {
  58. // name conflict.
  59. var add=1;
  60. while (window_list[name+'_'+add]) {
  61. add++;
  62. }
  63. name = name+'_'+add;
  64. evt.source.postMessage("register_newid "+name, evt.origin);
  65. }
  66. window_list[name] = evt.source;
  67. origin_list[name] = evt.origin;
  68. windowlist = listWindows();
  69. for (var windowid in window_list) {
  70. if (window_list.hasOwnProperty(windowid)) {
  71. // alert('windowid: '+windowid);
  72. window_list[windowid].postMessage("list_output "+windowlist, origin_list[windowid]);
  73. }
  74. }
  75. } else if (arr[0] == 'list') {
  76. // usage list
  77. windowlist = listWindows();
  78. evt.source.postMessage("list_output "+windowlist, evt.origin);
  79. } else {
  80. alert('unknown command '+arr[0]);
  81. }
  82. } else {
  83. alert('unexpected message from origin '+evt.origin);
  84. }
  85. }
  86. function dosetup()
  87. {
  88. if (navigator.userAgent.match(/Safari/)) {
  89. var version = parseFloat(navigator.userAgent.replace(/.*AppleWebKit\//, "").replace(/[^0-9.].*$/, ""));
  90. if (version < 528) {
  91. alert('WebKit version '+version+' does not support this application.');
  92. }
  93. }
  94. // for (var i=0; i<nchannels; i++) {
  95. // alert('setup channel '+i);
  96. // channel[i] = new MessageChannel();
  97. // }
  98. window.addEventListener('message', messageReceive, false);
  99. var boxstr = "";
  100. for (var i in boxes) {
  101. if (boxes.hasOwnProperty(i)) {
  102. boxstr += "<iframe height='600' id='"+i+"' src='"+boxes[i]+"'></iframe>\n";
  103. }
  104. }
  105. var boxlistdiv = document.getElementById('boxlist');
  106. boxlistdiv.innerHTML = boxstr;
  107. }
  108. dosetup();
  109. --></script>
  110. </head>
  111. <body onload='dosetup();'>
  112. <div id='boxlist'>
  113. </div>
  114. </body>
  115. </html>
Listing 8-2Cross-document messaging example: msg_contents.html
  1. <html><head>
  2. <script language='javascript' type='text/javascript'><!--
  3. /*global alert, document, window, navigator */
  4. var allowed_origins = {
  5. 'http://stavromula-beta.apple.com': 1,
  6. 'http://holst.apple.com': 1
  7. };
  8. var received_root_origin = '';
  9. var root_origin = '*';
  10. function smartsplit(string, pattern, count)
  11. {
  12. // alert('string '+string);
  13. // alert('pattern "'+pattern+'"');
  14. // alert('count '+count);
  15. var lastpos = count - 1;
  16. var arr = string.split(/ /);
  17. // alert('AC: '+arr.length+" "+string);
  18. if (arr.length > lastpos) {
  19. var temparr = [];
  20. for (var i=lastpos; i<arr.length; i++) {
  21. temparr[i-lastpos] = arr[i];
  22. arr[i] = undefined;
  23. }
  24. arr[lastpos] = temparr.join(pattern); }
  25. return arr;
  26. }
  27. function mkcheckbox(inpstr)
  28. {
  29. var str = "";
  30. var arr = inpstr.split("\n");
  31. for (var entid in arr) {
  32. if (arr.hasOwnProperty(entid)) {
  33. var ent = arr[entid];
  34. if (ent !== "") {
  35. // alert('ent: '+ent);
  36. var bits = smartsplit(ent, " ", 2);
  37. str += "<input type=checkbox name='"+ent+"'>"+bits[0]+"</input>\n";
  38. }
  39. }
  40. }
  41. return str;
  42. }
  43. function messageReceive(evt) {
  44. if (evt.origin === null || evt.origin in allowed_origins) {
  45. // The message came from an origin that
  46. // your code trusts. Work with the message.
  47. // alert('Received data: '+evt.data);
  48. // var tmp = 'Test this, please';
  49. // var x = smartsplit(tmp, " ", 2);
  50. // alert('x[0] = '+x[0]);
  51. // alert('x[1] = '+x[1]);
  52. // alert('x[2] = '+x[2]);
  53. var arr = smartsplit(evt.data, " ", 2);
  54. if (arr[0] == 'list_output') {
  55. if (evt.origin == root_origin || root_origin == '*') {
  56. var div2 = document.getElementById('temp2');
  57. div2.innerHTML = mkcheckbox(arr[1]);
  58. received_root_origin = evt.origin;
  59. // alert('arr[1] = '+arr[1]);
  60. } else {
  61. alert('received list_output message from unexpected origin: '+evt.origin);
  62. }
  63. } else if (arr[0] == 'sendto_output') {
  64. // alert('Received data: '+evt.data);
  65. var div3 = document.getElementById('temp3');
  66. div3.innerHTML += arr[1]+'<br />\n';
  67. } else if (arr[0] == 'register_newid') {
  68. var mydiv = document.getElementById('temp');
  69. mydiv.innerHTML = arr[1]+" box";
  70. }
  71. // Send a message back to the source:
  72. // evt.source.postMessage('response', evt.origin);
  73. } else {
  74. alert('unexpected message from origin '+evt.origin);
  75. }
  76. }
  77. function setup_listener()
  78. {
  79. window.addEventListener('message', messageReceive, false);
  80. }
  81. function setup()
  82. {
  83. var mydiv = document.getElementById('temp');
  84. // mydiv.innerHTML = 'Test';
  85. // mydiv.innerHTML = ' '+bigdoc;
  86. // If we can access the document object, we are locally loaded and should
  87. // talk to the remotely-loaded window. Otherwise, the reverse
  88. // is true.
  89. var myid = '';
  90. if (window.parent.document) {
  91. myid = 'local';
  92. } else {
  93. myid = 'remote';
  94. }
  95. mydiv.innerHTML = myid+" box";
  96. // window.addEventListener('message', messageReceive, false);
  97. setup_listener();
  98. var topwindow = window;
  99. while (topwindow.parent && (topwindow.parent != topwindow)) { topwindow = topwindow.parent; }
  100. topwindow.postMessage('register '+myid, root_origin);
  101. // window.parent.postMessage('list', '*');
  102. }
  103. function sendmsg()
  104. {
  105. var message = document.getElementById('sendtext').value;
  106. var mydiv2 = document.getElementById('temp2');
  107. var checkboxes = mydiv2.children;
  108. // alert('checkboxes: '+checkboxes);
  109. var topwindow = window;
  110. while (topwindow.parent && (topwindow.parent != topwindow)) { topwindow = topwindow.parent; }
  111. for (var boxid in checkboxes) {
  112. if (checkboxes.hasOwnProperty(boxid)) {
  113. var checkbox = checkboxes[boxid];
  114. // alert('checkbox: '+checkbox);
  115. if (checkbox.tagName == "INPUT") {
  116. // alert('input');
  117. if (checkbox.checked) {
  118. // alert('checked');
  119. var arr = smartsplit(checkbox.name, " ", 2);
  120. var uuid = arr[0];
  121. var origin = arr[1];
  122. // alert('send to: '+uuid+' origin '+origin);
  123. // alert('topwindow is '+topwindow);
  124. topwindow.postMessage('sendto '+uuid+' '+origin+' '+message, received_root_origin);
  125. }
  126. }
  127. }
  128. }
  129. return false;
  130. }
  131. --></script>
  132. </head><body onload='setup();'>
  133. <div id='temp'></div>
  134. <form onsubmit='return false;'>
  135. <input type='text' id='sendtext'></input>
  136. <input type='submit' value='submit' onclick='sendmsg();'></input>
  137. <div id='temp2'></div>
  138. </form>
  139. <div id='temp3'></div>
  140. </body></html>

You can create numerous interesting extensions on top of this sort of design. For example, you might add a command message that asks the window at the other end what commands it supports, then communicate with it if you share a common set of commands. The possibilities are limitless.

Security Considerations

There are several key things you should be aware of when using cross-document messaging:

  • Obtaining Window objects for other windows is not always easy. Scripts running in a window, frame, or iframe element served from one origin cannot access the DOM tree of documents served from a different origin, and thus cannot get access to the Window objects of other iframe elements within such a window.

    Among other things, this means that two iframe elements from different domains cannot directly obtain each other’s Window objects because one or the other is (by definition) from a different origin than the document containing the iframe elements. In this situation, there are two possible solutions.

    The easiest solution is to have both windows discover each other using the parent window as a communications hub. This solution is also the most general solution because it works even if neither window can see the element containing the other window.

    Alternatively, the window with access to the parent window’s DOM tree could discover the other one and initiate communication. By doing so, the second window receives the first window’s Window object by way of the source field in the event object.

  • Sending data to other windows can be dangerous, particularly if that information contains login information or other sensitive data. You should almost always take advantage of the target origin field when sending messages to avoid interception by content served by other sites. You should only use the wildcard asterisk (*) target origin value if you are absolutely sure that the data is harmless.

  • Receiving data from other windows can also be dangerous. You should generally check the origin field when receiving messages to make sure that the data was sent from a website that you trust to some degree.

    Where possible, you should also check any received data for validity before using it. In particular, you should generally avoid executing JavaScript code received from another window (with the possible exception of JSON objects after careful validity checking).

As with any software, for maximum reliability and security, you should write your output code carefully to minimize the risk of causing problems for other code, and you should write your code under the assumption that other code is maliciously trying to attack your code, and thus you should perform type, bounds, and other sanity checks accordingly.