| 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 |