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.WindowShape} windowShape
32 * @param {remoting.ConnectionInfo} connectionInfo
33 * @param {base.WindowMessageDispatcher} windowMessageDispatcher
36 * @implements {base.Disposable}
37 * @implements {remoting.ProtocolExtension}
39 remoting.AppConnectedView = function(containerElement, windowShape,
40 connectionInfo, windowMessageDispatcher) {
42 this.plugin_ = connectionInfo.plugin();
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);
55 this.contextMenu_ = new remoting.ApplicationContextMenu(
56 menuAdapter, this.plugin_, connectionInfo.session(), windowShape);
57 this.contextMenu_.setHostId(connectionInfo.host().hostId);
60 this.keyboardLayoutsMenu_ = new remoting.KeyboardLayoutsMenu(menuAdapter);
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));
81 new base.Disposables(baseView, windowShapeHook, desktopSizeHook,
82 this.contextMenu_, menuAdapter);
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_);
100 * @return {void} Nothing.
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.
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
126 remoting.AppConnectedView.prototype.onDesktopSizeChanged_ =
127 function(hostDesktop) {
128 // The first desktop size change indicates that we can close the loading
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}
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
156 * @override {remoting.ProtocolExtension}
158 remoting.AppConnectedView.prototype.startExtension = function(
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}));
168 var onRestoreHook = new base.ChromeEventHook(
169 chrome.app.window.current().onRestored, function() {
170 sendMessageToHost('restoreAllWindows', '');
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));
188 * @param {string} command The message command.
189 * @param {Object} message The parsed extension message data.
190 * @override {remoting.ProtocolExtension}
192 remoting.AppConnectedView.prototype.onExtensionMessage =
193 function(command, message) {
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);
206 case 'onWindowRemoved':
207 var id = base.getNumberAttr(message, 'id');
208 this.windowActivationMenu_.remove(id);
211 case 'onWindowAdded':
212 var id = base.getNumberAttr(message, 'id');
213 var title = base.getStringAttr(message, 'title');
214 this.windowActivationMenu_.add(id, title);
217 case 'onAllWindowsMinimized':
218 chrome.app.window.current().minimize();
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);
230 var then = base.getNumberAttr(message, 'timestamp');
231 var now = new Date().getTime();
232 this.contextMenu_.updateConnectionRTT(now - then);
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.');
244 this.cloudPrintDialogContainer_.printDocument(title, type, data);
252 * Timer callback to send the access token to the host.
253 * @param {function(string, string)} sendExtensionMessage
256 remoting.AppConnectedView.prototype.sendGoogleDriveAccessToken_ =
257 function(sendExtensionMessage) {
258 var googleDriveScopes = [
259 'https://docs.google.com/feeds/',
260 'https://www.googleapis.com/auth/drive'
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());
273 // The access token last received from getNewToken. Saved to ensure that we
274 // get a fresh token each time.
275 var previousToken_ = '';