summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--remoting/resources/remoting_strings.grd3
-rw-r--r--remoting/webapp/base/js/base.js36
-rw-r--r--remoting/webapp/base/js/chromoting_event.js3
-rw-r--r--remoting/webapp/base/js/client_plugin.js5
-rw-r--r--remoting/webapp/base/js/client_plugin_impl.js69
-rw-r--r--remoting/webapp/base/js/client_session_factory.js29
-rw-r--r--remoting/webapp/base/js/error.js3
-rw-r--r--remoting/webapp/base/js/session_logger.js2
-rw-r--r--remoting/webapp/crd/js/mock_client_plugin.js12
9 files changed, 120 insertions, 42 deletions
diff --git a/remoting/resources/remoting_strings.grd b/remoting/resources/remoting_strings.grd
index 38c35fc..59fb1d3 100644
--- a/remoting/resources/remoting_strings.grd
+++ b/remoting/resources/remoting_strings.grd
@@ -453,6 +453,9 @@
<message desc="Error displayed if the client plugin fails to load." name="IDS_ERROR_MISSING_PLUGIN">
Some required components are missing. Please make sure you're running the latest version of Chrome and try again.
</message>
+ <message desc="Error displayed if Native Client is not enabled." name="IDS_ERROR_NACL_DISABLED">
+ Some required components are missing. Please go to chrome://plugins and make sure Native Client is enabled.
+ </message>
<message desc="Message shown if the user is not authorized to perform the requested action." name="IDS_ERROR_NOT_AUTHORIZED">
You do not have permission to perform that action.
</message>
diff --git a/remoting/webapp/base/js/base.js b/remoting/webapp/base/js/base.js
index f8c7cc7..6f11278 100644
--- a/remoting/webapp/base/js/base.js
+++ b/remoting/webapp/base/js/base.js
@@ -415,6 +415,30 @@ base.Promise.withTimeout = function(promise, delay, opt_defaultValue) {
};
/**
+ * Creates a promise that will be rejected if it is not fulfilled within a
+ * certain timeframe.
+ *
+ * This function creates a result promise |R|. If |promise| is fulfilled
+ * (i.e. resolved or rejected) within |delay| milliseconds, then |R|
+ * is resolved or rejected, respectively. Otherwise, |R| is rejected with
+ * |opt_defaultError|.
+ *
+ * @param {!Promise<T>} promise The promise to wrap.
+ * @param {number} delay The number of milliseconds to wait.
+ * @param {*=} opt_defaultError The default error used to reject the promise.
+ * @return {!Promise<T>} A new promise.
+ * @template T
+ */
+base.Promise.rejectAfterTimeout = function(promise, delay, opt_defaultError) {
+ return Promise.race([
+ promise,
+ base.Promise.sleep(delay).then(function() {
+ return Promise.reject(opt_defaultError);
+ })
+ ]);
+};
+
+/**
* Converts a |method| with callbacks into a Promise.
*
* @param {Function} method
@@ -825,3 +849,15 @@ base.resizeWindowToContent = function(opt_centerWindow) {
appWindow.outerBounds.top = Math.round((screenHeight - height) / 2);
}
};
+
+/**
+ * @return {boolean} Whether NaCL is enabled in chrome://plugins.
+ */
+base.isNaclEnabled = function() {
+ for (var i = 0; i < navigator.mimeTypes.length; i++) {
+ if (navigator.mimeTypes.item(i).type == 'application/x-pnacl') {
+ return true;
+ }
+ }
+ return false;
+};
diff --git a/remoting/webapp/base/js/chromoting_event.js b/remoting/webapp/base/js/chromoting_event.js
index 72652f5..af10e8e 100644
--- a/remoting/webapp/base/js/chromoting_event.js
+++ b/remoting/webapp/base/js/chromoting_event.js
@@ -227,7 +227,8 @@ remoting.ChromotingEvent.ConnectionError = {
HOST_OVERLOAD: 11,
P2P_FAILURE: 12,
UNEXPECTED: 13,
- CLIENT_SUSPENDED: 14
+ CLIENT_SUSPENDED: 14,
+ NACL_DISABLED: 15,
};
/** @enum {number} */
diff --git a/remoting/webapp/base/js/client_plugin.js b/remoting/webapp/base/js/client_plugin.js
index cb6b070..df96b85 100644
--- a/remoting/webapp/base/js/client_plugin.js
+++ b/remoting/webapp/base/js/client_plugin.js
@@ -34,9 +34,10 @@ remoting.ClientPlugin.prototype.extensions = function() {};
remoting.ClientPlugin.prototype.element = function() {};
/**
- * @param {function(boolean):void} onDone Completion callback.
+ * @return {Promise<void>} A promise that will resolve when the plugin is
+ * initialized or reject if it fails.
*/
-remoting.ClientPlugin.prototype.initialize = function(onDone) {};
+remoting.ClientPlugin.prototype.initialize = function() {};
/**
* @param {remoting.Host} host The host to connect to.
diff --git a/remoting/webapp/base/js/client_plugin_impl.js b/remoting/webapp/base/js/client_plugin_impl.js
index 80df124..892de83 100644
--- a/remoting/webapp/base/js/client_plugin_impl.js
+++ b/remoting/webapp/base/js/client_plugin_impl.js
@@ -68,8 +68,8 @@ remoting.ClientPluginImpl = function(container, capabilities) {
this.hostCapabilities_ = null;
/** @private {boolean} */
this.helloReceived_ = false;
- /** @private {function(boolean)|null} */
- this.onInitializedCallback_ = null;
+ /** @private {base.Deferred} */
+ this.onInitializedDeferred_ = null;
/** @private {function(string, string):void} */
this.onPairingComplete_ = function(clientId, sharedSecret) {};
/** @private {remoting.ClientSession.PerfStats} */
@@ -77,12 +77,12 @@ remoting.ClientPluginImpl = function(container, capabilities) {
/** @type {remoting.ClientPluginImpl} */
var that = this;
- this.plugin_.addEventListener('message',
- /** @param {Event} event Message event from the plugin. */
- function(event) {
- that.handleMessage_(
- /** @type {remoting.ClientPluginMessage} */ (event.data));
- }, false);
+
+ this.eventHooks_ = new base.Disposables(
+ new base.DomEventHook(
+ this.plugin_, 'message', this.handleMessage_.bind(this), false),
+ new base.DomEventHook(
+ this.plugin_, 'crash', this.onPluginCrashed_.bind(this), false));
/** @private */
this.hostDesktop_ = new remoting.ClientPlugin.HostDesktopImpl(
@@ -113,7 +113,7 @@ remoting.ClientPluginImpl.createPluginElement_ = function() {
plugin.height = '0';
plugin.tabIndex = 0; // Required, otherwise focus() doesn't work.
return plugin;
-}
+};
/**
* @param {remoting.ClientPlugin.ConnectionEventHandler} handler
@@ -148,11 +148,12 @@ remoting.ClientPluginImpl.prototype.setDebugDirtyRegionHandler =
};
/**
- * @param {string|remoting.ClientPluginMessage}
- * rawMessage Message from the plugin.
+ * @param {Event} event Message from the plugin.
* @private
*/
-remoting.ClientPluginImpl.prototype.handleMessage_ = function(rawMessage) {
+remoting.ClientPluginImpl.prototype.handleMessage_ = function(event) {
+ var rawMessage =
+ /** @type {remoting.ClientPluginMessage|string} */ (event.data);
var message =
/** @type {remoting.ClientPluginMessage} */
((typeof(rawMessage) == 'string') ? base.jsonParseSafe(rawMessage)
@@ -167,7 +168,16 @@ remoting.ClientPluginImpl.prototype.handleMessage_ = function(rawMessage) {
} catch(/** @type {*} */ e) {
console.error(e);
}
-}
+};
+
+/** @private */
+remoting.ClientPluginImpl.prototype.onPluginCrashed_ = function(event) {
+ // This should only happen on assert() or exit() according to
+ // https://developer.chrome.com/native-client/devguide/coding/progress-events,
+ // which is extremely unlikely in retail builds. So we just log it without
+ // propagating it to the UI.
+ console.error('Plugin crashed.');
+};
/**
* @param {remoting.ClientPluginMessage}
@@ -238,9 +248,9 @@ remoting.ClientPluginImpl.prototype.handleMessageMethod_ = function(message) {
if (message.method == 'hello') {
this.helloReceived_ = true;
- if (this.onInitializedCallback_ != null) {
- this.onInitializedCallback_(true);
- this.onInitializedCallback_ = null;
+ if (this.onInitializedDeferred_ != null) {
+ this.onInitializedDeferred_.resolve(true);
+ this.onInitializedDeferred_ = null;
}
} else if (message.method == 'onDesktopSize') {
@@ -336,6 +346,9 @@ remoting.ClientPluginImpl.prototype.handleMessageMethod_ = function(message) {
* Deletes the plugin.
*/
remoting.ClientPluginImpl.prototype.dispose = function() {
+ base.dispose(this.eventHooks_);
+ this.eventHooks_ = null;
+
if (this.plugin_) {
this.plugin_.parentNode.removeChild(this.plugin_);
this.plugin_ = null;
@@ -353,14 +366,28 @@ remoting.ClientPluginImpl.prototype.element = function() {
};
/**
- * @param {function(boolean): void} onDone
+ * @return {Promise} A promise that resolves to true if the plugin initializes.
*/
-remoting.ClientPluginImpl.prototype.initialize = function(onDone) {
+remoting.ClientPluginImpl.prototype.initialize = function() {
+ // 99.9 percentile of plugin initialize time from our stats is 141 seconds.
+ var PLUGIN_INTIALIZE_TIMEOUT = 150 * 1000;
+
+ if (!base.isNaclEnabled()) {
+ return Promise.reject(new remoting.Error(remoting.Error.Tag.NACL_DISABLED));
+ }
+
if (this.helloReceived_) {
- onDone(true);
- } else {
- this.onInitializedCallback_ = onDone;
+ return Promise.resolve(true);
}
+
+ if (!this.onInitializedDeferred_) {
+ this.onInitializedDeferred_ = new base.Deferred();
+ }
+
+ return base.Promise.rejectAfterTimeout(
+ this.onInitializedDeferred_.promise(),
+ PLUGIN_INTIALIZE_TIMEOUT,
+ new remoting.Error(remoting.Error.Tag.MISSING_PLUGIN));
};
/**
diff --git a/remoting/webapp/base/js/client_session_factory.js b/remoting/webapp/base/js/client_session_factory.js
index 986466b..0dcadbd 100644
--- a/remoting/webapp/base/js/client_session_factory.js
+++ b/remoting/webapp/base/js/client_session_factory.js
@@ -54,7 +54,8 @@ remoting.ClientSessionFactory.prototype.createSession =
function OnError(/** !remoting.Error */ error) {
logger.logSessionStateChange(
remoting.ChromotingEvent.SessionState.CONNECTION_FAILED,
- remoting.ChromotingEvent.ConnectionError.UNEXPECTED);
+ toConnectionError(error));
+
base.dispose(signalStrategy);
base.dispose(clientPlugin);
throw error;
@@ -121,20 +122,24 @@ function connectSignaling(email, token) {
function createPlugin(container, capabilities) {
var plugin = remoting.ClientPlugin.factory.createPlugin(
container, capabilities);
- var deferred = new base.Deferred();
+ return plugin.initialize().then(function() {
+ return plugin;
+ });
+}
- function onInitialized(/** boolean */ initialized) {
- if (!initialized) {
- console.error('ERROR: remoting plugin not loaded');
- plugin.dispose();
- deferred.reject(new remoting.Error(remoting.Error.Tag.MISSING_PLUGIN));
- return;
+/**
+ * @param {remoting.Error} e
+ * @return {remoting.ChromotingEvent.ConnectionError}
+ */
+function toConnectionError(/** Error */ e) {
+ if (e instanceof remoting.Error) {
+ if (e.getTag() == remoting.Error.Tag.MISSING_PLUGIN) {
+ return remoting.ChromotingEvent.ConnectionError.MISSING_PLUGIN;
+ } else if (e.getTag() == remoting.Error.Tag.NACL_DISABLED) {
+ return remoting.ChromotingEvent.ConnectionError.NACL_DISABLED;
}
-
- deferred.resolve(plugin);
}
- plugin.initialize(onInitialized);
- return deferred.promise();
+ return remoting.ChromotingEvent.ConnectionError.UNEXPECTED;
}
})();
diff --git a/remoting/webapp/base/js/error.js b/remoting/webapp/base/js/error.js
index 8e321c9..28d79c6 100644
--- a/remoting/webapp/base/js/error.js
+++ b/remoting/webapp/base/js/error.js
@@ -123,7 +123,8 @@ remoting.Error.Tag = {
REGISTRATION_FAILED: /*i18n-content*/ 'ERROR_HOST_REGISTRATION_FAILED',
NOT_AUTHORIZED: /*i18n-content*/ 'ERROR_NOT_AUTHORIZED',
// TODO(garykac): Move app-specific errors into separate location.
- APP_NOT_AUTHORIZED: /*i18n-content*/ 'ERROR_APP_NOT_AUTHORIZED'
+ APP_NOT_AUTHORIZED: /*i18n-content*/ 'ERROR_APP_NOT_AUTHORIZED',
+ NACL_DISABLED: /*i18n-content*/ 'ERROR_NACL_DISABLED',
};
// A whole bunch of semi-redundant constants, mostly to reduce to size
diff --git a/remoting/webapp/base/js/session_logger.js b/remoting/webapp/base/js/session_logger.js
index 40a594c..ed70992 100644
--- a/remoting/webapp/base/js/session_logger.js
+++ b/remoting/webapp/base/js/session_logger.js
@@ -360,6 +360,8 @@ function toConnectionError(error) {
return ConnectionError.CLIENT_SUSPENDED;
case remoting.Error.Tag.UNEXPECTED:
return ConnectionError.UNEXPECTED;
+ case remoting.Error.Tag.NACL_DISABLED:
+ return ConnectionError.NACL_DISABLED;
default:
throw new Error('Unknown error Tag : ' + error.getTag());
}
diff --git a/remoting/webapp/crd/js/mock_client_plugin.js b/remoting/webapp/crd/js/mock_client_plugin.js
index 319fec1..1163f1ed 100644
--- a/remoting/webapp/crd/js/mock_client_plugin.js
+++ b/remoting/webapp/crd/js/mock_client_plugin.js
@@ -77,11 +77,13 @@ remoting.MockClientPlugin.prototype.element = function() {
return this.element_;
};
-remoting.MockClientPlugin.prototype.initialize = function(onDone) {
- var that = this;
- Promise.resolve().then(function() {
- onDone(that.mock$initializationResult);
- });
+remoting.MockClientPlugin.prototype.initialize = function() {
+ if (this.mock$initializationResult) {
+ return Promise.resolve();
+ } else {
+ return Promise.reject(
+ new remoting.Error(remoting.Error.Tag.MISSING_PLUGIN));
+ }
};