OLD | NEW |
| (Empty) |
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. | |
4 | |
5 /** | |
6 * @fileoverview | |
7 * Implements a basic UX control for a connected app remoting session. | |
8 */ | |
9 | |
10 /** @suppress {duplicate} */ | |
11 var remoting = remoting || {}; | |
12 | |
13 (function() { | |
14 | |
15 'use strict'; | |
16 | |
17 /** | |
18 * Interval to test the connection speed. | |
19 * @const {number} | |
20 */ | |
21 var CONNECTION_SPEED_PING_INTERVAL_MS = 10 * 1000; | |
22 | |
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; | |
28 | |
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(); | |
43 | |
44 /** @private */ | |
45 this.host_ = connectionInfo.host(); | |
46 | |
47 var menuAdapter = new remoting.ContextMenuChrome(); | |
48 | |
49 // Initialize the context menus. | |
50 if (!remoting.platformIsChromeOS()) { | |
51 menuAdapter = new remoting.ContextMenuDom( | |
52 base.getHtmlElement('context-menu'), windowShape); | |
53 } | |
54 | |
55 this.contextMenu_ = new remoting.ApplicationContextMenu( | |
56 menuAdapter, this.plugin_, connectionInfo.session(), windowShape); | |
57 this.contextMenu_.setHostId(connectionInfo.host().hostId); | |
58 | |
59 /** @private */ | |
60 this.keyboardLayoutsMenu_ = new remoting.KeyboardLayoutsMenu(menuAdapter); | |
61 | |
62 /** @private */ | |
63 this.windowActivationMenu_ = new remoting.WindowActivationMenu(menuAdapter); | |
64 | |
65 var baseView = new remoting.ConnectedView( | |
66 this.plugin_, containerElement, | |
67 containerElement.querySelector('.mouse-cursor-overlay')); | |
68 | |
69 var windowShapeHook = new base.EventHook( | |
70 this.plugin_.hostDesktop(), | |
71 remoting.HostDesktop.Events.shapeChanged, | |
72 windowShape.setDesktopRects.bind(windowShape)); | |
73 | |
74 var desktopSizeHook = new base.EventHook( | |
75 this.plugin_.hostDesktop(), | |
76 remoting.HostDesktop.Events.sizeChanged, | |
77 this.onDesktopSizeChanged_.bind(this)); | |
78 | |
79 /** @private */ | |
80 this.disposables_ = | |
81 new base.Disposables(baseView, windowShapeHook, desktopSizeHook, | |
82 this.contextMenu_, menuAdapter); | |
83 | |
84 /** @private */ | |
85 this.supportsGoogleDrive_ = this.plugin_.hasCapability( | |
86 remoting.ClientSession.Capability.GOOGLE_DRIVE); | |
87 | |
88 this.resizeHostToClientArea_(); | |
89 this.plugin_.extensions().register(this); | |
90 | |
91 /** @private {remoting.CloudPrintDialogContainer} */ | |
92 this.cloudPrintDialogContainer_ = new remoting.CloudPrintDialogContainer( | |
93 /** @type {!Webview} */ (document.getElementById('cloud-print-webview')), | |
94 windowShape, windowMessageDispatcher, baseView); | |
95 | |
96 this.disposables_.add(this.cloudPrintDialogContainer_); | |
97 }; | |
98 | |
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_); | |
106 }; | |
107 | |
108 /** | |
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.getDesktopScale(); | |
115 hostDesktop.resize(window.innerWidth * desktopScale, | |
116 window.innerHeight * desktopScale, | |
117 window.devicePixelRatio); | |
118 }; | |
119 | |
120 /** | |
121 * Adjust the size of the plugin according to the dimensions of the hostDesktop. | |
122 * | |
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(); | |
131 | |
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.getDesktopScale(), | |
138 true /* fullscreen */ , true /* shrinkToFit */ ); | |
139 | |
140 this.plugin_.element().style.width = newSize.width + 'px'; | |
141 this.plugin_.element().style.height = newSize.height + 'px'; | |
142 }; | |
143 | |
144 /** | |
145 * @return {Array<string>} | |
146 * @override {remoting.ProtocolExtension} | |
147 */ | |
148 remoting.AppConnectedView.prototype.getExtensionTypes = function() { | |
149 return ['openURL', 'onWindowRemoved', 'onWindowAdded', | |
150 'onAllWindowsMinimized', 'setKeyboardLayouts', 'pingResponse']; | |
151 }; | |
152 | |
153 /** | |
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); | |
162 | |
163 remoting.identity.getUserInfo().then(function(userInfo) { | |
164 sendMessageToHost('setUserDisplayInfo', | |
165 JSON.stringify({fullName: userInfo.name})); | |
166 }); | |
167 | |
168 var onRestoreHook = new base.ChromeEventHook( | |
169 chrome.app.window.current().onRestored, function() { | |
170 sendMessageToHost('restoreAllWindows', ''); | |
171 }); | |
172 | |
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); | |
177 | |
178 this.disposables_.add(onRestoreHook, pingTimer); | |
179 | |
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 } | |
185 }; | |
186 | |
187 /** | |
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; | |
205 | |
206 case 'onWindowRemoved': | |
207 var id = base.getNumberAttr(message, 'id'); | |
208 this.windowActivationMenu_.remove(id); | |
209 return true; | |
210 | |
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; | |
216 | |
217 case 'onAllWindowsMinimized': | |
218 chrome.app.window.current().minimize(); | |
219 return true; | |
220 | |
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; | |
228 | |
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; | |
234 | |
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 } | |
243 | |
244 this.cloudPrintDialogContainer_.printDocument(title, type, data); | |
245 return true; | |
246 } | |
247 | |
248 return false; | |
249 }; | |
250 | |
251 /** | |
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 })); | |
271 }; | |
272 | |
273 // The access token last received from getNewToken. Saved to ensure that we | |
274 // get a fresh token each time. | |
275 var previousToken_ = ''; | |
276 | |
277 })(); | |
OLD | NEW |