Only grant permissions to new extensions from sync if they have the expected version
[chromium-blink-merge.git] / chrome / browser / resources / gaia_auth_host / post_message_channel.js
blob0b2b6e7323f946b1deb8ef2dd69ea6f056874317
1 // Copyright 2015 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
7  * Provides a HTML5 postMessage channel to the injected JS to talk back
8  * to Authenticator.
9  */
10 'use strict';
12 <include src="../gaia_auth/channel.js">
14 var PostMessageChannel = (function() {
15   /**
16    * Allowed origins of the hosting page.
17    * @type {Array<string>}
18    */
19   var ALLOWED_ORIGINS = [
20     'chrome://oobe',
21     'chrome://chrome-signin'
22   ];
24   /** @const */
25   var PORT_MESSAGE = 'post-message-port-message';
27   /** @const */
28   var CHANNEL_INIT_MESSAGE = 'post-message-channel-init';
30   /** @const */
31   var CHANNEL_CONNECT_MESSAGE = 'post-message-channel-connect';
33   /**
34    * Whether the script runs in a top level window.
35    */
36   function isTopLevel() {
37     return window === window.top;
38   }
40   /**
41    * A simple event target.
42    */
43   function EventTarget() {
44     this.listeners_ = [];
45   }
47   EventTarget.prototype = {
48     /**
49      * Add an event listener.
50      */
51     addListener: function(listener) {
52       this.listeners_.push(listener);
53     },
55     /**
56      * Dispatches a given event to all listeners.
57      */
58     dispatch: function(e) {
59       for (var i = 0; i < this.listeners_.length; ++i) {
60         this.listeners_[i].call(undefined, e);
61       }
62     }
63   };
65   /**
66    * ChannelManager handles window message events by dispatching them to
67    * PostMessagePorts or forwarding to other windows (up/down the hierarchy).
68    * @constructor
69    */
70   function ChannelManager() {
71     /**
72      * Window and origin to forward message up the hierarchy. For subframes,
73      * they defaults to window.parent and any origin. For top level window,
74      * this would be set to the hosting webview on CHANNEL_INIT_MESSAGE.
75      */
76     this.upperWindow = isTopLevel() ? null : window.parent;
77     this.upperOrigin = isTopLevel() ? '' : '*';
79     /**
80      * Channle Id to port map.
81      * @type {Object<number, PostMessagePort>}
82      */
83     this.channels_ = {};
85     /**
86      * Deferred messages to be posted to |upperWindow|.
87      * @type {Array}
88      */
89     this.deferredUpperWindowMessages_ = [];
91     /**
92      * Ports that depend on upperWindow and need to be setup when its available.
93      */
94     this.deferredUpperWindowPorts_ = [];
96     /**
97      * Whether the ChannelManager runs in daemon mode and accepts connections.
98      */
99     this.isDaemon = false;
101     /**
102      * Fires when ChannelManager is in listening mode and a
103      * CHANNEL_CONNECT_MESSAGE is received.
104      */
105     this.onConnect = new EventTarget();
107     window.addEventListener('message', this.onMessage_.bind(this));
108   }
110   ChannelManager.prototype = {
111     /**
112      * Gets a global unique id to use.
113      * @return {number}
114      */
115     createChannelId_: function() {
116       return (new Date()).getTime();
117     },
119     /**
120      * Posts data to upperWindow. Queue it if upperWindow is not available.
121      */
122     postToUpperWindow: function(data) {
123       if (this.upperWindow == null) {
124         this.deferredUpperWindowMessages_.push(data);
125         return;
126       }
128       this.upperWindow.postMessage(data, this.upperOrigin);
129     },
131     /**
132      * Creates a port and register it in |channels_|.
133      * @param {number} channelId
134      * @param {string} channelName
135      * @param {DOMWindow=} opt_targetWindow
136      * @param {string=} opt_targetOrigin
137      */
138     createPort: function(
139         channelId, channelName, opt_targetWindow, opt_targetOrigin) {
140       var port = new PostMessagePort(channelId, channelName);
141       if (opt_targetWindow)
142         port.setTarget(opt_targetWindow, opt_targetOrigin);
143       this.channels_[channelId] = port;
144       return port;
145     },
147     /*
148      * Returns a message forward handler for the given proxy port.
149      * @private
150      */
151     getProxyPortForwardHandler_: function(proxyPort) {
152       return function(msg) { proxyPort.postMessage(msg); };
153     },
155     /**
156      * Creates a forwarding porxy port.
157      * @param {number} channelId
158      * @param {string} channelName
159      * @param {!DOMWindow} targetWindow
160      * @param {!string} targetOrigin
161      */
162     createProxyPort: function(
163         channelId, channelName, targetWindow, targetOrigin) {
164       var port = this.createPort(
165           channelId, channelName, targetWindow, targetOrigin);
166       port.onMessage.addListener(this.getProxyPortForwardHandler_(port));
167       return port;
168     },
170     /**
171      * Creates a connecting port to the daemon and request connection.
172      * @param {string} name
173      * @return {PostMessagePort}
174      */
175     connectToDaemon: function(name) {
176       if (this.isDaemon) {
177         console.error(
178             'Error: Connecting from the daemon page is not supported.');
179         return;
180       }
182       var port = this.createPort(this.createChannelId_(), name);
183       if (this.upperWindow) {
184         port.setTarget(this.upperWindow, this.upperOrigin);
185       } else {
186         this.deferredUpperWindowPorts_.push(port);
187       }
189       this.postToUpperWindow({
190         type: CHANNEL_CONNECT_MESSAGE,
191         channelId: port.channelId,
192         channelName: port.name
193       });
194       return port;
195     },
197     /**
198      * Dispatches a 'message' event to port.
199      * @private
200      */
201     dispatchMessageToPort_: function(e) {
202       var channelId = e.data.channelId;
203       var port = this.channels_[channelId];
204       if (!port) {
205         console.error('Error: Unable to dispatch message. Unknown channel.');
206         return;
207       }
209       port.handleWindowMessage(e);
210     },
212     /**
213      * Window 'message' handler.
214      */
215     onMessage_: function(e) {
216       if (typeof e.data != 'object' ||
217           !e.data.hasOwnProperty('type')) {
218         return;
219       }
221       if (e.data.type === PORT_MESSAGE) {
222         // Dispatch port message to ports if this is the daemon page or
223         // the message is from upperWindow. In case of null upperWindow,
224         // the message is assumed to be forwarded to upperWindow and queued.
225         if (this.isDaemon ||
226             (this.upperWindow && e.source === this.upperWindow)) {
227           this.dispatchMessageToPort_(e);
228         } else {
229           this.postToUpperWindow(e.data);
230         }
231       } else if (e.data.type === CHANNEL_CONNECT_MESSAGE) {
232         var channelId = e.data.channelId;
233         var channelName = e.data.channelName;
235         if (this.isDaemon) {
236           var port = this.createPort(
237               channelId, channelName, e.source, e.origin);
238           this.onConnect.dispatch(port);
239         } else {
240           this.createProxyPort(channelId, channelName, e.source, e.origin);
241           this.postToUpperWindow(e.data);
242         }
243       } else if (e.data.type === CHANNEL_INIT_MESSAGE) {
244         if (ALLOWED_ORIGINS.indexOf(e.origin) == -1)
245           return;
247         this.upperWindow = e.source;
248         this.upperOrigin = e.origin;
250         for (var i = 0; i < this.deferredUpperWindowMessages_.length; ++i) {
251           this.upperWindow.postMessage(this.deferredUpperWindowMessages_[i],
252                                        this.upperOrigin);
253         }
254         this.deferredUpperWindowMessages_ = [];
256         for (var i = 0; i < this.deferredUpperWindowPorts_.length; ++i) {
257           this.deferredUpperWindowPorts_[i].setTarget(this.upperWindow,
258                                                       this.upperOrigin);
259         }
260         this.deferredUpperWindowPorts_ = [];
261       }
262     }
263   };
265   /**
266    * Singleton instance of ChannelManager.
267    * @type {ChannelManager}
268    */
269   var channelManager = new ChannelManager();
271   /**
272    * A HTML5 postMessage based port that provides the same port interface
273    * as the messaging API port.
274    * @param {number} channelId
275    * @param {string} name
276    */
277   function PostMessagePort(channelId, name) {
278     this.channelId = channelId;
279     this.name = name;
280     this.targetWindow = null;
281     this.targetOrigin = '';
282     this.deferredMessages_ = [];
284     this.onMessage = new EventTarget();
285   };
287   PostMessagePort.prototype = {
288     /**
289      * Sets the target window and origin.
290      * @param {DOMWindow} targetWindow
291      * @param {string} targetOrigin
292      */
293     setTarget: function(targetWindow, targetOrigin) {
294       this.targetWindow = targetWindow;
295       this.targetOrigin = targetOrigin;
297       for (var i = 0; i < this.deferredMessages_.length; ++i) {
298         this.postMessage(this.deferredMessages_[i]);
299       }
300       this.deferredMessages_ = [];
301     },
303     postMessage: function(msg) {
304       if (!this.targetWindow) {
305         this.deferredMessages_.push(msg);
306         return;
307       }
309       this.targetWindow.postMessage({
310         type: PORT_MESSAGE,
311         channelId: this.channelId,
312         payload: msg
313       }, this.targetOrigin);
314     },
316     handleWindowMessage: function(e) {
317       this.onMessage.dispatch(e.data.payload);
318     }
319   };
321   /**
322    * A message channel based on PostMessagePort.
323    * @extends {Channel}
324    * @constructor
325    */
326   function PostMessageChannel() {
327     Channel.apply(this, arguments);
328   };
330   PostMessageChannel.prototype = {
331     __proto__: Channel.prototype,
333     /** @override */
334     connect: function(name) {
335       this.port_ = channelManager.connectToDaemon(name);
336       this.port_.onMessage.addListener(this.onMessage_.bind(this));
337     },
338   };
340   /**
341    * Initialize webview content window for postMessage channel.
342    * @param {DOMWindow} webViewContentWindow Content window of the webview.
343    */
344   PostMessageChannel.init = function(webViewContentWindow) {
345     webViewContentWindow.postMessage({
346       type: CHANNEL_INIT_MESSAGE
347     }, '*');
348   };
350   /**
351    * Run in daemon mode and listen for incoming connections. Note that the
352    * current implementation assumes the daemon runs in the hosting page
353    * at the upper layer of the DOM tree. That is, all connect requests go
354    * up the DOM tree instead of going into sub frames.
355    * @param {function(PostMessagePort)} callback Invoked when a connection is
356    *     made.
357    */
358   PostMessageChannel.runAsDaemon = function(callback) {
359     channelManager.isDaemon = true;
361     var onConnect = function(port) {
362       callback(port);
363     };
364     channelManager.onConnect.addListener(onConnect);
365   };
367   return PostMessageChannel;
368 })();
370 /** @override */
371 Channel.create = function() {
372   return new PostMessageChannel();