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 @@
var remoting = remoting || {};
/**
* @param {remoting.Application} app The main app that owns this delegate.
* @param {Array<string>} appCapabilities Array of application capabilities.
* @constructor
* @implements {remoting.Application.Delegate}
* @implements {remoting.ApplicationInterface}
* @implements {remoting.ProtocolExtension}
* @extends {remoting.Application}
*/
remoting.AppRemoting = function(app) {
app.setDelegate(this);
remoting.AppRemoting = function(appCapabilities) {
base.inherits(this, remoting.Application, appCapabilities);
/** @private {remoting.ApplicationContextMenu} */
this.contextMenu_ = null;
......@@ -60,11 +61,26 @@ remoting.AppRemoting.AppHostResponse = function() {
};
/**
* 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
* handlers, etc.
* @return {string} Application product name to be used in UI.
* @override {remoting.ApplicationInterface}
*/
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
// so that this is no longer required.
remoting.fullscreen = new remoting.FullscreenAppsV2();
......@@ -91,15 +107,10 @@ remoting.AppRemoting.prototype.init = function() {
};
/**
* Start the application. Once start() is called, the delegate can assume that
* the user has consented to all permissions specified in the manifest.
*
* @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.
* @param {string} token An OAuth access token.
* @override {remoting.ApplicationInterface}
*/
remoting.AppRemoting.prototype.start = function(connector, token) {
remoting.AppRemoting.prototype.startApplication_ = function(token) {
remoting.LoadingWindow.show();
/** @type {remoting.AppRemoting} */
......@@ -147,9 +158,9 @@ remoting.AppRemoting.prototype.start = function(connector, token) {
host['sharedSecret']);
};
connector.connectMe2App(host, fetchThirdPartyToken);
that.sessionConnector_.connectMe2App(host, fetchThirdPartyToken);
} else if (response && response.status == 'pending') {
that.handleError(new remoting.Error(
that.onError_(new remoting.Error(
remoting.Error.Tag.SERVICE_UNAVAILABLE));
}
} else {
......@@ -158,61 +169,45 @@ remoting.AppRemoting.prototype.start = function(connector, token) {
// been updated to properly report 'unknown' errors (rather than
// reporting them as AUTHENTICATION_FAILED).
if (xhrResponse.status == 0) {
that.handleError(new remoting.Error(
that.onError_(new remoting.Error(
remoting.Error.Tag.NETWORK_FAILURE));
} else if (xhrResponse.status == 401) {
that.handleError(new remoting.Error(
that.onError_(new remoting.Error(
remoting.Error.Tag.AUTHENTICATION_FAILED));
} else if (xhrResponse.status == 403) {
that.handleError(new remoting.Error(
that.onError_(new remoting.Error(
remoting.Error.Tag.APP_NOT_AUTHORIZED));
} else if (xhrResponse.status == 502 || xhrResponse.status == 503) {
that.handleError(new remoting.Error(
that.onError_(new remoting.Error(
remoting.Error.Tag.SERVICE_UNAVAILABLE));
} else {
that.handleError(remoting.Error.unexpected());
that.onError_(remoting.Error.unexpected());
}
}
};
new remoting.Xhr({
method: 'POST',
url: that.runApplicationUrl(),
url: that.runApplicationUrl_(),
oauthToken: token
}).start().then(parseAppHostResponse);
};
/**
* Report an authentication error to the user. This is called in lieu of start()
* if the user cannot be authenticated or if they decline the app permissions.
*
* @param {!remoting.Error} error The failure reason.
* @override {remoting.ApplicationInterface}
*/
remoting.AppRemoting.prototype.signInFailed = function(error) {
this.handleError(error);
};
/**
* @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';
remoting.AppRemoting.prototype.exitApplication_ = function() {
remoting.LoadingWindow.close();
this.closeMainWindow_();
};
/**
* Called when a new session has been connected.
*
* @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(
function(userInfo) {
remoting.clientSession.sendClientMessage(
......@@ -220,7 +215,7 @@ remoting.AppRemoting.prototype.handleConnected = function(connectionInfo) {
JSON.stringify({fullName: userInfo.name}));
});
remoting.app.getSessionConnector().registerProtocolExtension(this);
this.sessionConnector_.registerProtocolExtension(this);
this.connectedView_ = new remoting.AppConnectedView(
document.getElementById('client-container'), connectionInfo);
......@@ -233,11 +228,9 @@ remoting.AppRemoting.prototype.handleConnected = function(connectionInfo) {
};
/**
* Called when the current session has been disconnected.
*
* @return {void} Nothing.
* @override {remoting.ApplicationInterface}
*/
remoting.AppRemoting.prototype.handleDisconnected = function() {
remoting.AppRemoting.prototype.onDisconnected_ = function() {
base.dispose(this.connectedView_);
this.connectedView_ = null;
......@@ -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
* @return {void} Nothing.
* @override {remoting.ApplicationInterface}
*/
remoting.AppRemoting.prototype.handleConnectionFailed = function(
connector, error) {
this.handleError(error);
remoting.AppRemoting.prototype.onConnectionFailed_ = function(error) {
this.onError_(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() {
return ['openURL', 'onWindowRemoved', 'onWindowAdded',
'onAllWindowsMinimized', 'setKeyboardLayouts', 'pingResponse'];
......@@ -265,6 +270,7 @@ remoting.AppRemoting.prototype.getExtensionTypes = function() {
/**
* @param {function(string,string)} sendMessageToHost Callback to send a message
* to the host.
* @override {remoting.ProtocolExtension}
*/
remoting.AppRemoting.prototype.startExtension = function(sendMessageToHost) {
};
......@@ -272,6 +278,7 @@ remoting.AppRemoting.prototype.startExtension = function(sendMessageToHost) {
/**
* @param {string} type The message type.
* @param {Object} message The parsed extension message data.
* @override {remoting.ProtocolExtension}
*/
remoting.AppRemoting.prototype.onExtensionMessage = function(type, message) {
switch (type) {
......@@ -321,22 +328,10 @@ remoting.AppRemoting.prototype.onExtensionMessage = function(type, message) {
};
/**
* Called when an error needs to be displayed to the user.
*
* @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.
* @return {string}
* @private
*/
remoting.AppRemoting.prototype.handleExit = function() {
remoting.LoadingWindow.close();
remoting.AppRemoting.prototype.runApplicationUrl_ = function() {
return remoting.settings.APP_REMOTING_API_BASE_URL + '/applications/' +
remoting.settings.getAppRemotingApplicationId() + '/run';
};
......@@ -11,8 +11,7 @@ var remoting = remoting || {};
* Entry point ('load' handler) for App Remoting webapp.
*/
remoting.startAppRemoting = function() {
remoting.app = new remoting.Application(remoting.app_capabilities());
var app_remoting = new remoting.AppRemoting(remoting.app);
remoting.app = new remoting.AppRemoting(remoting.app_capabilities());
remoting.app.start();
};
......
This diff is collapsed.
......@@ -17,9 +17,12 @@ var GrandChildClass = function() {
this.name = 'grandChild';
}
/** @return {string} */
GrandChildClass.prototype.overrideMethod = function() {
return 'overrideMethod - grandChild';
/**
* @param {string} arg
* @return {string}
*/
GrandChildClass.prototype.overrideMethod = function(arg) {
return 'overrideMethod - grandChild - ' + arg;
}
/**
......@@ -32,9 +35,12 @@ var ChildClass = function() {
this.childOnly = 'childOnly';
}
/** @return {string} */
ChildClass.prototype.overrideMethod = function() {
return 'overrideMethod - child';
/**
* @param {string} arg
* @return {string}
*/
ChildClass.prototype.overrideMethod = function(arg) {
return 'overrideMethod - child - ' + arg;
}
/** @return {string} */
......@@ -43,8 +49,8 @@ ChildClass.prototype.childMethod = function() {
}
/**
* @constructor
* @param {string} arg
* @constructor
*/
var ParentClass = function(arg) {
/** @type {string} */
......@@ -60,9 +66,12 @@ ParentClass.prototype.parentMethod = function() {
return 'parentMethod';
}
/** @return {string} */
ParentClass.prototype.overrideMethod = function() {
return 'overrideMethod - parent';
/**
* @param {string} arg
* @return {string}
*/
ParentClass.prototype.overrideMethod = function(arg) {
return 'overrideMethod - parent - ' + arg;
}
QUnit.test('should invoke parent constructor with the correct arguments',
......@@ -90,19 +99,33 @@ QUnit.test('should preserve instanceof', function(assert) {
QUnit.test('should override parent property and method', function(assert) {
var child = new ChildClass();
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.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();
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.childMethod(), 'childMethod');
assert.equal(grandChild.parentOnly, 'parentOnly');
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 =
/** @return {remoting.DesktopViewport} */
function getViewportForTesting() {
var view = remoting.desktopDelegateForTesting.getConnectedViewForTesting();
var desktopApp = /** @type {remoting.DesktopRemoting} */ (remoting.app);
var view = desktopApp.getConnectedViewForTesting();
if (view) {
return view.getViewportForTesting();
}
......
......@@ -200,8 +200,7 @@ remoting.showErrorMessage = function(error) {
remoting.startDesktopRemoting = function() {
remoting.app = new remoting.Application(remoting.app_capabilities());
var desktop_remoting = new remoting.DesktopRemoting(remoting.app);
remoting.app = new remoting.DesktopRemoting(remoting.app_capabilities());
remoting.app.start();
};
......
......@@ -14,20 +14,13 @@
var remoting = remoting || {};
/**
* @param {remoting.Application} app The main app that owns this delegate.
* @param {Array<string>} appCapabilities Array of application capabilities.
* @constructor
* @implements {remoting.Application.Delegate}
* @implements {remoting.ApplicationInterface}
* @extends {remoting.Application}
*/
remoting.DesktopRemoting = function(app) {
/**
* 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);
remoting.DesktopRemoting = function(appCapabilities) {
base.inherits(this, remoting.Application, appCapabilities);
/**
* Whether to refresh the JID and retry the connection if the current JID
......@@ -39,17 +32,28 @@ remoting.DesktopRemoting = function(app) {
/** @private {remoting.DesktopConnectedView} */
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
* is called, the app is running and waiting for user events.
*
* @return {void} Nothing.
* @param {!remoting.Error} error The failure reason.
* @override {remoting.ApplicationInterface}
*/
remoting.DesktopRemoting.prototype.init = function() {
remoting.DesktopRemoting.prototype.signInFailed_ = function(error) {
remoting.showErrorMessage(error);
};
/**
* @override {remoting.ApplicationInterface}
*/
remoting.DesktopRemoting.prototype.initApplication_ = function() {
remoting.initElementEventHandlers();
if (base.isAppsV2()) {
......@@ -122,15 +126,10 @@ remoting.DesktopRemoting.prototype.init = function() {
};
/**
* Start the application. Once start() is called, the delegate can assume that
* the user has consented to all permissions specified in the manifest.
*
* @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.
* @param {string} token An OAuth access token.
* @override {remoting.ApplicationInterface}
*/
remoting.DesktopRemoting.prototype.start = function(connector, token) {
remoting.DesktopRemoting.prototype.startApplication_ = function(token) {
remoting.identity.getEmail().then(
function(/** string */ email) {
document.getElementById('current-email').innerText = email;
......@@ -139,35 +138,23 @@ remoting.DesktopRemoting.prototype.start = function(connector, token) {
});
};
/**
* Report an authentication error to the user. This is called in lieu of start()
* if the user cannot be authenticated or if they decline the app permissions.
*
* @param {!remoting.Error} error The failure reason.
*/
remoting.DesktopRemoting.prototype.signInFailed = function(error) {
remoting.showErrorMessage(error);
/** @override {remoting.ApplicationInterface} */
remoting.DesktopRemoting.prototype.exitApplication_ = function() {
this.closeMainWindow_();
};
/**
* @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
* @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
// easy to understand in the case where a successful connection failed, as
// opposed to the case where a connection never succeeded.
// 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');
l10n.localizeElementFromTag(button1, /*i18n-content*/'RECONNECT');
button1.removeAttribute('autofocus');
......@@ -197,10 +184,10 @@ remoting.DesktopRemoting.prototype.handleConnected = function(connectionInfo) {
var sessionConnector = remoting.app.getSessionConnector();
if (connectionInfo.mode() === remoting.DesktopConnectedView.Mode.ME2ME) {
if (remoting.app.hasCapability(remoting.ClientSession.Capability.CAST)) {
sessionConnector.registerProtocolExtension(
this.sessionConnector_.registerProtocolExtension(
new remoting.CastExtensionHandler());
}
sessionConnector.registerProtocolExtension(
this.sessionConnector_.registerProtocolExtension(
new remoting.GnubbyAuthHandler());
}
if (connectionInfo.session().hasCapability(
......@@ -211,12 +198,13 @@ remoting.DesktopRemoting.prototype.handleConnected = function(connectionInfo) {
}
if (remoting.pairingRequested) {
var that = this;
/**
* @param {string} clientId
* @param {string} sharedSecret
*/
var onPairingComplete = function(clientId, sharedSecret) {
var connector = remoting.app.getSessionConnector();
var connector = that.sessionConnector_;
var host = remoting.hostList.getHostForId(connector.getHostId());
host.options.pairingInfo.clientId = clientId;
host.options.pairingInfo.sharedSecret = sharedSecret;
......@@ -244,11 +232,9 @@ remoting.DesktopRemoting.prototype.handleConnected = function(connectionInfo) {
};
/**
* Called when the current session has been disconnected.
*
* @return {void} Nothing.
* @override {remoting.ApplicationInterface}
*/
remoting.DesktopRemoting.prototype.handleDisconnected = function() {
remoting.DesktopRemoting.prototype.onDisconnected_ = function() {
var mode = this.connectedView_.getMode();
if (mode === remoting.DesktopConnectedView.Mode.IT2ME) {
remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_IT2ME);
......@@ -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
* @return {void} Nothing.
* @override {remoting.ApplicationInterface}
*/
remoting.DesktopRemoting.prototype.handleConnectionFailed = function(
connector, error) {
remoting.DesktopRemoting.prototype.onConnectionFailed_ = function(error) {
var that = this;
var onHostListRefresh = function(/** boolean */ success) {
if (success) {
var connector = that.sessionConnector_;
var host = remoting.hostList.getHostForId(connector.getHostId());
if (host) {
connector.retryConnectMe2Me(host);
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) &&
mode === remoting.DesktopConnectedView.Mode.ME2ME &&
this.refreshHostJidIfOffline_) {
......@@ -289,20 +272,18 @@ remoting.DesktopRemoting.prototype.handleConnectionFailed = function(
// The plugin will be re-created when the host finished refreshing
remoting.hostList.refresh(onHostListRefresh);
} 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.
* @return {void} Nothing.
* @override {remoting.ApplicationInterface}
*/
remoting.DesktopRemoting.prototype.handleError = function(error) {
remoting.DesktopRemoting.prototype.onError_ = function(error) {
console.error('Connection failed: ' + error.toString());
var mode = this.connectedView_ ? this.connectedView_.getMode()
: this.app_.getSessionConnector().getConnectionMode();
: this.sessionConnector_.getConnectionMode();
base.dispose(this.connectedView_);
this.connectedView_ = null;
......@@ -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.
* @param {function(boolean):void} callback Callback to receive whether or not
......@@ -364,7 +339,7 @@ remoting.DesktopRemoting.prototype.isWindowed_ = function(callback) {
* @private
*/
remoting.DesktopRemoting.prototype.promptClose_ = function() {
var sessionConnector = remoting.app.getSessionConnector();
var sessionConnector = this.sessionConnector_;
if (sessionConnector &&
sessionConnector.getConnectionMode() ==
remoting.DesktopConnectedView.Mode.IT2ME) {
......@@ -385,9 +360,3 @@ remoting.DesktopRemoting.prototype.promptClose_ = function() {
remoting.DesktopRemoting.prototype.getConnectedViewForTesting = function() {
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) {
{ cls: 'window-maximize-restore',
fn: this.maximizeOrRestoreWindow_.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) }
];
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