blob: f3d05beecb74460243f01e12e12fd5de35abf954 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview
* Implements a basic UX control for a connected app remoting session.
*/
/** @suppress {duplicate} */
var remoting = remoting || {};
(function() {
'use strict';
/**
* Interval to test the connection speed.
* @const {number}
*/
var CONNECTION_SPEED_PING_INTERVAL_MS = 10 * 1000;
/**
* Interval to refresh the google drive access token.
* @const {number}
*/
var DRIVE_ACCESS_TOKEN_REFRESH_INTERVAL_MS = 15 * 60 * 1000;
/**
* @param {HTMLElement} containerElement
* @param {remoting.WindowShape} windowShape
* @param {remoting.ConnectionInfo} connectionInfo
* @param {base.WindowMessageDispatcher} windowMessageDispatcher
*
* @constructor
* @implements {base.Disposable}
* @implements {remoting.ProtocolExtension}
*/
remoting.AppConnectedView = function(containerElement, windowShape,
connectionInfo, windowMessageDispatcher) {
/** @private */
this.plugin_ = connectionInfo.plugin();
/** @private */
this.host_ = connectionInfo.host();
var menuAdapter = new remoting.ContextMenuChrome();
// Initialize the context menus.
if (!remoting.platformIsChromeOS()) {
menuAdapter = new remoting.ContextMenuDom(
document.getElementById('context-menu'), windowShape);
}
this.contextMenu_ = new remoting.ApplicationContextMenu(
menuAdapter, this.plugin_, connectionInfo.session(), windowShape);
this.contextMenu_.setHostId(connectionInfo.host().hostId);
/** @private */
this.keyboardLayoutsMenu_ = new remoting.KeyboardLayoutsMenu(menuAdapter);
/** @private */
this.windowActivationMenu_ = new remoting.WindowActivationMenu(menuAdapter);
var baseView = new remoting.ConnectedView(
this.plugin_, containerElement,
containerElement.querySelector('.mouse-cursor-overlay'));
var windowShapeHook = new base.EventHook(
this.plugin_.hostDesktop(),
remoting.HostDesktop.Events.shapeChanged,
windowShape.setDesktopRects.bind(windowShape));
var desktopSizeHook = new base.EventHook(
this.plugin_.hostDesktop(),
remoting.HostDesktop.Events.sizeChanged,
this.onDesktopSizeChanged_.bind(this));
/** @private */
this.disposables_ =
new base.Disposables(baseView, windowShapeHook, desktopSizeHook,
this.contextMenu_, menuAdapter);
/** @private */
this.supportsGoogleDrive_ = this.plugin_.hasCapability(
remoting.ClientSession.Capability.GOOGLE_DRIVE);
this.resizeHostToClientArea_();
this.plugin_.extensions().register(this);
/** @private {remoting.CloudPrintDialogContainer} */
this.cloudPrintDialogContainer_ = new remoting.CloudPrintDialogContainer(
/** @type {!Webview} */ (document.getElementById('cloud-print-webview')),
windowShape, windowMessageDispatcher, baseView);
this.disposables_.add(this.cloudPrintDialogContainer_);
};
/**
* @return {void} Nothing.
*/
remoting.AppConnectedView.prototype.dispose = function() {
this.windowActivationMenu_.setExtensionMessageSender(base.doNothing);
this.keyboardLayoutsMenu_.setExtensionMessageSender(base.doNothing);
base.dispose(this.disposables_);
};
/**
* Resize the host to the dimensions of the current window.
* @private
*/
remoting.AppConnectedView.prototype.resizeHostToClientArea_ = function() {
var hostDesktop = this.plugin_.hostDesktop();
var desktopScale = this.host_.options.getDesktopScale();
hostDesktop.resize(window.innerWidth * desktopScale,
window.innerHeight * desktopScale,
window.devicePixelRatio);
};
/**
* Adjust the size of the plugin according to the dimensions of the hostDesktop.
*
* @param {{width:number, height:number, xDpi:number, yDpi:number}} hostDesktop
* @private
*/
remoting.AppConnectedView.prototype.onDesktopSizeChanged_ =
function(hostDesktop) {
// The first desktop size change indicates that we can close the loading
// window.
remoting.LoadingWindow.close();
var hostSize = { width: hostDesktop.width, height: hostDesktop.height };
var hostDpi = { x: hostDesktop.xDpi, y: hostDesktop.yDpi };
var clientArea = { width: window.innerWidth, height: window.innerHeight };
var newSize = remoting.Viewport.choosePluginSize(
clientArea, window.devicePixelRatio,
hostSize, hostDpi, this.host_.options.getDesktopScale(),
true /* fullscreen */ , true /* shrinkToFit */ );
this.plugin_.element().style.width = newSize.width + 'px';
this.plugin_.element().style.height = newSize.height + 'px';
};
/**
* @return {Array<string>}
* @override {remoting.ProtocolExtension}
*/
remoting.AppConnectedView.prototype.getExtensionTypes = function() {
return ['openURL', 'onWindowRemoved', 'onWindowAdded',
'onAllWindowsMinimized', 'setKeyboardLayouts', 'pingResponse'];
};
/**
* @param {function(string,string)} sendMessageToHost Callback to send a message
* to the host.
* @override {remoting.ProtocolExtension}
*/
remoting.AppConnectedView.prototype.startExtension = function(
sendMessageToHost) {
this.windowActivationMenu_.setExtensionMessageSender(sendMessageToHost);
this.keyboardLayoutsMenu_.setExtensionMessageSender(sendMessageToHost);
remoting.identity.getUserInfo().then(function(userInfo) {
sendMessageToHost('setUserDisplayInfo',
JSON.stringify({fullName: userInfo.name}));
});
var onRestoreHook = new base.ChromeEventHook(
chrome.app.window.current().onRestored, function() {
sendMessageToHost('restoreAllWindows', '');
});
var pingTimer = new base.RepeatingTimer(function() {
var message = {timestamp: new Date().getTime()};
sendMessageToHost('pingRequest', JSON.stringify(message));
}, CONNECTION_SPEED_PING_INTERVAL_MS);
this.disposables_.add(onRestoreHook, pingTimer);
if (this.supportsGoogleDrive_) {
this.disposables_.add(new base.RepeatingTimer(
this.sendGoogleDriveAccessToken_.bind(this, sendMessageToHost),
DRIVE_ACCESS_TOKEN_REFRESH_INTERVAL_MS, true));
}
};
/**
* @param {string} command The message command.
* @param {Object} message The parsed extension message data.
* @override {remoting.ProtocolExtension}
*/
remoting.AppConnectedView.prototype.onExtensionMessage =
function(command, message) {
switch (command) {
case 'openURL':
// URL requests from the hosted app are untrusted, so disallow anything
// other than HTTP or HTTPS.
var url = base.getStringAttr(message, 'url');
if (url.indexOf('http:') != 0 && url.indexOf('https:') != 0) {
console.error('Bad URL: ' + url);
} else {
window.open(url);
}
return true;
case 'onWindowRemoved':
var id = base.getNumberAttr(message, 'id');
this.windowActivationMenu_.remove(id);
return true;
case 'onWindowAdded':
var id = base.getNumberAttr(message, 'id');
var title = base.getStringAttr(message, 'title');
this.windowActivationMenu_.add(id, title);
return true;
case 'onAllWindowsMinimized':
chrome.app.window.current().minimize();
return true;
case 'setKeyboardLayouts':
var supportedLayouts = base.getArrayAttr(message, 'supportedLayouts');
var currentLayout = base.getStringAttr(message, 'currentLayout');
console.log('Current host keyboard layout: ' + currentLayout);
console.log('Supported host keyboard layouts: ' + supportedLayouts);
this.keyboardLayoutsMenu_.setLayouts(supportedLayouts, currentLayout);
return true;
case 'pingResponse':
var then = base.getNumberAttr(message, 'timestamp');
var now = new Date().getTime();
this.contextMenu_.updateConnectionRTT(now - then);
return true;
case 'printDocument':
var title = base.getStringAttr(message, 'title');
var type = base.getStringAttr(message, 'type');
var data = base.getStringAttr(message, 'data');
if (type == '' || data == '') {
console.error('"type" and "data" cannot be empty.');
return true;
}
this.cloudPrintDialogContainer_.printDocument(title, type, data);
return true;
}
return false;
};
/**
* Timer callback to send the access token to the host.
* @param {function(string, string)} sendExtensionMessage
* @private
*/
remoting.AppConnectedView.prototype.sendGoogleDriveAccessToken_ =
function(sendExtensionMessage) {
var googleDriveScopes = [
'https://docs.google.com/feeds/',
'https://www.googleapis.com/auth/drive'
];
remoting.identity.getNewToken(googleDriveScopes).then(
function(/** string */ token){
console.assert(token !== previousToken_,
'getNewToken() returned the same token.');
previousToken_ = token;
sendExtensionMessage('accessToken', token);
}).catch(remoting.Error.handler(function(/** remoting.Error */ error) {
console.log('Failed to refresh access token: ' + error.toString());
}));
};
// The access token last received from getNewToken. Saved to ensure that we
// get a fresh token each time.
var previousToken_ = '';
})();