diff options
-rw-r--r-- | remoting/resources/remoting_strings.grd | 3 | ||||
-rw-r--r-- | remoting/webapp/base/js/base.js | 36 | ||||
-rw-r--r-- | remoting/webapp/base/js/chromoting_event.js | 3 | ||||
-rw-r--r-- | remoting/webapp/base/js/client_plugin.js | 5 | ||||
-rw-r--r-- | remoting/webapp/base/js/client_plugin_impl.js | 69 | ||||
-rw-r--r-- | remoting/webapp/base/js/client_session_factory.js | 29 | ||||
-rw-r--r-- | remoting/webapp/base/js/error.js | 3 | ||||
-rw-r--r-- | remoting/webapp/base/js/session_logger.js | 2 | ||||
-rw-r--r-- | remoting/webapp/crd/js/mock_client_plugin.js | 12 |
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)); + } }; |