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.
7 * Implements a basic UX control for a connected app remoting session.
10 /** @suppress {duplicate} */
11 var remoting = remoting || {};
18 * Interval to test the connection speed.
21 var CONNECTION_SPEED_PING_INTERVAL_MS = 10 * 1000;
24 * Interval to refresh the google drive access token.
27 var DRIVE_ACCESS_TOKEN_REFRESH_INTERVAL_MS = 15 * 60 * 1000;
30 * @param {HTMLElement} containerElement
31 * @param {remoting.ConnectionInfo} connectionInfo
34 * @implements {base.Disposable}
35 * @implements {remoting.ProtocolExtension}
37 remoting.AppConnectedView = function(containerElement, connectionInfo) {
39 this.plugin_ = connectionInfo.plugin();
42 this.host_ = connectionInfo.host();
44 var menuAdapter = new remoting.ContextMenuChrome();
46 // Initialize the context menus.
47 if (!remoting.platformIsChromeOS()) {
49 new remoting.ContextMenuDom(document.getElementById('context-menu'));
52 this.contextMenu_ = new remoting.ApplicationContextMenu(
53 menuAdapter, this.plugin_, connectionInfo.session());
54 this.contextMenu_.setHostId(connectionInfo.host().hostId);
57 this.keyboardLayoutsMenu_ = new remoting.KeyboardLayoutsMenu(menuAdapter);
60 this.windowActivationMenu_ = new remoting.WindowActivationMenu(menuAdapter);
62 var baseView = new remoting.ConnectedView(
63 this.plugin_, containerElement,
64 containerElement.querySelector('.mouse-cursor-overlay'));
66 var windowShapeHook = new base.EventHook(
67 this.plugin_.hostDesktop(),
68 remoting.HostDesktop.Events.shapeChanged,
69 remoting.windowShape.setDesktopRects.bind(remoting.windowShape));
71 var desktopSizeHook = new base.EventHook(
72 this.plugin_.hostDesktop(),
73 remoting.HostDesktop.Events.sizeChanged,
74 this.onDesktopSizeChanged_.bind(this));
78 new base.Disposables(baseView, windowShapeHook, desktopSizeHook,
79 this.contextMenu_, menuAdapter);
82 this.supportsGoogleDrive_ = this.plugin_.hasCapability(
83 remoting.ClientSession.Capability.GOOGLE_DRIVE);
85 this.resizeHostToClientArea_();
86 this.plugin_.extensions().register(this);
90 * @return {void} Nothing.
92 remoting.AppConnectedView.prototype.dispose = function() {
93 this.windowActivationMenu_.setExtensionMessageSender(base.doNothing);
94 this.keyboardLayoutsMenu_.setExtensionMessageSender(base.doNothing);
95 base.dispose(this.disposables_);
99 * Resize the host to the dimensions of the current window.
102 remoting.AppConnectedView.prototype.resizeHostToClientArea_ = function() {
103 var hostDesktop = this.plugin_.hostDesktop();
104 var desktopScale = this.host_.options.desktopScale;
105 hostDesktop.resize(window.innerWidth * desktopScale,
106 window.innerHeight * desktopScale,
107 window.devicePixelRatio);
111 * Adjust the size of the plugin according to the dimensions of the hostDesktop.
113 * @param {{width:number, height:number, xDpi:number, yDpi:number}} hostDesktop
116 remoting.AppConnectedView.prototype.onDesktopSizeChanged_ =
117 function(hostDesktop) {
118 // The first desktop size change indicates that we can close the loading
120 remoting.LoadingWindow.close();
122 var hostSize = { width: hostDesktop.width, height: hostDesktop.height };
123 var hostDpi = { x: hostDesktop.xDpi, y: hostDesktop.yDpi };
124 var clientArea = { width: window.innerWidth, height: window.innerHeight };
125 var newSize = remoting.Viewport.choosePluginSize(
126 clientArea, window.devicePixelRatio,
127 hostSize, hostDpi, this.host_.options.desktopScale,
128 true /* fullscreen */ , true /* shrinkToFit */ );
130 this.plugin_.element().style.width = newSize.width + 'px';
131 this.plugin_.element().style.height = newSize.height + 'px';
135 * @return {Array<string>}
136 * @override {remoting.ProtocolExtension}
138 remoting.AppConnectedView.prototype.getExtensionTypes = function() {
139 return ['openURL', 'onWindowRemoved', 'onWindowAdded',
140 'onAllWindowsMinimized', 'setKeyboardLayouts', 'pingResponse'];
144 * @param {function(string,string)} sendMessageToHost Callback to send a message
146 * @override {remoting.ProtocolExtension}
148 remoting.AppConnectedView.prototype.startExtension = function(
150 this.windowActivationMenu_.setExtensionMessageSender(sendMessageToHost);
151 this.keyboardLayoutsMenu_.setExtensionMessageSender(sendMessageToHost);
153 remoting.identity.getUserInfo().then(function(userInfo) {
154 sendMessageToHost('setUserDisplayInfo',
155 JSON.stringify({fullName: userInfo.name}));
158 var onRestoreHook = new base.ChromeEventHook(
159 chrome.app.window.current().onRestored, function() {
160 sendMessageToHost('restoreAllWindows', '');
163 var pingTimer = new base.RepeatingTimer(function() {
164 var message = {timestamp: new Date().getTime()};
165 sendMessageToHost('pingRequest', JSON.stringify(message));
166 }, CONNECTION_SPEED_PING_INTERVAL_MS);
168 this.disposables_.add(onRestoreHook, pingTimer);
170 if (this.supportsGoogleDrive_) {
171 this.disposables_.add(new base.RepeatingTimer(
172 this.sendGoogleDriveAccessToken_.bind(this, sendMessageToHost),
173 DRIVE_ACCESS_TOKEN_REFRESH_INTERVAL_MS, true));
178 * @param {string} type The message type.
179 * @param {Object} message The parsed extension message data.
180 * @override {remoting.ProtocolExtension}
182 remoting.AppConnectedView.prototype.onExtensionMessage =
183 function(type, message) {
186 // URL requests from the hosted app are untrusted, so disallow anything
187 // other than HTTP or HTTPS.
188 var url = base.getStringAttr(message, 'url');
189 if (url.indexOf('http:') != 0 && url.indexOf('https:') != 0) {
190 console.error('Bad URL: ' + url);
196 case 'onWindowRemoved':
197 var id = base.getNumberAttr(message, 'id');
198 this.windowActivationMenu_.remove(id);
201 case 'onWindowAdded':
202 var id = base.getNumberAttr(message, 'id');
203 var title = base.getStringAttr(message, 'title');
204 this.windowActivationMenu_.add(id, title);
207 case 'onAllWindowsMinimized':
208 chrome.app.window.current().minimize();
211 case 'setKeyboardLayouts':
212 var supportedLayouts = base.getArrayAttr(message, 'supportedLayouts');
213 var currentLayout = base.getStringAttr(message, 'currentLayout');
214 console.log('Current host keyboard layout: ' + currentLayout);
215 console.log('Supported host keyboard layouts: ' + supportedLayouts);
216 this.keyboardLayoutsMenu_.setLayouts(supportedLayouts, currentLayout);
220 var then = base.getNumberAttr(message, 'timestamp');
221 var now = new Date().getTime();
222 this.contextMenu_.updateConnectionRTT(now - then);
230 * Timer callback to send the access token to the host.
231 * @param {function(string, string)} sendExtensionMessage
234 remoting.AppConnectedView.prototype.sendGoogleDriveAccessToken_ =
235 function(sendExtensionMessage) {
236 var googleDriveScopes = [
237 'https://docs.google.com/feeds/',
238 'https://www.googleapis.com/auth/drive'
240 remoting.identity.getNewToken(googleDriveScopes).then(
241 function(/** string */ token){
242 base.debug.assert(token !== previousToken_);
243 previousToken_ = token;
244 sendExtensionMessage('accessToken', token);
245 }).catch(remoting.Error.handler(function(/** remoting.Error */ error) {
246 console.log('Failed to refresh access token: ' + error.toString());
250 // The access token last received from getNewToken. Saved to ensure that we
251 // get a fresh token each time.
252 var previousToken_ = '';