Commit a22c2029 authored by garykac's avatar garykac Committed by Commit bot

[Chromoting] Change Application.Delegate to proper subclass of Application.

App Delegate conversion:
* Removed Application.Delegate
* Convert DesktopRemoting and AppRemoting to be subclasses of Application
* Add ApplicationInterface so that jscompile can verify all required overrides are provided.

Access to SessionConnector was a big motivation for having the apps as a subclass:
* Create the SessionConnector in the constructor rather than in the getter.
* Remove some references to remoting.app.getSessionConnector

Test updates:
* Updated base.inherits unittests to verify calling superclass methods with arguments.
* Removed desktopDelegateForTesting since we can use remoting.app for that now.

BUG=465878

Review URL: https://codereview.chromium.org/1016373003

Cr-Commit-Position: refs/heads/master@{#322437}
parent d8d7b0da
...@@ -14,13 +14,14 @@ ...@@ -14,13 +14,14 @@
var remoting = remoting || {}; var remoting = remoting || {};
/** /**
* @param {remoting.Application} app The main app that owns this delegate. * @param {Array<string>} appCapabilities Array of application capabilities.
* @constructor * @constructor
* @implements {remoting.Application.Delegate} * @implements {remoting.ApplicationInterface}
* @implements {remoting.ProtocolExtension} * @implements {remoting.ProtocolExtension}
* @extends {remoting.Application}
*/ */
remoting.AppRemoting = function(app) { remoting.AppRemoting = function(appCapabilities) {
app.setDelegate(this); base.inherits(this, remoting.Application, appCapabilities);
/** @private {remoting.ApplicationContextMenu} */ /** @private {remoting.ApplicationContextMenu} */
this.contextMenu_ = null; this.contextMenu_ = null;
...@@ -60,11 +61,26 @@ remoting.AppRemoting.AppHostResponse = function() { ...@@ -60,11 +61,26 @@ remoting.AppRemoting.AppHostResponse = function() {
}; };
/** /**
* Initialize the application. This is called before an OAuth token is requested * @return {string} Application product name to be used in UI.
* and should be used for tasks such as initializing the DOM, registering event * @override {remoting.ApplicationInterface}
* handlers, etc. */
remoting.AppRemoting.prototype.getApplicationName = function() {
var manifest = chrome.runtime.getManifest();
return manifest.name;
};
/**
* @param {!remoting.Error} error The failure reason.
* @override {remoting.ApplicationInterface}
*/
remoting.AppRemoting.prototype.signInFailed_ = function(error) {
this.onError_(error);
};
/**
* @override {remoting.ApplicationInterface}
*/ */
remoting.AppRemoting.prototype.init = function() { remoting.AppRemoting.prototype.initApplication_ = function() {
// TODO(jamiewalch): Remove ClientSession's dependency on remoting.fullscreen // TODO(jamiewalch): Remove ClientSession's dependency on remoting.fullscreen
// so that this is no longer required. // so that this is no longer required.
remoting.fullscreen = new remoting.FullscreenAppsV2(); remoting.fullscreen = new remoting.FullscreenAppsV2();
...@@ -91,15 +107,10 @@ remoting.AppRemoting.prototype.init = function() { ...@@ -91,15 +107,10 @@ remoting.AppRemoting.prototype.init = function() {
}; };
/** /**
* Start the application. Once start() is called, the delegate can assume that * @param {string} token An OAuth access token.
* the user has consented to all permissions specified in the manifest. * @override {remoting.ApplicationInterface}
*
* @param {remoting.SessionConnector} connector
* @param {string} token An OAuth access token. The delegate should not cache
* this token, but can assume that it will remain valid during application
* start-up.
*/ */
remoting.AppRemoting.prototype.start = function(connector, token) { remoting.AppRemoting.prototype.startApplication_ = function(token) {
remoting.LoadingWindow.show(); remoting.LoadingWindow.show();
/** @type {remoting.AppRemoting} */ /** @type {remoting.AppRemoting} */
...@@ -147,9 +158,9 @@ remoting.AppRemoting.prototype.start = function(connector, token) { ...@@ -147,9 +158,9 @@ remoting.AppRemoting.prototype.start = function(connector, token) {
host['sharedSecret']); host['sharedSecret']);
}; };
connector.connectMe2App(host, fetchThirdPartyToken); that.sessionConnector_.connectMe2App(host, fetchThirdPartyToken);
} else if (response && response.status == 'pending') { } else if (response && response.status == 'pending') {
that.handleError(new remoting.Error( that.onError_(new remoting.Error(
remoting.Error.Tag.SERVICE_UNAVAILABLE)); remoting.Error.Tag.SERVICE_UNAVAILABLE));
} }
} else { } else {
...@@ -158,61 +169,45 @@ remoting.AppRemoting.prototype.start = function(connector, token) { ...@@ -158,61 +169,45 @@ remoting.AppRemoting.prototype.start = function(connector, token) {
// been updated to properly report 'unknown' errors (rather than // been updated to properly report 'unknown' errors (rather than
// reporting them as AUTHENTICATION_FAILED). // reporting them as AUTHENTICATION_FAILED).
if (xhrResponse.status == 0) { if (xhrResponse.status == 0) {
that.handleError(new remoting.Error( that.onError_(new remoting.Error(
remoting.Error.Tag.NETWORK_FAILURE)); remoting.Error.Tag.NETWORK_FAILURE));
} else if (xhrResponse.status == 401) { } else if (xhrResponse.status == 401) {
that.handleError(new remoting.Error( that.onError_(new remoting.Error(
remoting.Error.Tag.AUTHENTICATION_FAILED)); remoting.Error.Tag.AUTHENTICATION_FAILED));
} else if (xhrResponse.status == 403) { } else if (xhrResponse.status == 403) {
that.handleError(new remoting.Error( that.onError_(new remoting.Error(
remoting.Error.Tag.APP_NOT_AUTHORIZED)); remoting.Error.Tag.APP_NOT_AUTHORIZED));
} else if (xhrResponse.status == 502 || xhrResponse.status == 503) { } else if (xhrResponse.status == 502 || xhrResponse.status == 503) {
that.handleError(new remoting.Error( that.onError_(new remoting.Error(
remoting.Error.Tag.SERVICE_UNAVAILABLE)); remoting.Error.Tag.SERVICE_UNAVAILABLE));
} else { } else {
that.handleError(remoting.Error.unexpected()); that.onError_(remoting.Error.unexpected());
} }
} }
}; };
new remoting.Xhr({ new remoting.Xhr({
method: 'POST', method: 'POST',
url: that.runApplicationUrl(), url: that.runApplicationUrl_(),
oauthToken: token oauthToken: token
}).start().then(parseAppHostResponse); }).start().then(parseAppHostResponse);
}; };
/** /**
* Report an authentication error to the user. This is called in lieu of start() * @override {remoting.ApplicationInterface}
* if the user cannot be authenticated or if they decline the app permissions.
*
* @param {!remoting.Error} error The failure reason.
*/ */
remoting.AppRemoting.prototype.signInFailed = function(error) { remoting.AppRemoting.prototype.exitApplication_ = function() {
this.handleError(error); remoting.LoadingWindow.close();
}; this.closeMainWindow_();
/**
* @return {string} Application product name to be used in UI.
*/
remoting.AppRemoting.prototype.getApplicationName = function() {
var manifest = chrome.runtime.getManifest();
return manifest.name;
};
/** @return {string} */
remoting.AppRemoting.prototype.runApplicationUrl = function() {
return remoting.settings.APP_REMOTING_API_BASE_URL + '/applications/' +
remoting.settings.getAppRemotingApplicationId() + '/run';
}; };
/** /**
* Called when a new session has been connected.
*
* @param {remoting.ConnectionInfo} connectionInfo * @param {remoting.ConnectionInfo} connectionInfo
* @return {void} Nothing. * @override {remoting.ApplicationInterface}
*/ */
remoting.AppRemoting.prototype.handleConnected = function(connectionInfo) { remoting.AppRemoting.prototype.onConnected_ = function(connectionInfo) {
this.initSession_(connectionInfo);
remoting.identity.getUserInfo().then( remoting.identity.getUserInfo().then(
function(userInfo) { function(userInfo) {
remoting.clientSession.sendClientMessage( remoting.clientSession.sendClientMessage(
...@@ -220,7 +215,7 @@ remoting.AppRemoting.prototype.handleConnected = function(connectionInfo) { ...@@ -220,7 +215,7 @@ remoting.AppRemoting.prototype.handleConnected = function(connectionInfo) {
JSON.stringify({fullName: userInfo.name})); JSON.stringify({fullName: userInfo.name}));
}); });
remoting.app.getSessionConnector().registerProtocolExtension(this); this.sessionConnector_.registerProtocolExtension(this);
this.connectedView_ = new remoting.AppConnectedView( this.connectedView_ = new remoting.AppConnectedView(
document.getElementById('client-container'), connectionInfo); document.getElementById('client-container'), connectionInfo);
...@@ -233,11 +228,9 @@ remoting.AppRemoting.prototype.handleConnected = function(connectionInfo) { ...@@ -233,11 +228,9 @@ remoting.AppRemoting.prototype.handleConnected = function(connectionInfo) {
}; };
/** /**
* Called when the current session has been disconnected. * @override {remoting.ApplicationInterface}
*
* @return {void} Nothing.
*/ */
remoting.AppRemoting.prototype.handleDisconnected = function() { remoting.AppRemoting.prototype.onDisconnected_ = function() {
base.dispose(this.connectedView_); base.dispose(this.connectedView_);
this.connectedView_ = null; this.connectedView_ = null;
...@@ -245,18 +238,30 @@ remoting.AppRemoting.prototype.handleDisconnected = function() { ...@@ -245,18 +238,30 @@ remoting.AppRemoting.prototype.handleDisconnected = function() {
}; };
/** /**
* Called when the current session's connection has failed.
*
* @param {remoting.SessionConnector} connector
* @param {!remoting.Error} error * @param {!remoting.Error} error
* @return {void} Nothing. * @override {remoting.ApplicationInterface}
*/ */
remoting.AppRemoting.prototype.handleConnectionFailed = function( remoting.AppRemoting.prototype.onConnectionFailed_ = function(error) {
connector, error) { this.onError_(error);
this.handleError(error);
}; };
/** @return {Array<string>} */ /**
* @param {!remoting.Error} error The error to be localized and displayed.
* @override {remoting.ApplicationInterface}
*/
remoting.AppRemoting.prototype.onError_ = function(error) {
console.error('Connection failed: ' + error.toString());
remoting.LoadingWindow.close();
remoting.MessageWindow.showErrorMessage(
chrome.i18n.getMessage(/*i18n-content*/'CONNECTION_FAILED'),
chrome.i18n.getMessage(error.getTag()));
};
/**
* @return {Array<string>}
* @override {remoting.ProtocolExtension}
*/
remoting.AppRemoting.prototype.getExtensionTypes = function() { remoting.AppRemoting.prototype.getExtensionTypes = function() {
return ['openURL', 'onWindowRemoved', 'onWindowAdded', return ['openURL', 'onWindowRemoved', 'onWindowAdded',
'onAllWindowsMinimized', 'setKeyboardLayouts', 'pingResponse']; 'onAllWindowsMinimized', 'setKeyboardLayouts', 'pingResponse'];
...@@ -265,6 +270,7 @@ remoting.AppRemoting.prototype.getExtensionTypes = function() { ...@@ -265,6 +270,7 @@ remoting.AppRemoting.prototype.getExtensionTypes = function() {
/** /**
* @param {function(string,string)} sendMessageToHost Callback to send a message * @param {function(string,string)} sendMessageToHost Callback to send a message
* to the host. * to the host.
* @override {remoting.ProtocolExtension}
*/ */
remoting.AppRemoting.prototype.startExtension = function(sendMessageToHost) { remoting.AppRemoting.prototype.startExtension = function(sendMessageToHost) {
}; };
...@@ -272,6 +278,7 @@ remoting.AppRemoting.prototype.startExtension = function(sendMessageToHost) { ...@@ -272,6 +278,7 @@ remoting.AppRemoting.prototype.startExtension = function(sendMessageToHost) {
/** /**
* @param {string} type The message type. * @param {string} type The message type.
* @param {Object} message The parsed extension message data. * @param {Object} message The parsed extension message data.
* @override {remoting.ProtocolExtension}
*/ */
remoting.AppRemoting.prototype.onExtensionMessage = function(type, message) { remoting.AppRemoting.prototype.onExtensionMessage = function(type, message) {
switch (type) { switch (type) {
...@@ -321,22 +328,10 @@ remoting.AppRemoting.prototype.onExtensionMessage = function(type, message) { ...@@ -321,22 +328,10 @@ remoting.AppRemoting.prototype.onExtensionMessage = function(type, message) {
}; };
/** /**
* Called when an error needs to be displayed to the user. * @return {string}
* * @private
* @param {!remoting.Error} error The error to be localized and displayed.
* @return {void} Nothing.
*/
remoting.AppRemoting.prototype.handleError = function(error) {
console.error('Connection failed: ' + error.toString());
remoting.LoadingWindow.close();
remoting.MessageWindow.showErrorMessage(
chrome.i18n.getMessage(/*i18n-content*/'CONNECTION_FAILED'),
chrome.i18n.getMessage(error.getTag()));
};
/**
* Close the loading window before exiting.
*/ */
remoting.AppRemoting.prototype.handleExit = function() { remoting.AppRemoting.prototype.runApplicationUrl_ = function() {
remoting.LoadingWindow.close(); return remoting.settings.APP_REMOTING_API_BASE_URL + '/applications/' +
remoting.settings.getAppRemotingApplicationId() + '/run';
}; };
...@@ -11,8 +11,7 @@ var remoting = remoting || {}; ...@@ -11,8 +11,7 @@ var remoting = remoting || {};
* Entry point ('load' handler) for App Remoting webapp. * Entry point ('load' handler) for App Remoting webapp.
*/ */
remoting.startAppRemoting = function() { remoting.startAppRemoting = function() {
remoting.app = new remoting.Application(remoting.app_capabilities()); remoting.app = new remoting.AppRemoting(remoting.app_capabilities());
var app_remoting = new remoting.AppRemoting(remoting.app);
remoting.app.start(); remoting.app.start();
}; };
......
...@@ -15,10 +15,13 @@ var remoting = remoting || {}; ...@@ -15,10 +15,13 @@ var remoting = remoting || {};
/** /**
* @param {Array<string>} appCapabilities Array of application capabilities. * @param {Array<string>} appCapabilities Array of application capabilities.
* @constructor * @constructor
* @implements {remoting.ApplicationInterface}
*/ */
remoting.Application = function(appCapabilities) { remoting.Application = function(appCapabilities) {
/** @private {remoting.Application.Delegate} */ // Create global factories.
this.delegate_ = null; remoting.ClientPlugin.factory = new remoting.DefaultClientPluginFactory();
remoting.SessionConnector.factory =
new remoting.DefaultSessionConnectorFactory();
/** @private {Array<string>} */ /** @private {Array<string>} */
this.appCapabilities_ = [ this.appCapabilities_ = [
...@@ -29,26 +32,23 @@ remoting.Application = function(appCapabilities) { ...@@ -29,26 +32,23 @@ remoting.Application = function(appCapabilities) {
// Append the app-specific capabilities. // Append the app-specific capabilities.
this.appCapabilities_.push.apply(this.appCapabilities_, appCapabilities); this.appCapabilities_.push.apply(this.appCapabilities_, appCapabilities);
/** @private {remoting.SessionConnector} */ /** @protected {remoting.SessionConnector} */
this.sessionConnector_ = null; this.sessionConnector_ = remoting.SessionConnector.factory.createConnector(
document.getElementById('client-container'),
this.onConnected_.bind(this),
this.onError_.bind(this),
this.onConnectionFailed_.bind(this),
this.appCapabilities_);
/** @private {base.Disposable} */ /** @private {base.Disposable} */
this.sessionConnectedHooks_ = null; this.sessionConnectedHooks_ = null;
}; };
/** /**
* @param {remoting.Application.Delegate} appDelegate The delegate that * @return {remoting.SessionConnector} The session connector.
* contains the app-specific functionality.
*/
remoting.Application.prototype.setDelegate = function(appDelegate) {
this.delegate_ = appDelegate;
};
/**
* @return {string} Application product name to be used in UI.
*/ */
remoting.Application.prototype.getApplicationName = function() { remoting.Application.prototype.getSessionConnector = function() {
return this.delegate_.getApplicationName(); return this.sessionConnector_;
}; };
/** /**
...@@ -60,18 +60,33 @@ remoting.Application.prototype.hasCapability = function(capability) { ...@@ -60,18 +60,33 @@ remoting.Application.prototype.hasCapability = function(capability) {
return capabilities.indexOf(capability) != -1; return capabilities.indexOf(capability) != -1;
}; };
/* Disconnect the remoting client. */
remoting.Application.prototype.disconnect = function() {
if (remoting.clientSession) {
remoting.clientSession.disconnect(remoting.Error.none());
console.log('Disconnected.');
}
};
/* Public method to exit the application. */
remoting.Application.prototype.quit = function() {
this.exitApplication_();
};
/**
* Close the main window when quitting the application. This should be called
* by exitApplication() in the subclass.
* @protected
*/
remoting.Application.prototype.closeMainWindow_ = function() {
chrome.app.window.current().close();
};
/** /**
* Initialize the application and register all event handlers. After this * Initialize the application and register all event handlers. After this
* is called, the app is running and waiting for user events. * is called, the app is running and waiting for user events.
*
* @return {void} Nothing.
*/ */
remoting.Application.prototype.start = function() { remoting.Application.prototype.start = function() {
// Create global objects.
remoting.ClientPlugin.factory = new remoting.DefaultClientPluginFactory();
remoting.SessionConnector.factory =
new remoting.DefaultSessionConnectorFactory();
// TODO(garykac): This should be owned properly rather than living in the // TODO(garykac): This should be owned properly rather than living in the
// global 'remoting' namespace. // global 'remoting' namespace.
remoting.settings = new remoting.Settings(); remoting.settings = new remoting.Settings();
...@@ -79,100 +94,37 @@ remoting.Application.prototype.start = function() { ...@@ -79,100 +94,37 @@ remoting.Application.prototype.start = function() {
remoting.initGlobalObjects(); remoting.initGlobalObjects();
remoting.initIdentity(); remoting.initIdentity();
this.delegate_.init(); this.initApplication_();
var that = this; var that = this;
remoting.identity.getToken().then( remoting.identity.getToken().
this.delegate_.start.bind(this.delegate_, this.getSessionConnector()) then(this.startApplication_.bind(this)).
).catch(remoting.Error.handler( catch(remoting.Error.handler(
function(/** !remoting.Error */ error) { function(/** !remoting.Error */ error) {
if (error.hasTag(remoting.Error.Tag.CANCELLED)) { if (error.hasTag(remoting.Error.Tag.CANCELLED)) {
that.exit(); that.exitApplication_();
} else { } else {
that.delegate_.signInFailed(error); that.signInFailed_(error);
} }
} }
) )
); );
}; };
/**
* Quit the application.
*/
remoting.Application.prototype.exit = function() {
this.delegate_.handleExit();
chrome.app.window.current().close();
};
/** Disconnect the remoting client. */
remoting.Application.prototype.disconnect = function() {
if (remoting.clientSession) {
remoting.clientSession.disconnect(remoting.Error.none());
console.log('Disconnected.');
}
};
/** /**
* Called when a new session has been connected. * Called when a new session has been connected.
* *
* @param {remoting.ConnectionInfo} connectionInfo * @param {remoting.ConnectionInfo} connectionInfo
* @return {void} Nothing. * @return {void} Nothing.
* @protected
*/ */
remoting.Application.prototype.onConnected = function(connectionInfo) { remoting.Application.prototype.initSession_ = function(connectionInfo) {
this.sessionConnectedHooks_ = new base.Disposables( this.sessionConnectedHooks_ = new base.Disposables(
new base.EventHook(connectionInfo.session(), 'stateChanged', new base.EventHook(connectionInfo.session(), 'stateChanged',
this.onSessionFinished_.bind(this)), this.onSessionFinished_.bind(this)),
new base.RepeatingTimer(this.updateStatistics_.bind(this), 1000) new base.RepeatingTimer(this.updateStatistics_.bind(this), 1000)
); );
remoting.clipboard.startSession(); remoting.clipboard.startSession();
this.delegate_.handleConnected(connectionInfo);
};
/**
* Called when the current session has been disconnected.
*
* @return {void} Nothing.
*/
remoting.Application.prototype.onDisconnected = function() {
this.delegate_.handleDisconnected();
};
/**
* Called when the current session's connection has failed.
*
* @param {!remoting.Error} error
* @return {void} Nothing.
*/
remoting.Application.prototype.onConnectionFailed = function(error) {
this.delegate_.handleConnectionFailed(this.sessionConnector_, error);
};
/**
* Called when an error needs to be displayed to the user.
*
* @param {!remoting.Error} errorTag The error to be localized and displayed.
* @return {void} Nothing.
*/
remoting.Application.prototype.onError = function(errorTag) {
this.delegate_.handleError(errorTag);
};
/**
* @return {remoting.SessionConnector} A session connector, creating a new one
* if necessary.
*/
remoting.Application.prototype.getSessionConnector = function() {
// TODO(garykac): Check if this can be initialized in the ctor.
if (!this.sessionConnector_) {
this.sessionConnector_ = remoting.SessionConnector.factory.createConnector(
document.getElementById('client-container'),
this.onConnected.bind(this),
this.onError.bind(this),
this.onConnectionFailed.bind(this),
this.appCapabilities_);
}
return this.sessionConnector_;
}; };
/** /**
...@@ -186,7 +138,7 @@ remoting.Application.prototype.onSessionFinished_ = function(state) { ...@@ -186,7 +138,7 @@ remoting.Application.prototype.onSessionFinished_ = function(state) {
switch (state.current) { switch (state.current) {
case remoting.ClientSession.State.CLOSED: case remoting.ClientSession.State.CLOSED:
console.log('Connection closed by host'); console.log('Connection closed by host');
this.onDisconnected(); this.onDisconnected_();
break; break;
case remoting.ClientSession.State.FAILED: case remoting.ClientSession.State.FAILED:
var error = remoting.clientSession.getError(); var error = remoting.clientSession.getError();
...@@ -195,14 +147,14 @@ remoting.Application.prototype.onSessionFinished_ = function(state) { ...@@ -195,14 +147,14 @@ remoting.Application.prototype.onSessionFinished_ = function(state) {
if (error === null) { if (error === null) {
error = remoting.Error.unexpected(); error = remoting.Error.unexpected();
} }
this.onError(error); this.onError_(error);
break; break;
default: default:
console.error('Unexpected client plugin state: ' + state.current); console.error('Unexpected client plugin state: ' + state.current);
// This should only happen if the web-app and client plugin get out of // This should only happen if the web-app and client plugin get out of
// sync, so MISSING_PLUGIN is a suitable error. // sync, so MISSING_PLUGIN is a suitable error.
this.onError(new remoting.Error(remoting.Error.Tag.MISSING_PLUGIN)); this.onError_(new remoting.Error(remoting.Error.Tag.MISSING_PLUGIN));
break; break;
} }
...@@ -219,83 +171,144 @@ remoting.Application.prototype.updateStatistics_ = function() { ...@@ -219,83 +171,144 @@ remoting.Application.prototype.updateStatistics_ = function() {
}; };
/*
* remoting.ApplicationInterface
* These functions must be overridden in the subclass.
*/
/** @return {string} */
remoting.Application.prototype.getApplicationName = function() {
base.debug.assert(false, "Subclass must override");
};
/**
* @param {!remoting.Error} error
* @protected
*/
remoting.Application.prototype.signInFailed_ = function(error) {
base.debug.assert(false, "Subclass must override");
};
/** @protected */
remoting.Application.prototype.initApplication_ = function() {
base.debug.assert(false, "Subclass must override");
};
/**
* @param {string} token
* @protected
*/
remoting.Application.prototype.startApplication_ = function(token) {
base.debug.assert(false, "Subclass must override");
};
/** @protected */
remoting.Application.prototype.exitApplication_ = function() {
base.debug.assert(false, "Subclass must override");
};
/**
* @param {remoting.ConnectionInfo} connectionInfo
* @protected
*/
remoting.Application.prototype.onConnected_ = function(connectionInfo) {
base.debug.assert(false, "Subclass must override");
};
/** @protected */
remoting.Application.prototype.onDisconnected_ = function() {
base.debug.assert(false, "Subclass must override");
};
/** /**
* @param {!remoting.Error} error
* @protected
*/
remoting.Application.prototype.onConnectionFailed_ = function(error) {
base.debug.assert(false, "Subclass must override");
};
/**
* @param {!remoting.Error} error The error to be localized and displayed.
* @protected
*/
remoting.Application.prototype.onError_ = function(error) {
base.debug.assert(false, "Subclass must override");
};
/**
* The interface specifies the methods that a subclass of remoting.Application
* is required implement to override the default behavior.
*
* @interface * @interface
*/ */
remoting.Application.Delegate = function() {}; remoting.ApplicationInterface = function() {};
/**
* @return {string} Application product name to be used in UI.
*/
remoting.ApplicationInterface.prototype.getApplicationName = function() {};
/**
* Report an authentication error to the user. This is called in lieu of
* startApplication() if the user cannot be authenticated or if they decline
* the app permissions.
*
* @param {!remoting.Error} error The failure reason.
*/
remoting.ApplicationInterface.prototype.signInFailed_ = function(error) {};
/** /**
* Initialize the application. This is called before an OAuth token is requested * Initialize the application. This is called before an OAuth token is requested
* and should be used for tasks such as initializing the DOM, registering event * and should be used for tasks such as initializing the DOM, registering event
* handlers, etc. * handlers, etc. After this is called, the app is running and waiting for
* user events.
*/ */
remoting.Application.Delegate.prototype.init = function() {}; remoting.ApplicationInterface.prototype.initApplication_ = function() {};
/** /**
* Start the application. Once start() is called, the delegate can assume that * Start the application. Once startApplication() is called, we can assume that
* the user has consented to all permissions specified in the manifest. * the user has consented to all permissions specified in the manifest.
* *
* @param {remoting.SessionConnector} connector * @param {string} token An OAuth access token. The app should not cache
* @param {string} token An OAuth access token. The delegate should not cache
* this token, but can assume that it will remain valid during application * this token, but can assume that it will remain valid during application
* start-up. * start-up.
*/ */
remoting.Application.Delegate.prototype.start = function(connector, token) {}; remoting.ApplicationInterface.prototype.startApplication_ = function(token) {};
/** /**
* Report an authentication error to the user. This is called in lieu of start() * Close down the application before exiting.
* if the user cannot be authenticated.
*
* @param {!remoting.Error} error The failure reason.
*/
remoting.Application.Delegate.prototype.signInFailed = function(error) {};
/**
* @return {string} Application product name to be used in UI.
*/ */
remoting.Application.Delegate.prototype.getApplicationName = function() {}; remoting.ApplicationInterface.prototype.exitApplication_ = function() {};
/** /**
* Called when a new session has been connected. * Called when a new session has been connected.
* *
* @param {remoting.ConnectionInfo} connectionInfo * @param {remoting.ConnectionInfo} connectionInfo
* @return {void} Nothing.
*/ */
remoting.Application.Delegate.prototype.handleConnected = function( remoting.ApplicationInterface.prototype.onConnected_ =
connectionInfo) {}; function(connectionInfo) {};
/** /**
* Called when the current session has been disconnected. * Called when the current session has been disconnected.
*
* @return {void} Nothing.
*/ */
remoting.Application.Delegate.prototype.handleDisconnected = function() {}; remoting.ApplicationInterface.prototype.onDisconnected_ = function() {};
/** /**
* Called when the current session's connection has failed. * Called when the current session's connection has failed.
* *
* @param {remoting.SessionConnector} connector
* @param {!remoting.Error} error * @param {!remoting.Error} error
* @return {void} Nothing.
*/ */
remoting.Application.Delegate.prototype.handleConnectionFailed = remoting.ApplicationInterface.prototype.onConnectionFailed_ =
function(connector, error) {}; function(error) {};
/** /**
* Called when an error needs to be displayed to the user. * Called when an error needs to be displayed to the user.
* *
* @param {!remoting.Error} errorTag The error to be localized and displayed. * @param {!remoting.Error} errorTag The error to be localized and displayed.
* @return {void} Nothing.
*/
remoting.Application.Delegate.prototype.handleError = function(errorTag) {};
/**
* Perform any application-specific cleanup before exiting. This is called in
* lieu of start() if the user declines the app permissions, and will usually
* be called immediately prior to exiting, although delegates should not rely
* on this.
*/ */
remoting.Application.Delegate.prototype.handleExit = function() {}; remoting.ApplicationInterface.prototype.onError_ = function(errorTag) {};
/** @type {remoting.Application} */ /** @type {remoting.Application} */
......
...@@ -17,9 +17,12 @@ var GrandChildClass = function() { ...@@ -17,9 +17,12 @@ var GrandChildClass = function() {
this.name = 'grandChild'; this.name = 'grandChild';
} }
/** @return {string} */ /**
GrandChildClass.prototype.overrideMethod = function() { * @param {string} arg
return 'overrideMethod - grandChild'; * @return {string}
*/
GrandChildClass.prototype.overrideMethod = function(arg) {
return 'overrideMethod - grandChild - ' + arg;
} }
/** /**
...@@ -32,9 +35,12 @@ var ChildClass = function() { ...@@ -32,9 +35,12 @@ var ChildClass = function() {
this.childOnly = 'childOnly'; this.childOnly = 'childOnly';
} }
/** @return {string} */ /**
ChildClass.prototype.overrideMethod = function() { * @param {string} arg
return 'overrideMethod - child'; * @return {string}
*/
ChildClass.prototype.overrideMethod = function(arg) {
return 'overrideMethod - child - ' + arg;
} }
/** @return {string} */ /** @return {string} */
...@@ -43,8 +49,8 @@ ChildClass.prototype.childMethod = function() { ...@@ -43,8 +49,8 @@ ChildClass.prototype.childMethod = function() {
} }
/** /**
* @constructor
* @param {string} arg * @param {string} arg
* @constructor
*/ */
var ParentClass = function(arg) { var ParentClass = function(arg) {
/** @type {string} */ /** @type {string} */
...@@ -60,9 +66,12 @@ ParentClass.prototype.parentMethod = function() { ...@@ -60,9 +66,12 @@ ParentClass.prototype.parentMethod = function() {
return 'parentMethod'; return 'parentMethod';
} }
/** @return {string} */ /**
ParentClass.prototype.overrideMethod = function() { * @param {string} arg
return 'overrideMethod - parent'; * @return {string}
*/
ParentClass.prototype.overrideMethod = function(arg) {
return 'overrideMethod - parent - ' + arg;
} }
QUnit.test('should invoke parent constructor with the correct arguments', QUnit.test('should invoke parent constructor with the correct arguments',
...@@ -90,19 +99,33 @@ QUnit.test('should preserve instanceof', function(assert) { ...@@ -90,19 +99,33 @@ QUnit.test('should preserve instanceof', function(assert) {
QUnit.test('should override parent property and method', function(assert) { QUnit.test('should override parent property and method', function(assert) {
var child = new ChildClass(); var child = new ChildClass();
assert.equal(child.name, 'child'); assert.equal(child.name, 'child');
assert.equal(child.overrideMethod(), 'overrideMethod - child'); assert.equal(child.overrideMethod('123'), 'overrideMethod - child - 123');
assert.equal(child.childOnly, 'childOnly'); assert.equal(child.childOnly, 'childOnly');
assert.equal(child.childMethod(), 'childMethod'); assert.equal(child.childMethod(), 'childMethod');
}); });
QUnit.test('should works on an inheritance chain', function(assert) { QUnit.test('should work on an inheritance chain', function(assert) {
var grandChild = new GrandChildClass(); var grandChild = new GrandChildClass();
assert.equal(grandChild.name, 'grandChild'); assert.equal(grandChild.name, 'grandChild');
assert.equal(grandChild.overrideMethod(), 'overrideMethod - grandChild'); assert.equal(grandChild.overrideMethod('246'),
'overrideMethod - grandChild - 246');
assert.equal(grandChild.childOnly, 'childOnly'); assert.equal(grandChild.childOnly, 'childOnly');
assert.equal(grandChild.childMethod(), 'childMethod'); assert.equal(grandChild.childMethod(), 'childMethod');
assert.equal(grandChild.parentOnly, 'parentOnly'); assert.equal(grandChild.parentOnly, 'parentOnly');
assert.equal(grandChild.parentMethod(), 'parentMethod'); assert.equal(grandChild.parentMethod(), 'parentMethod');
}); });
QUnit.test('should be able to access parent class methods', function(assert) {
var grandChild = new GrandChildClass();
assert.equal(grandChild.overrideMethod('789'),
'overrideMethod - grandChild - 789');
var childMethod = ChildClass.prototype.overrideMethod.call(grandChild, '81');
assert.equal(childMethod, 'overrideMethod - child - 81');
var parentMethod = ParentClass.prototype.overrideMethod.call(grandChild, '4');
assert.equal(parentMethod, 'overrideMethod - parent - 4');
});
})(); })();
...@@ -58,7 +58,8 @@ browserTest.FakeDesktopViewport.prototype.raiseEvent = ...@@ -58,7 +58,8 @@ browserTest.FakeDesktopViewport.prototype.raiseEvent =
/** @return {remoting.DesktopViewport} */ /** @return {remoting.DesktopViewport} */
function getViewportForTesting() { function getViewportForTesting() {
var view = remoting.desktopDelegateForTesting.getConnectedViewForTesting(); var desktopApp = /** @type {remoting.DesktopRemoting} */ (remoting.app);
var view = desktopApp.getConnectedViewForTesting();
if (view) { if (view) {
return view.getViewportForTesting(); return view.getViewportForTesting();
} }
......
...@@ -200,8 +200,7 @@ remoting.showErrorMessage = function(error) { ...@@ -200,8 +200,7 @@ remoting.showErrorMessage = function(error) {
remoting.startDesktopRemoting = function() { remoting.startDesktopRemoting = function() {
remoting.app = new remoting.Application(remoting.app_capabilities()); remoting.app = new remoting.DesktopRemoting(remoting.app_capabilities());
var desktop_remoting = new remoting.DesktopRemoting(remoting.app);
remoting.app.start(); remoting.app.start();
}; };
......
...@@ -14,20 +14,13 @@ ...@@ -14,20 +14,13 @@
var remoting = remoting || {}; var remoting = remoting || {};
/** /**
* @param {remoting.Application} app The main app that owns this delegate. * @param {Array<string>} appCapabilities Array of application capabilities.
* @constructor * @constructor
* @implements {remoting.Application.Delegate} * @implements {remoting.ApplicationInterface}
* @extends {remoting.Application}
*/ */
remoting.DesktopRemoting = function(app) { remoting.DesktopRemoting = function(appCapabilities) {
/** base.inherits(this, remoting.Application, appCapabilities);
* TODO(garykac): Remove this reference to the Application. It's only
* needed to get the current mode when reporting errors. So we should be
* able to refactor and remove this reference cycle.
*
* @private {remoting.Application}
*/
this.app_ = app;
app.setDelegate(this);
/** /**
* Whether to refresh the JID and retry the connection if the current JID * Whether to refresh the JID and retry the connection if the current JID
...@@ -39,17 +32,28 @@ remoting.DesktopRemoting = function(app) { ...@@ -39,17 +32,28 @@ remoting.DesktopRemoting = function(app) {
/** @private {remoting.DesktopConnectedView} */ /** @private {remoting.DesktopConnectedView} */
this.connectedView_ = null; this.connectedView_ = null;
};
remoting.desktopDelegateForTesting = this; /**
* @return {string} Application product name to be used in UI.
* @override {remoting.ApplicationInterface}
*/
remoting.DesktopRemoting.prototype.getApplicationName = function() {
return chrome.i18n.getMessage(/*i18n-content*/'PRODUCT_NAME');
}; };
/** /**
* Initialize the application and register all event handlers. After this * @param {!remoting.Error} error The failure reason.
* is called, the app is running and waiting for user events. * @override {remoting.ApplicationInterface}
*
* @return {void} Nothing.
*/ */
remoting.DesktopRemoting.prototype.init = function() { remoting.DesktopRemoting.prototype.signInFailed_ = function(error) {
remoting.showErrorMessage(error);
};
/**
* @override {remoting.ApplicationInterface}
*/
remoting.DesktopRemoting.prototype.initApplication_ = function() {
remoting.initElementEventHandlers(); remoting.initElementEventHandlers();
if (base.isAppsV2()) { if (base.isAppsV2()) {
...@@ -122,15 +126,10 @@ remoting.DesktopRemoting.prototype.init = function() { ...@@ -122,15 +126,10 @@ remoting.DesktopRemoting.prototype.init = function() {
}; };
/** /**
* Start the application. Once start() is called, the delegate can assume that * @param {string} token An OAuth access token.
* the user has consented to all permissions specified in the manifest. * @override {remoting.ApplicationInterface}
*
* @param {remoting.SessionConnector} connector
* @param {string} token An OAuth access token. The delegate should not cache
* this token, but can assume that it will remain valid during application
* start-up.
*/ */
remoting.DesktopRemoting.prototype.start = function(connector, token) { remoting.DesktopRemoting.prototype.startApplication_ = function(token) {
remoting.identity.getEmail().then( remoting.identity.getEmail().then(
function(/** string */ email) { function(/** string */ email) {
document.getElementById('current-email').innerText = email; document.getElementById('current-email').innerText = email;
...@@ -139,35 +138,23 @@ remoting.DesktopRemoting.prototype.start = function(connector, token) { ...@@ -139,35 +138,23 @@ remoting.DesktopRemoting.prototype.start = function(connector, token) {
}); });
}; };
/** /** @override {remoting.ApplicationInterface} */
* Report an authentication error to the user. This is called in lieu of start() remoting.DesktopRemoting.prototype.exitApplication_ = function() {
* if the user cannot be authenticated or if they decline the app permissions. this.closeMainWindow_();
*
* @param {!remoting.Error} error The failure reason.
*/
remoting.DesktopRemoting.prototype.signInFailed = function(error) {
remoting.showErrorMessage(error);
}; };
/** /**
* @return {string} Application product name to be used in UI.
*/
remoting.DesktopRemoting.prototype.getApplicationName = function() {
return chrome.i18n.getMessage(/*i18n-content*/'PRODUCT_NAME');
};
/**
* Called when a new session has been connected.
*
* @param {remoting.ConnectionInfo} connectionInfo * @param {remoting.ConnectionInfo} connectionInfo
* @return {void} Nothing. * @override {remoting.ApplicationInterface}
*/ */
remoting.DesktopRemoting.prototype.handleConnected = function(connectionInfo) { remoting.DesktopRemoting.prototype.onConnected_ = function(connectionInfo) {
this.initSession_(connectionInfo);
// Set the text on the buttons shown under the error message so that they are // Set the text on the buttons shown under the error message so that they are
// easy to understand in the case where a successful connection failed, as // easy to understand in the case where a successful connection failed, as
// opposed to the case where a connection never succeeded. // opposed to the case where a connection never succeeded.
// TODO(garykac): Investigate to see if these need to be reverted to their // TODO(garykac): Investigate to see if these need to be reverted to their
// original values in the onDisconnected method. // original values in the onDisconnected_ method.
var button1 = document.getElementById('client-reconnect-button'); var button1 = document.getElementById('client-reconnect-button');
l10n.localizeElementFromTag(button1, /*i18n-content*/'RECONNECT'); l10n.localizeElementFromTag(button1, /*i18n-content*/'RECONNECT');
button1.removeAttribute('autofocus'); button1.removeAttribute('autofocus');
...@@ -197,10 +184,10 @@ remoting.DesktopRemoting.prototype.handleConnected = function(connectionInfo) { ...@@ -197,10 +184,10 @@ remoting.DesktopRemoting.prototype.handleConnected = function(connectionInfo) {
var sessionConnector = remoting.app.getSessionConnector(); var sessionConnector = remoting.app.getSessionConnector();
if (connectionInfo.mode() === remoting.DesktopConnectedView.Mode.ME2ME) { if (connectionInfo.mode() === remoting.DesktopConnectedView.Mode.ME2ME) {
if (remoting.app.hasCapability(remoting.ClientSession.Capability.CAST)) { if (remoting.app.hasCapability(remoting.ClientSession.Capability.CAST)) {
sessionConnector.registerProtocolExtension( this.sessionConnector_.registerProtocolExtension(
new remoting.CastExtensionHandler()); new remoting.CastExtensionHandler());
} }
sessionConnector.registerProtocolExtension( this.sessionConnector_.registerProtocolExtension(
new remoting.GnubbyAuthHandler()); new remoting.GnubbyAuthHandler());
} }
if (connectionInfo.session().hasCapability( if (connectionInfo.session().hasCapability(
...@@ -211,12 +198,13 @@ remoting.DesktopRemoting.prototype.handleConnected = function(connectionInfo) { ...@@ -211,12 +198,13 @@ remoting.DesktopRemoting.prototype.handleConnected = function(connectionInfo) {
} }
if (remoting.pairingRequested) { if (remoting.pairingRequested) {
var that = this;
/** /**
* @param {string} clientId * @param {string} clientId
* @param {string} sharedSecret * @param {string} sharedSecret
*/ */
var onPairingComplete = function(clientId, sharedSecret) { var onPairingComplete = function(clientId, sharedSecret) {
var connector = remoting.app.getSessionConnector(); var connector = that.sessionConnector_;
var host = remoting.hostList.getHostForId(connector.getHostId()); var host = remoting.hostList.getHostForId(connector.getHostId());
host.options.pairingInfo.clientId = clientId; host.options.pairingInfo.clientId = clientId;
host.options.pairingInfo.sharedSecret = sharedSecret; host.options.pairingInfo.sharedSecret = sharedSecret;
...@@ -244,11 +232,9 @@ remoting.DesktopRemoting.prototype.handleConnected = function(connectionInfo) { ...@@ -244,11 +232,9 @@ remoting.DesktopRemoting.prototype.handleConnected = function(connectionInfo) {
}; };
/** /**
* Called when the current session has been disconnected. * @override {remoting.ApplicationInterface}
*
* @return {void} Nothing.
*/ */
remoting.DesktopRemoting.prototype.handleDisconnected = function() { remoting.DesktopRemoting.prototype.onDisconnected_ = function() {
var mode = this.connectedView_.getMode(); var mode = this.connectedView_.getMode();
if (mode === remoting.DesktopConnectedView.Mode.IT2ME) { if (mode === remoting.DesktopConnectedView.Mode.IT2ME) {
remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_IT2ME); remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_IT2ME);
...@@ -260,27 +246,24 @@ remoting.DesktopRemoting.prototype.handleDisconnected = function() { ...@@ -260,27 +246,24 @@ remoting.DesktopRemoting.prototype.handleDisconnected = function() {
}; };
/** /**
* Called when the current session's connection has failed.
*
* @param {remoting.SessionConnector} connector
* @param {!remoting.Error} error * @param {!remoting.Error} error
* @return {void} Nothing. * @override {remoting.ApplicationInterface}
*/ */
remoting.DesktopRemoting.prototype.handleConnectionFailed = function( remoting.DesktopRemoting.prototype.onConnectionFailed_ = function(error) {
connector, error) {
var that = this; var that = this;
var onHostListRefresh = function(/** boolean */ success) { var onHostListRefresh = function(/** boolean */ success) {
if (success) { if (success) {
var connector = that.sessionConnector_;
var host = remoting.hostList.getHostForId(connector.getHostId()); var host = remoting.hostList.getHostForId(connector.getHostId());
if (host) { if (host) {
connector.retryConnectMe2Me(host); connector.retryConnectMe2Me(host);
return; return;
} }
} }
that.handleError(error); that.onError_(error);
}; };
var mode = this.app_.getSessionConnector().getConnectionMode(); var mode = this.sessionConnector_.getConnectionMode();
if (error.hasTag(remoting.Error.Tag.HOST_IS_OFFLINE) && if (error.hasTag(remoting.Error.Tag.HOST_IS_OFFLINE) &&
mode === remoting.DesktopConnectedView.Mode.ME2ME && mode === remoting.DesktopConnectedView.Mode.ME2ME &&
this.refreshHostJidIfOffline_) { this.refreshHostJidIfOffline_) {
...@@ -289,20 +272,18 @@ remoting.DesktopRemoting.prototype.handleConnectionFailed = function( ...@@ -289,20 +272,18 @@ remoting.DesktopRemoting.prototype.handleConnectionFailed = function(
// The plugin will be re-created when the host finished refreshing // The plugin will be re-created when the host finished refreshing
remoting.hostList.refresh(onHostListRefresh); remoting.hostList.refresh(onHostListRefresh);
} else { } else {
this.handleError(error); this.onError_(error);
} }
}; };
/** /**
* Called when an error needs to be displayed to the user.
*
* @param {!remoting.Error} error The error to be localized and displayed. * @param {!remoting.Error} error The error to be localized and displayed.
* @return {void} Nothing. * @override {remoting.ApplicationInterface}
*/ */
remoting.DesktopRemoting.prototype.handleError = function(error) { remoting.DesktopRemoting.prototype.onError_ = function(error) {
console.error('Connection failed: ' + error.toString()); console.error('Connection failed: ' + error.toString());
var mode = this.connectedView_ ? this.connectedView_.getMode() var mode = this.connectedView_ ? this.connectedView_.getMode()
: this.app_.getSessionConnector().getConnectionMode(); : this.sessionConnector_.getConnectionMode();
base.dispose(this.connectedView_); base.dispose(this.connectedView_);
this.connectedView_ = null; this.connectedView_ = null;
...@@ -325,12 +306,6 @@ remoting.DesktopRemoting.prototype.handleError = function(error) { ...@@ -325,12 +306,6 @@ remoting.DesktopRemoting.prototype.handleError = function(error) {
} }
}; };
/**
* No cleanup required for desktop remoting.
*/
remoting.DesktopRemoting.prototype.handleExit = function() {
};
/** /**
* Determine whether or not the app is running in a window. * Determine whether or not the app is running in a window.
* @param {function(boolean):void} callback Callback to receive whether or not * @param {function(boolean):void} callback Callback to receive whether or not
...@@ -364,7 +339,7 @@ remoting.DesktopRemoting.prototype.isWindowed_ = function(callback) { ...@@ -364,7 +339,7 @@ remoting.DesktopRemoting.prototype.isWindowed_ = function(callback) {
* @private * @private
*/ */
remoting.DesktopRemoting.prototype.promptClose_ = function() { remoting.DesktopRemoting.prototype.promptClose_ = function() {
var sessionConnector = remoting.app.getSessionConnector(); var sessionConnector = this.sessionConnector_;
if (sessionConnector && if (sessionConnector &&
sessionConnector.getConnectionMode() == sessionConnector.getConnectionMode() ==
remoting.DesktopConnectedView.Mode.IT2ME) { remoting.DesktopConnectedView.Mode.IT2ME) {
...@@ -385,9 +360,3 @@ remoting.DesktopRemoting.prototype.promptClose_ = function() { ...@@ -385,9 +360,3 @@ remoting.DesktopRemoting.prototype.promptClose_ = function() {
remoting.DesktopRemoting.prototype.getConnectedViewForTesting = function() { remoting.DesktopRemoting.prototype.getConnectedViewForTesting = function() {
return this.connectedView_; return this.connectedView_;
}; };
/**
* Global instance of remoting.DesktopRemoting used for testing.
* @type {remoting.DesktopRemoting}
*/
remoting.desktopDelegateForTesting = null;
...@@ -53,7 +53,7 @@ remoting.WindowFrame = function(titleBar) { ...@@ -53,7 +53,7 @@ remoting.WindowFrame = function(titleBar) {
{ cls: 'window-maximize-restore', { cls: 'window-maximize-restore',
fn: this.maximizeOrRestoreWindow_.bind(this) }, fn: this.maximizeOrRestoreWindow_.bind(this) },
{ cls: 'window-minimize', fn: this.minimizeWindow_.bind(this) }, { cls: 'window-minimize', fn: this.minimizeWindow_.bind(this) },
{ cls: 'window-close', fn: remoting.app.exit.bind(remoting.app) }, { cls: 'window-close', fn: remoting.app.quit.bind(remoting.app) },
{ cls: 'window-controls-stub', fn: this.toggleWindowControls_.bind(this) } { cls: 'window-controls-stub', fn: this.toggleWindowControls_.bind(this) }
]; ];
for (var i = 0; i < handlers.length; ++i) { for (var i = 0; i < handlers.length; ++i) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment