Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / chromevox / common / interframe.js
blob8d7b7fc117f55da7bb99c25783118bbdb72e86ac
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /**
6  * @fileoverview Tools for interframe communication. To use this class, every
7  * window that wants to communicate with its child iframes should enumerate
8  * them using document.getElementsByTagName('iframe'), create an ID to
9  * associate with that iframe, then call cvox.Interframe.sendIdToIFrame
10  * on each of them. Then use cvox.Interframe.sendMessageToIFrame to send
11  * messages to that iframe and cvox.Interframe.addListener to receive
12  * replies. When a reply is received, it will automatically contain the ID of
13  * that iframe as a parameter.
14  *
15  */
17 goog.provide('cvox.Interframe');
19 goog.require('cvox.ChromeVoxJSON');
20 goog.require('cvox.DomUtil');
22 /**
23  * @constructor
24  */
25 cvox.Interframe = function() {
28 /**
29  * The prefix of all interframe messages.
30  * @type {string}
31  * @const
32  */
33 cvox.Interframe.IF_MSG_PREFIX = 'cvox.INTERFRAME:';
35 /**
36  * The message used to set the ID of a child frame so that it can send replies
37  * to its parent frame.
38  * @type {string}
39  * @const
40  */
41 cvox.Interframe.SET_ID = 'cvox.INTERFRAME_SET_ID';
43 /**
44  * The message used by a child frame to acknowledge an id was set (sent to its
45  * parent frame.
46  * @type {string}
47  * @const
48  */
49 cvox.Interframe.ACK_SET_ID = 'cvox.INTERFRAME_ACK_SET_ID';
51 /**
52  * The ID of this window (relative to its parent farme).
53  * @type {number|string|undefined}
54  */
55 cvox.Interframe.id;
57 /**
58  * Array of functions that have been registered as listeners to interframe
59  * messages send to this window.
60  * @type {Array<function(Object)>}
61  */
62 cvox.Interframe.listeners = [];
64 /**
65  * Maps an id to a function which gets called when a frame first sends an ack
66  * for a set id msg.
67  @dict {!Object<number|string, function()>}
68  * @private
69  */
70 cvox.Interframe.idToCallback_ = {};
72 /**
73  * Flag for unit testing. When false, skips over iframe.contentWindow check
74  * in sendMessageToIframe. This is needed because in the wild, ChromeVox may
75  * not have access to iframe.contentWindow due to the same-origin security
76  * policy. There is no reason to set this outside of a test.
77  * @type {boolean}
78  */
79 cvox.Interframe.allowAccessToIframeContentWindow = true;
81 /**
82  * Initializes the cvox.Interframe module. (This is called automatically.)
83  */
84 cvox.Interframe.init = function() {
85   cvox.Interframe.messageListener = function(event) {
86     if (typeof event.data === 'string' &&
87         event.data.indexOf(cvox.Interframe.IF_MSG_PREFIX) == 0) {
88       var suffix = event.data.substr(cvox.Interframe.IF_MSG_PREFIX.length);
89       var message = /** @type {Object} */ (
90           cvox.ChromeVoxJSON.parse(suffix));
91       if (message['command'] == cvox.Interframe.SET_ID) {
92         cvox.Interframe.id = message['id'];
93         message['command'] = cvox.Interframe.ACK_SET_ID;
94         cvox.Interframe.sendMessageToParentWindow(message);
95       } else if (message['command'] == cvox.Interframe.ACK_SET_ID) {
96         cvox.Interframe.id = message['id'];
97         var callback = cvox.Interframe.idToCallback_[cvox.Interframe.id];
98         callback();
99       }
100       for (var i = 0, listener; listener = cvox.Interframe.listeners[i]; i++) {
101         listener(message);
102       }
103     }
104     return false;
105   };
106   window.addEventListener('message', cvox.Interframe.messageListener, true);
110  * Unregister the main window event listener. Intended for clean unit testing;
111  * normally there's no reason to call this outside of a test.
112  */
113 cvox.Interframe.shutdown = function() {
114   window.removeEventListener('message', cvox.Interframe.messageListener, true);
118  * Register a function to listen to all interframe communication messages.
119  * Messages from a child frame will have a parameter 'id' that you assigned
120  * when you called cvox.Interframe.sendIdToIFrame.
121  * @param {function(Object)} listener The listener function.
122  */
123 cvox.Interframe.addListener = function(listener) {
124   cvox.Interframe.listeners.push(listener);
128  * Send a message to another window.
129  * @param {Object} message The message to send.
130  * @param {Window} window The window to receive the message.
131  */
132 cvox.Interframe.sendMessageToWindow = function(message, window) {
133   var encodedMessage = cvox.Interframe.IF_MSG_PREFIX +
134       cvox.ChromeVoxJSON.stringify(message, null, null);
135   window.postMessage(encodedMessage, '*');
139  * Send a message to another iframe.
140  * @param {Object} message The message to send. The message must have an 'id'
141  *     parameter in order to be sent.
142  * @param {HTMLIFrameElement} iframe The iframe to send the message to.
143  */
144 cvox.Interframe.sendMessageToIFrame = function(message, iframe) {
145   if (cvox.Interframe.allowAccessToIframeContentWindow &&
146       iframe.contentWindow) {
147     cvox.Interframe.sendMessageToWindow(message, iframe.contentWindow);
148     return;
149   }
151   // A content script can't access window.parent, but the page can, so
152   // inject a tiny bit of javascript into the page.
153   var encodedMessage = cvox.Interframe.IF_MSG_PREFIX +
154       cvox.ChromeVoxJSON.stringify(message, null, null);
155   var script = document.createElement('script');
156   script.type = 'text/javascript';
158   // TODO: Make this logic more like makeNodeReference_ inside api.js
159   // (line 126) so we can use an attribute instead of a classname
160   if (iframe.hasAttribute('id') &&
161       document.getElementById(iframe.id) == iframe) {
162     // Ideally, try to send it based on the iframe's existing id.
163     script.innerHTML =
164         'document.getElementById(decodeURI(\'' +
165         encodeURI(iframe.id) + '\')).contentWindow.postMessage(decodeURI(\'' +
166         encodeURI(encodedMessage) + '\'), \'*\');';
167   } else {
168     // If not, add a style name and send it based on that.
169     var styleName = 'cvox_iframe' + message['id'];
170     if (iframe.className === '') {
171       iframe.className = styleName;
172     } else if (iframe.className.indexOf(styleName) == -1) {
173       iframe.className += ' ' + styleName;
174     }
176     script.innerHTML =
177         'document.getElementsByClassName(decodeURI(\'' +
178         encodeURI(styleName) +
179         '\'))[0].contentWindow.postMessage(decodeURI(\'' +
180         encodeURI(encodedMessage) + '\'), \'*\');';
181   }
183   // Remove the script so we don't leave any clutter.
184   document.head.appendChild(script);
185   window.setTimeout(function() {
186     document.head.removeChild(script);
187   }, 1000);
191  * Send a message to the parent window of this window, if any. If the parent
192  * assigned this window an ID, sends back the ID in the reply automatically.
193  * @param {Object} message The message to send.
194  */
195 cvox.Interframe.sendMessageToParentWindow = function(message) {
196   if (!cvox.Interframe.isIframe()) {
197     return;
198   }
200   message['sourceId'] = cvox.Interframe.id;
201   if (window.parent) {
202     cvox.Interframe.sendMessageToWindow(message, window.parent);
203     return;
204   }
206   // A content script can't access window.parent, but the page can, so
207   // use window.location.href to execute a simple line of javascript in
208   // the page context.
209   var encodedMessage = cvox.Interframe.IF_MSG_PREFIX +
210       cvox.ChromeVoxJSON.stringify(message, null, null);
211   window.location.href =
212       'javascript:window.parent.postMessage(\'' +
213       encodeURI(encodedMessage) + '\', \'*\');';
217  * Send the given ID to a child iframe.
218  * @param {number|string} id The ID you want to receive in replies from
219  *     this iframe.
220  * @param {HTMLIFrameElement} iframe The iframe to assign.
221  * @param {function()=} opt_callback Called when a ack msg arrives from the
222  *frame.
223  */
224 cvox.Interframe.sendIdToIFrame = function(id, iframe, opt_callback) {
225   if (opt_callback) {
226     cvox.Interframe.idToCallback_[id] = opt_callback;
227   }
228   var message = {'command': cvox.Interframe.SET_ID, 'id': id};
229   cvox.Interframe.sendMessageToIFrame(message, iframe);
233  * Returns true if inside iframe
234  * @return {boolean} true if inside iframe.
235  */
236 cvox.Interframe.isIframe = function() {
237   return (window != window.parent);
240 cvox.Interframe.init();