Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / remoting / webapp / app_remoting / js / app_connected_view.js
blobbe1e0aba6f670e30c1ea73ed5cc12c8c741a894d
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  * Implements a basic UX control for a connected app remoting session.
8  */
10 /** @suppress {duplicate} */
11 var remoting = remoting || {};
13 (function() {
15 'use strict';
17 /**
18  * Interval to test the connection speed.
19  * @const {number}
20  */
21 var CONNECTION_SPEED_PING_INTERVAL_MS = 10 * 1000;
23 /**
24  * Interval to refresh the google drive access token.
25  * @const {number}
26  */
27 var DRIVE_ACCESS_TOKEN_REFRESH_INTERVAL_MS = 15 * 60 * 1000;
29 /**
30  * @param {HTMLElement} containerElement
31  * @param {remoting.WindowShape} windowShape
32  * @param {remoting.ConnectionInfo} connectionInfo
33  * @param {base.WindowMessageDispatcher} windowMessageDispatcher
34  *
35  * @constructor
36  * @implements {base.Disposable}
37  * @implements {remoting.ProtocolExtension}
38  */
39 remoting.AppConnectedView = function(containerElement, windowShape,
40                                      connectionInfo, windowMessageDispatcher) {
41   /** @private */
42   this.plugin_ = connectionInfo.plugin();
44   /** @private */
45   this.host_ = connectionInfo.host();
47   var menuAdapter = new remoting.ContextMenuChrome();
49   // Initialize the context menus.
50   if (!remoting.platformIsChromeOS()) {
51     menuAdapter = new remoting.ContextMenuDom(
52         document.getElementById('context-menu'), windowShape);
53   }
55   this.contextMenu_ = new remoting.ApplicationContextMenu(
56       menuAdapter, this.plugin_, connectionInfo.session(), windowShape);
57   this.contextMenu_.setHostId(connectionInfo.host().hostId);
59   /** @private */
60   this.keyboardLayoutsMenu_ = new remoting.KeyboardLayoutsMenu(menuAdapter);
62   /** @private */
63   this.windowActivationMenu_ = new remoting.WindowActivationMenu(menuAdapter);
65   var baseView = new remoting.ConnectedView(
66       this.plugin_, containerElement,
67       containerElement.querySelector('.mouse-cursor-overlay'));
69   var windowShapeHook = new base.EventHook(
70       this.plugin_.hostDesktop(),
71       remoting.HostDesktop.Events.shapeChanged,
72       windowShape.setDesktopRects.bind(windowShape));
74   var desktopSizeHook = new base.EventHook(
75       this.plugin_.hostDesktop(),
76       remoting.HostDesktop.Events.sizeChanged,
77       this.onDesktopSizeChanged_.bind(this));
79   /** @private */
80   this.disposables_ =
81       new base.Disposables(baseView, windowShapeHook, desktopSizeHook,
82                            this.contextMenu_, menuAdapter);
84   /** @private */
85   this.supportsGoogleDrive_ = this.plugin_.hasCapability(
86       remoting.ClientSession.Capability.GOOGLE_DRIVE);
88   this.resizeHostToClientArea_();
89   this.plugin_.extensions().register(this);
91   /** @private {remoting.CloudPrintDialogContainer} */
92   this.cloudPrintDialogContainer_ = new remoting.CloudPrintDialogContainer(
93       /** @type {!Webview} */ (document.getElementById('cloud-print-webview')),
94       windowShape, windowMessageDispatcher, baseView);
96   this.disposables_.add(this.cloudPrintDialogContainer_);
99 /**
100  * @return {void} Nothing.
101  */
102 remoting.AppConnectedView.prototype.dispose = function() {
103   this.windowActivationMenu_.setExtensionMessageSender(base.doNothing);
104   this.keyboardLayoutsMenu_.setExtensionMessageSender(base.doNothing);
105   base.dispose(this.disposables_);
109  * Resize the host to the dimensions of the current window.
110  * @private
111  */
112 remoting.AppConnectedView.prototype.resizeHostToClientArea_ = function() {
113   var hostDesktop = this.plugin_.hostDesktop();
114   var desktopScale = this.host_.options.desktopScale;
115   hostDesktop.resize(window.innerWidth * desktopScale,
116                      window.innerHeight * desktopScale,
117                      window.devicePixelRatio);
121  * Adjust the size of the plugin according to the dimensions of the hostDesktop.
123  * @param {{width:number, height:number, xDpi:number, yDpi:number}} hostDesktop
124  * @private
125  */
126 remoting.AppConnectedView.prototype.onDesktopSizeChanged_ =
127     function(hostDesktop) {
128   // The first desktop size change indicates that we can close the loading
129   // window.
130   remoting.LoadingWindow.close();
132   var hostSize = { width: hostDesktop.width, height: hostDesktop.height };
133   var hostDpi = { x: hostDesktop.xDpi, y: hostDesktop.yDpi };
134   var clientArea = { width: window.innerWidth, height: window.innerHeight };
135   var newSize = remoting.Viewport.choosePluginSize(
136       clientArea, window.devicePixelRatio,
137       hostSize, hostDpi, this.host_.options.desktopScale,
138       true /* fullscreen */ , true /* shrinkToFit */ );
140   this.plugin_.element().style.width = newSize.width + 'px';
141   this.plugin_.element().style.height = newSize.height + 'px';
145  * @return {Array<string>}
146  * @override {remoting.ProtocolExtension}
147  */
148 remoting.AppConnectedView.prototype.getExtensionTypes = function() {
149   return ['openURL', 'onWindowRemoved', 'onWindowAdded',
150           'onAllWindowsMinimized', 'setKeyboardLayouts', 'pingResponse'];
154  * @param {function(string,string)} sendMessageToHost Callback to send a message
155  *     to the host.
156  * @override {remoting.ProtocolExtension}
157  */
158 remoting.AppConnectedView.prototype.startExtension = function(
159     sendMessageToHost) {
160   this.windowActivationMenu_.setExtensionMessageSender(sendMessageToHost);
161   this.keyboardLayoutsMenu_.setExtensionMessageSender(sendMessageToHost);
163   remoting.identity.getUserInfo().then(function(userInfo) {
164     sendMessageToHost('setUserDisplayInfo',
165                       JSON.stringify({fullName: userInfo.name}));
166   });
168   var onRestoreHook = new base.ChromeEventHook(
169       chrome.app.window.current().onRestored, function() {
170         sendMessageToHost('restoreAllWindows', '');
171       });
173   var pingTimer = new base.RepeatingTimer(function() {
174     var message = {timestamp: new Date().getTime()};
175     sendMessageToHost('pingRequest', JSON.stringify(message));
176   }, CONNECTION_SPEED_PING_INTERVAL_MS);
178   this.disposables_.add(onRestoreHook, pingTimer);
180   if (this.supportsGoogleDrive_) {
181     this.disposables_.add(new base.RepeatingTimer(
182         this.sendGoogleDriveAccessToken_.bind(this, sendMessageToHost),
183         DRIVE_ACCESS_TOKEN_REFRESH_INTERVAL_MS, true));
184   }
188  * @param {string} command The message command.
189  * @param {Object} message The parsed extension message data.
190  * @override {remoting.ProtocolExtension}
191  */
192 remoting.AppConnectedView.prototype.onExtensionMessage =
193     function(command, message) {
194   switch (command) {
195     case 'openURL':
196       // URL requests from the hosted app are untrusted, so disallow anything
197       // other than HTTP or HTTPS.
198       var url = base.getStringAttr(message, 'url');
199       if (url.indexOf('http:') != 0 && url.indexOf('https:') != 0) {
200         console.error('Bad URL: ' + url);
201       } else {
202         window.open(url);
203       }
204       return true;
206     case 'onWindowRemoved':
207       var id = base.getNumberAttr(message, 'id');
208       this.windowActivationMenu_.remove(id);
209       return true;
211     case 'onWindowAdded':
212       var id = base.getNumberAttr(message, 'id');
213       var title = base.getStringAttr(message, 'title');
214       this.windowActivationMenu_.add(id, title);
215       return true;
217     case 'onAllWindowsMinimized':
218       chrome.app.window.current().minimize();
219       return true;
221     case 'setKeyboardLayouts':
222       var supportedLayouts = base.getArrayAttr(message, 'supportedLayouts');
223       var currentLayout = base.getStringAttr(message, 'currentLayout');
224       console.log('Current host keyboard layout: ' + currentLayout);
225       console.log('Supported host keyboard layouts: ' + supportedLayouts);
226       this.keyboardLayoutsMenu_.setLayouts(supportedLayouts, currentLayout);
227       return true;
229     case 'pingResponse':
230       var then = base.getNumberAttr(message, 'timestamp');
231       var now = new Date().getTime();
232       this.contextMenu_.updateConnectionRTT(now - then);
233       return true;
235     case 'printDocument':
236       var title = base.getStringAttr(message, 'title');
237       var type = base.getStringAttr(message, 'type');
238       var data = base.getStringAttr(message, 'data');
239       if (type == '' || data == '') {
240         console.error('"type" and "data" cannot be empty.');
241         return true;
242       }
244       this.cloudPrintDialogContainer_.printDocument(title, type, data);
245       return true;
246   }
248   return false;
252  * Timer callback to send the access token to the host.
253  * @param {function(string, string)} sendExtensionMessage
254  * @private
255  */
256 remoting.AppConnectedView.prototype.sendGoogleDriveAccessToken_ =
257     function(sendExtensionMessage) {
258   var googleDriveScopes = [
259     'https://docs.google.com/feeds/',
260     'https://www.googleapis.com/auth/drive'
261   ];
262   remoting.identity.getNewToken(googleDriveScopes).then(
263     function(/** string */ token){
264       console.assert(token !== previousToken_,
265                      'getNewToken() returned the same token.');
266       previousToken_ = token;
267       sendExtensionMessage('accessToken', token);
268   }).catch(remoting.Error.handler(function(/** remoting.Error */ error) {
269     console.log('Failed to refresh access token: ' + error.toString());
270   }));
273 // The access token last received from getNewToken. Saved to ensure that we
274 // get a fresh token each time.
275 var previousToken_ = '';
277 })();