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_
= '';