diff options
Diffstat (limited to 'remoting/webapp')
-rw-r--r-- | remoting/webapp/all_js_load.gtestjs | 1 | ||||
-rw-r--r-- | remoting/webapp/client_plugin.js | 585 | ||||
-rw-r--r-- | remoting/webapp/client_plugin_async.js | 654 | ||||
-rw-r--r-- | remoting/webapp/client_session.js | 2 | ||||
-rw-r--r-- | remoting/webapp/main.html | 1 |
5 files changed, 521 insertions, 722 deletions
diff --git a/remoting/webapp/all_js_load.gtestjs b/remoting/webapp/all_js_load.gtestjs index c00cc0b..89a14a8 100644 --- a/remoting/webapp/all_js_load.gtestjs +++ b/remoting/webapp/all_js_load.gtestjs @@ -18,7 +18,6 @@ AllJsLoadTest.prototype = { 'browser_globals.gtestjs', // All of our Javascript files should be listed here unless they are // only used by JSCompiler - 'client_plugin_async.js', 'client_plugin.js', 'client_screen.js', 'client_session.js', diff --git a/remoting/webapp/client_plugin.js b/remoting/webapp/client_plugin.js index 8cd8f1c..cc621f2 100644 --- a/remoting/webapp/client_plugin.js +++ b/remoting/webapp/client_plugin.js @@ -2,54 +2,80 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +/** + * @fileoverview + * Class that wraps low-level details of interacting with the client plugin. + * + * This abstracts a <embed> element and controls the plugin which does + * the actual remoting work. It also handles differences between + * client plugins versions when it is necessary. + */ + +'use strict'; + /** @suppress {duplicate} */ var remoting = remoting || {}; /** - * Interface used for ClientPlugin objects. - * @interface + * @param {remoting.ViewerPlugin} plugin The plugin embed element. + * @constructor */ -remoting.ClientPlugin = function() { -}; +remoting.ClientPlugin = function(plugin) { + this.plugin = plugin; -/** @type {number} Desktop width */ -remoting.ClientPlugin.prototype.desktopWidth; -/** @type {number} Desktop height */ -remoting.ClientPlugin.prototype.desktopHeight; -/** @type {number} Desktop x DPI */ -remoting.ClientPlugin.prototype.desktopXDpi; -/** @type {number} Desktop y DPI */ -remoting.ClientPlugin.prototype.desktopYDpi; + this.desktopWidth = 0; + this.desktopHeight = 0; + this.desktopXDpi = 96; + this.desktopYDpi = 96; -/** @type {function(string): void} Outgoing signaling message callback. */ -remoting.ClientPlugin.prototype.onOutgoingIqHandler; -/** @type {function(string): void} Debug messages callback. */ -remoting.ClientPlugin.prototype.onDebugMessageHandler; -/** @type {function(number, number): void} State change callback. */ -remoting.ClientPlugin.prototype.onConnectionStatusUpdateHandler; -/** @type {function(boolean): void} Connection ready state callback. */ -remoting.ClientPlugin.prototype.onConnectionReadyHandler; -/** @type {function(): void} Desktop size change callback. */ -remoting.ClientPlugin.prototype.onDesktopSizeUpdateHandler; -/** @type {function(!Array.<string>): void} Capabilities negotiated callback. */ -remoting.ClientPlugin.prototype.onSetCapabilitiesHandler; -/** @type {function(boolean): void} Request a PIN from the user. */ -remoting.ClientPlugin.prototype.fetchPinHandler; + /** @param {string} iq The Iq stanza received from the host. */ + this.onOutgoingIqHandler = function (iq) {}; + /** @param {string} message Log message. */ + this.onDebugMessageHandler = function (message) {}; + /** + * @param {number} state The connection state. + * @param {number} error The error code, if any. + */ + this.onConnectionStatusUpdateHandler = function(state, error) {}; + /** @param {boolean} ready Connection ready state. */ + this.onConnectionReadyHandler = function(ready) {}; + /** + * @param {string} tokenUrl Token-request URL, received from the host. + * @param {string} hostPublicKey Public key for the host. + * @param {string} scope OAuth scope to request the token for. + */ + this.fetchThirdPartyTokenHandler = function( + tokenUrl, hostPublicKey, scope) {}; + this.onDesktopSizeUpdateHandler = function () {}; + /** @param {!Array.<string>} capabilities The negotiated capabilities. */ + this.onSetCapabilitiesHandler = function (capabilities) {}; + this.fetchPinHandler = function (supportsPairing) {}; -/** - * Initializes the plugin asynchronously and calls specified function - * when done. - * - * @param {function(boolean): void} onDone Function to be called when - * the plugin is initialized. Parameter is set to true when the plugin - * is loaded successfully. - */ -remoting.ClientPlugin.prototype.initialize = function(onDone) {}; + /** @type {number} */ + this.pluginApiVersion_ = -1; + /** @type {Array.<string>} */ + this.pluginApiFeatures_ = []; + /** @type {number} */ + this.pluginApiMinVersion_ = -1; + /** @type {!Array.<string>} */ + this.capabilities_ = []; + /** @type {boolean} */ + this.helloReceived_ = false; + /** @type {function(boolean)|null} */ + this.onInitializedCallback_ = null; + /** @type {function(string, string):void} */ + this.onPairingComplete_ = function(clientId, sharedSecret) {}; + /** @type {remoting.ClientSession.PerfStats} */ + this.perfStats_ = new remoting.ClientSession.PerfStats(); -/** - * @return {boolean} True if the plugin and web-app versions are compatible. - */ -remoting.ClientPlugin.prototype.isSupportedVersion = function() {}; + /** @type {remoting.ClientPlugin} */ + var that = this; + /** @param {Event} event Message event from the plugin. */ + this.plugin.addEventListener('message', function(event) { + that.handleMessage_(event.data); + }, false); + window.setTimeout(this.showPluginForClickToPlay_.bind(this), 500); +}; /** * Set of features for which hasFeature() can be used to test. @@ -71,26 +97,329 @@ remoting.ClientPlugin.Feature = { }; /** - * @param {remoting.ClientPlugin.Feature} feature The feature to test for. - * @return {boolean} True if the plugin supports the named feature. + * Chromoting session API version (for this javascript). + * This is compared with the plugin API version to verify that they are + * compatible. + * + * @const + * @private */ -remoting.ClientPlugin.prototype.hasFeature = function(feature) {}; +remoting.ClientPlugin.prototype.API_VERSION_ = 6; /** - * @return {HTMLEmbedElement} HTML element that corresponds to the plugin. + * The oldest API version that we support. + * This will differ from the |API_VERSION_| if we maintain backward + * compatibility with older API versions. + * + * @const + * @private */ -remoting.ClientPlugin.prototype.element = function() {}; +remoting.ClientPlugin.prototype.API_MIN_VERSION_ = 5; + +/** + * @param {string} messageStr Message from the plugin. + */ +remoting.ClientPlugin.prototype.handleMessage_ = function(messageStr) { + var message = /** @type {{method:string, data:Object.<string,string>}} */ + jsonParseSafe(messageStr); + + if (!message || !('method' in message) || !('data' in message)) { + console.error('Received invalid message from the plugin: ' + messageStr); + return; + } + + /** + * Splits a string into a list of words delimited by spaces. + * @param {string} str String that should be split. + * @return {!Array.<string>} List of words. + */ + var tokenize = function(str) { + /** @type {Array.<string>} */ + var tokens = str.match(/\S+/g); + return tokens ? tokens : []; + }; + + if (message.method == 'hello') { + // Reset the size in case we had to enlarge it to support click-to-play. + this.plugin.width = 0; + this.plugin.height = 0; + if (typeof message.data['apiVersion'] != 'number' || + typeof message.data['apiMinVersion'] != 'number') { + console.error('Received invalid hello message: ' + messageStr); + return; + } + this.pluginApiVersion_ = /** @type {number} */ message.data['apiVersion']; + + if (this.pluginApiVersion_ >= 7) { + if (typeof message.data['apiFeatures'] != 'string') { + console.error('Received invalid hello message: ' + messageStr); + return; + } + this.pluginApiFeatures_ = + /** @type {Array.<string>} */ tokenize(message.data['apiFeatures']); + + // Negotiate capabilities. + + /** @type {!Array.<string>} */ + var requestedCapabilities = []; + if ('requestedCapabilities' in message.data) { + if (typeof message.data['requestedCapabilities'] != 'string') { + console.error('Received invalid hello message: ' + messageStr); + return; + } + requestedCapabilities = tokenize(message.data['requestedCapabilities']); + } + + /** @type {!Array.<string>} */ + var supportedCapabilities = []; + if ('supportedCapabilities' in message.data) { + if (typeof message.data['supportedCapabilities'] != 'string') { + console.error('Received invalid hello message: ' + messageStr); + return; + } + supportedCapabilities = tokenize(message.data['supportedCapabilities']); + } + + // At the moment the webapp does not recognize any of + // 'requestedCapabilities' capabilities (so they all should be disabled) + // and do not care about any of 'supportedCapabilities' capabilities (so + // they all can be enabled). + this.capabilities_ = supportedCapabilities; + + // Let the host know that the webapp can be requested to always send + // the client's dimensions. + this.capabilities_.push( + remoting.ClientSession.Capability.SEND_INITIAL_RESOLUTION); + + // Let the host know that we're interested in knowing whether or not + // it rate-limits desktop-resize requests. + this.capabilities_.push( + remoting.ClientSession.Capability.RATE_LIMIT_RESIZE_REQUESTS); + } else if (this.pluginApiVersion_ >= 6) { + this.pluginApiFeatures_ = ['highQualityScaling', 'injectKeyEvent']; + } else { + this.pluginApiFeatures_ = ['highQualityScaling']; + } + this.pluginApiMinVersion_ = + /** @type {number} */ message.data['apiMinVersion']; + this.helloReceived_ = true; + if (this.onInitializedCallback_ != null) { + this.onInitializedCallback_(true); + this.onInitializedCallback_ = null; + } + } else if (message.method == 'sendOutgoingIq') { + if (typeof message.data['iq'] != 'string') { + console.error('Received invalid sendOutgoingIq message: ' + messageStr); + return; + } + this.onOutgoingIqHandler(message.data['iq']); + } else if (message.method == 'logDebugMessage') { + if (typeof message.data['message'] != 'string') { + console.error('Received invalid logDebugMessage message: ' + messageStr); + return; + } + this.onDebugMessageHandler(message.data['message']); + } else if (message.method == 'onConnectionStatus') { + if (typeof message.data['state'] != 'string' || + !remoting.ClientSession.State.hasOwnProperty(message.data['state']) || + typeof message.data['error'] != 'string') { + console.error('Received invalid onConnectionState message: ' + + messageStr); + return; + } + + /** @type {remoting.ClientSession.State} */ + var state = remoting.ClientSession.State[message.data['state']]; + var error; + if (remoting.ClientSession.ConnectionError.hasOwnProperty( + message.data['error'])) { + error = /** @type {remoting.ClientSession.ConnectionError} */ + remoting.ClientSession.ConnectionError[message.data['error']]; + } else { + error = remoting.ClientSession.ConnectionError.UNKNOWN; + } + + this.onConnectionStatusUpdateHandler(state, error); + } else if (message.method == 'onDesktopSize') { + if (typeof message.data['width'] != 'number' || + typeof message.data['height'] != 'number') { + console.error('Received invalid onDesktopSize message: ' + messageStr); + return; + } + this.desktopWidth = /** @type {number} */ message.data['width']; + this.desktopHeight = /** @type {number} */ message.data['height']; + this.desktopXDpi = (typeof message.data['x_dpi'] == 'number') ? + /** @type {number} */ (message.data['x_dpi']) : 96; + this.desktopYDpi = (typeof message.data['y_dpi'] == 'number') ? + /** @type {number} */ (message.data['y_dpi']) : 96; + this.onDesktopSizeUpdateHandler(); + } else if (message.method == 'onPerfStats') { + if (typeof message.data['videoBandwidth'] != 'number' || + typeof message.data['videoFrameRate'] != 'number' || + typeof message.data['captureLatency'] != 'number' || + typeof message.data['encodeLatency'] != 'number' || + typeof message.data['decodeLatency'] != 'number' || + typeof message.data['renderLatency'] != 'number' || + typeof message.data['roundtripLatency'] != 'number') { + console.error('Received incorrect onPerfStats message: ' + messageStr); + return; + } + this.perfStats_ = + /** @type {remoting.ClientSession.PerfStats} */ message.data; + } else if (message.method == 'injectClipboardItem') { + if (typeof message.data['mimeType'] != 'string' || + typeof message.data['item'] != 'string') { + console.error('Received incorrect injectClipboardItem message.'); + return; + } + if (remoting.clipboard) { + remoting.clipboard.fromHost(message.data['mimeType'], + message.data['item']); + } + } else if (message.method == 'onFirstFrameReceived') { + if (remoting.clientSession) { + remoting.clientSession.onFirstFrameReceived(); + } + } else if (message.method == 'onConnectionReady') { + if (typeof message.data['ready'] != 'boolean') { + console.error('Received incorrect onConnectionReady message.'); + return; + } + var ready = /** @type {boolean} */ message.data['ready']; + this.onConnectionReadyHandler(ready); + } else if (message.method == 'fetchPin') { + // The pairingSupported value in the dictionary indicates whether both + // client and host support pairing. If the client doesn't support pairing, + // then the value won't be there at all, so give it a default of false. + /** @type {boolean} */ + var pairingSupported = false; + if ('pairingSupported' in message.data) { + pairingSupported = + /** @type {boolean} */ message.data['pairingSupported']; + if (typeof pairingSupported != 'boolean') { + console.error('Received incorrect fetchPin message.'); + return; + } + } + this.fetchPinHandler(pairingSupported); + } else if (message.method == 'setCapabilities') { + if (typeof message.data['capabilities'] != 'string') { + console.error('Received incorrect setCapabilities message.'); + return; + } + + /** @type {!Array.<string>} */ + var capabilities = tokenize(message.data['capabilities']); + this.onSetCapabilitiesHandler(capabilities); + } else if (message.method == 'fetchThirdPartyToken') { + if (typeof message.data['tokenUrl'] != 'string' || + typeof message.data['hostPublicKey'] != 'string' || + typeof message.data['scope'] != 'string') { + console.error('Received incorrect fetchThirdPartyToken message.'); + return; + } + var tokenUrl = /** @type {string} */ message.data['tokenUrl']; + var hostPublicKey = + /** @type {string} */ message.data['hostPublicKey']; + var scope = /** @type {string} */ message.data['scope']; + this.fetchThirdPartyTokenHandler(tokenUrl, hostPublicKey, scope); + } else if (message.method == 'pairingResponse') { + var clientId = /** @type {string} */ message.data['clientId']; + var sharedSecret = /** @type {string} */ message.data['sharedSecret']; + if (typeof clientId != 'string' || typeof sharedSecret != 'string') { + console.error('Received incorrect pairingResponse message.'); + return; + } + this.onPairingComplete_(clientId, sharedSecret); + } else if (message.method == 'extensionMessage') { + if (typeof(message.data['type']) != 'string' || + typeof(message.data['data']) != 'string') { + console.error('Invalid extension message:', message.data); + return; + } + switch (message.data['type']) { + case 'test-echo-reply': + console.log('Got echo reply: ' + message.data['data']); + break; + default: + console.log('Unexpected message received: ' + + message.data['type'] + ': ' + message.data['data']); + } + } +}; /** * Deletes the plugin. */ -remoting.ClientPlugin.prototype.cleanup = function() {}; +remoting.ClientPlugin.prototype.cleanup = function() { + this.plugin.parentNode.removeChild(this.plugin); +}; + +/** + * @return {HTMLEmbedElement} HTML element that correspods to the plugin. + */ +remoting.ClientPlugin.prototype.element = function() { + return this.plugin; +}; + +/** + * @param {function(boolean): void} onDone + */ +remoting.ClientPlugin.prototype.initialize = function(onDone) { + if (this.helloReceived_) { + onDone(true); + } else { + this.onInitializedCallback_ = onDone; + } +}; + +/** + * @return {boolean} True if the plugin and web-app versions are compatible. + */ +remoting.ClientPlugin.prototype.isSupportedVersion = function() { + if (!this.helloReceived_) { + console.error( + "isSupportedVersion() is called before the plugin is initialized."); + return false; + } + return this.API_VERSION_ >= this.pluginApiMinVersion_ && + this.pluginApiVersion_ >= this.API_MIN_VERSION_; +}; + +/** + * @param {remoting.ClientPlugin.Feature} feature The feature to test for. + * @return {boolean} True if the plugin supports the named feature. + */ +remoting.ClientPlugin.prototype.hasFeature = function(feature) { + if (!this.helloReceived_) { + console.error( + "hasFeature() is called before the plugin is initialized."); + return false; + } + return this.pluginApiFeatures_.indexOf(feature) > -1; +}; + +/** + * @return {boolean} True if the plugin supports the injectKeyEvent API. + */ +remoting.ClientPlugin.prototype.isInjectKeyEventSupported = function() { + return this.pluginApiVersion_ >= 6; +}; /** - * Must be called for each incoming stanza received from the host. * @param {string} iq Incoming IQ stanza. */ -remoting.ClientPlugin.prototype.onIncomingIq = function(iq) {}; +remoting.ClientPlugin.prototype.onIncomingIq = function(iq) { + if (this.plugin && this.plugin.postMessage) { + this.plugin.postMessage(JSON.stringify( + { method: 'incomingIq', data: { iq: iq } })); + } else { + // plugin.onIq may not be set after the plugin has been shut + // down. Particularly this happens when we receive response to + // session-terminate stanza. + console.warn('plugin.onIq is not set so dropping incoming message.'); + } +}; /** * @param {string} hostJid The jid of the host to connect to. @@ -111,12 +440,29 @@ remoting.ClientPlugin.prototype.onIncomingIq = function(iq) {}; remoting.ClientPlugin.prototype.connect = function( hostJid, hostPublicKey, localJid, sharedSecret, authenticationMethods, authenticationTag, - clientPairingId, clientPairedSecret) {}; + clientPairingId, clientPairedSecret) { + this.plugin.postMessage(JSON.stringify( + { method: 'connect', data: { + hostJid: hostJid, + hostPublicKey: hostPublicKey, + localJid: localJid, + sharedSecret: sharedSecret, + authenticationMethods: authenticationMethods, + authenticationTag: authenticationTag, + capabilities: this.capabilities_.join(" "), + clientPairingId: clientPairingId, + clientPairedSecret: clientPairedSecret + } + })); +}; /** * Release all currently pressed keys. */ -remoting.ClientPlugin.prototype.releaseAllKeys = function() {}; +remoting.ClientPlugin.prototype.releaseAllKeys = function() { + this.plugin.postMessage(JSON.stringify( + { method: 'releaseAllKeys', data: {} })); +}; /** * Send a key event to the host. @@ -125,7 +471,13 @@ remoting.ClientPlugin.prototype.releaseAllKeys = function() {}; * @param {boolean} pressed True to inject a key press, False for a release. */ remoting.ClientPlugin.prototype.injectKeyEvent = - function(usbKeycode, pressed) {}; + function(usbKeycode, pressed) { + this.plugin.postMessage(JSON.stringify( + { method: 'injectKeyEvent', data: { + 'usbKeycode': usbKeycode, + 'pressed': pressed} + })); +}; /** * Remap one USB keycode to another in all subsequent key events. @@ -134,7 +486,13 @@ remoting.ClientPlugin.prototype.injectKeyEvent = * @param {number} toKeycode The USB-style code to remap the key to. */ remoting.ClientPlugin.prototype.remapKey = - function(fromKeycode, toKeycode) {}; + function(fromKeycode, toKeycode) { + this.plugin.postMessage(JSON.stringify( + { method: 'remapKey', data: { + 'fromKeycode': fromKeycode, + 'toKeycode': toKeycode} + })); +}; /** * Enable/disable redirection of the specified key to the web-app. @@ -142,14 +500,22 @@ remoting.ClientPlugin.prototype.remapKey = * @param {number} keycode The USB-style code of the key. * @param {Boolean} trap True to enable trapping, False to disable. */ -remoting.ClientPlugin.prototype.trapKey = function(keycode, trap) {}; +remoting.ClientPlugin.prototype.trapKey = function(keycode, trap) { + this.plugin.postMessage(JSON.stringify( + { method: 'trapKey', data: { + 'keycode': keycode, + 'trap': trap} + })); +}; /** - * Returns an associative array with a set of stats for this connection. + * Returns an associative array with a set of stats for this connecton. * * @return {remoting.ClientSession.PerfStats} The connection statistics. */ -remoting.ClientPlugin.prototype.getPerfStats = function() {}; +remoting.ClientPlugin.prototype.getPerfStats = function() { + return this.perfStats_; +}; /** * Sends a clipboard item to the host. @@ -157,7 +523,14 @@ remoting.ClientPlugin.prototype.getPerfStats = function() {}; * @param {string} mimeType The MIME type of the clipboard item. * @param {string} item The clipboard item. */ -remoting.ClientPlugin.prototype.sendClipboardItem = function(mimeType, item) {}; +remoting.ClientPlugin.prototype.sendClipboardItem = + function(mimeType, item) { + if (!this.hasFeature(remoting.ClientPlugin.Feature.SEND_CLIPBOARD_ITEM)) + return; + this.plugin.postMessage(JSON.stringify( + { method: 'sendClipboardItem', + data: { mimeType: mimeType, item: item }})); +}; /** * Notifies the host that the client has the specified size and pixel density. @@ -167,7 +540,16 @@ remoting.ClientPlugin.prototype.sendClipboardItem = function(mimeType, item) {}; * @param {number} device_scale The number of device pixels per DIP. */ remoting.ClientPlugin.prototype.notifyClientResolution = - function(width, height, device_scale) {}; + function(width, height, device_scale) { + if (this.hasFeature(remoting.ClientPlugin.Feature.NOTIFY_CLIENT_RESOLUTION)) { + var dpi = Math.floor(device_scale * 96); + this.plugin.postMessage(JSON.stringify( + { method: 'notifyClientResolution', + data: { width: Math.floor(width * device_scale), + height: Math.floor(height * device_scale), + x_dpi: dpi, y_dpi: dpi }})); + } +}; /** * Requests that the host pause or resume sending video updates. @@ -175,7 +557,12 @@ remoting.ClientPlugin.prototype.notifyClientResolution = * @param {boolean} pause True to suspend video updates, false otherwise. */ remoting.ClientPlugin.prototype.pauseVideo = - function(pause) {}; + function(pause) { + if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_VIDEO)) + return; + this.plugin.postMessage(JSON.stringify( + { method: 'pauseVideo', data: { pause: pause }})); +}; /** * Requests that the host pause or resume sending audio updates. @@ -183,19 +570,38 @@ remoting.ClientPlugin.prototype.pauseVideo = * @param {boolean} pause True to suspend audio updates, false otherwise. */ remoting.ClientPlugin.prototype.pauseAudio = - function(pause) {}; + function(pause) { + if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_AUDIO)) + return; + this.plugin.postMessage(JSON.stringify( + { method: 'pauseAudio', data: { pause: pause }})); +}; /** - * Gives the client authenticator the PIN. + * Called when a PIN is obtained from the user. * * @param {string} pin The PIN. */ -remoting.ClientPlugin.prototype.onPinFetched = function(pin) {}; +remoting.ClientPlugin.prototype.onPinFetched = + function(pin) { + if (!this.hasFeature(remoting.ClientPlugin.Feature.ASYNC_PIN)) { + return; + } + this.plugin.postMessage(JSON.stringify( + { method: 'onPinFetched', data: { pin: pin }})); +}; /** * Tells the plugin to ask for the PIN asynchronously. */ -remoting.ClientPlugin.prototype.useAsyncPinDialog = function() {}; +remoting.ClientPlugin.prototype.useAsyncPinDialog = + function() { + if (!this.hasFeature(remoting.ClientPlugin.Feature.ASYNC_PIN)) { + return; + } + this.plugin.postMessage(JSON.stringify( + { method: 'useAsyncPinDialog', data: {} })); +}; /** * Sets the third party authentication token and shared secret. @@ -203,8 +609,12 @@ remoting.ClientPlugin.prototype.useAsyncPinDialog = function() {}; * @param {string} token The token received from the token URL. * @param {string} sharedSecret Shared secret received from the token URL. */ -remoting.ClientPlugin.prototype.onThirdPartyTokenFetched = - function(token, sharedSecret) {}; +remoting.ClientPlugin.prototype.onThirdPartyTokenFetched = function( + token, sharedSecret) { + this.plugin.postMessage(JSON.stringify( + { method: 'onThirdPartyTokenFetched', + data: { token: token, sharedSecret: sharedSecret}})); +}; /** * Request pairing with the host for PIN-less authentication. @@ -213,5 +623,50 @@ remoting.ClientPlugin.prototype.onThirdPartyTokenFetched = * @param {function(string, string):void} onDone, Callback to receive the * client id and shared secret when they are available. */ -remoting.ClientPlugin.prototype.requestPairing = function( - clientName, onDone) {}; +remoting.ClientPlugin.prototype.requestPairing = + function(clientName, onDone) { + if (!this.hasFeature(remoting.ClientPlugin.Feature.PINLESS_AUTH)) { + return; + } + this.onPairingComplete_ = onDone; + this.plugin.postMessage(JSON.stringify( + { method: 'requestPairing', data: { clientName: clientName } })); +}; + +/** + * Send an extension message to the host. + * + * @param {string} type The message type. + * @param {Object} message The message payload. + */ +remoting.ClientPlugin.prototype.sendClientMessage = + function(type, message) { + if (!this.hasFeature(remoting.ClientPlugin.Feature.EXTENSION_MESSAGE)) { + return; + } + this.plugin.postMessage(JSON.stringify( + { method: 'extensionMessage', + data: { type: type, data: JSON.stringify(message) } })); + +}; + +/** + * If we haven't yet received a "hello" message from the plugin, change its + * size so that the user can confirm it if click-to-play is enabled, or can + * see the "this plugin is disabled" message if it is actually disabled. + * @private + */ +remoting.ClientPlugin.prototype.showPluginForClickToPlay_ = function() { + if (!this.helloReceived_) { + var width = 200; + var height = 200; + this.plugin.width = width; + this.plugin.height = height; + // Center the plugin just underneath the "Connnecting..." dialog. + var parentNode = this.plugin.parentNode; + var dialog = document.getElementById('client-dialog'); + var dialogRect = dialog.getBoundingClientRect(); + parentNode.style.top = (dialogRect.bottom + 16) + 'px'; + parentNode.style.left = (window.innerWidth - width) / 2 + 'px'; + } +}; diff --git a/remoting/webapp/client_plugin_async.js b/remoting/webapp/client_plugin_async.js deleted file mode 100644 index 3e0461f..0000000 --- a/remoting/webapp/client_plugin_async.js +++ /dev/null @@ -1,654 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -/** - * @fileoverview - * Class that wraps low-level details of interacting with the client plugin. - * - * This abstracts a <embed> element and controls the plugin which does - * the actual remoting work. It also handles differences between - * client plugins versions when it is necessary. - */ - -'use strict'; - -/** @suppress {duplicate} */ -var remoting = remoting || {}; - -/** - * @param {remoting.ViewerPlugin} plugin The plugin embed element. - * @constructor - * @implements {remoting.ClientPlugin} - */ -remoting.ClientPluginAsync = function(plugin) { - this.plugin = plugin; - - this.desktopWidth = 0; - this.desktopHeight = 0; - this.desktopXDpi = 96; - this.desktopYDpi = 96; - - /** @param {string} iq The Iq stanza received from the host. */ - this.onOutgoingIqHandler = function (iq) {}; - /** @param {string} message Log message. */ - this.onDebugMessageHandler = function (message) {}; - /** - * @param {number} state The connection state. - * @param {number} error The error code, if any. - */ - this.onConnectionStatusUpdateHandler = function(state, error) {}; - /** @param {boolean} ready Connection ready state. */ - this.onConnectionReadyHandler = function(ready) {}; - /** - * @param {string} tokenUrl Token-request URL, received from the host. - * @param {string} hostPublicKey Public key for the host. - * @param {string} scope OAuth scope to request the token for. - */ - this.fetchThirdPartyTokenHandler = function( - tokenUrl, hostPublicKey, scope) {}; - this.onDesktopSizeUpdateHandler = function () {}; - /** @param {!Array.<string>} capabilities The negotiated capabilities. */ - this.onSetCapabilitiesHandler = function (capabilities) {}; - this.fetchPinHandler = function (supportsPairing) {}; - - /** @type {number} */ - this.pluginApiVersion_ = -1; - /** @type {Array.<string>} */ - this.pluginApiFeatures_ = []; - /** @type {number} */ - this.pluginApiMinVersion_ = -1; - /** @type {!Array.<string>} */ - this.capabilities_ = []; - /** @type {boolean} */ - this.helloReceived_ = false; - /** @type {function(boolean)|null} */ - this.onInitializedCallback_ = null; - /** @type {function(string, string):void} */ - this.onPairingComplete_ = function(clientId, sharedSecret) {}; - /** @type {remoting.ClientSession.PerfStats} */ - this.perfStats_ = new remoting.ClientSession.PerfStats(); - - /** @type {remoting.ClientPluginAsync} */ - var that = this; - /** @param {Event} event Message event from the plugin. */ - this.plugin.addEventListener('message', function(event) { - that.handleMessage_(event.data); - }, false); - window.setTimeout(this.showPluginForClickToPlay_.bind(this), 500); -}; - -/** - * Chromoting session API version (for this javascript). - * This is compared with the plugin API version to verify that they are - * compatible. - * - * @const - * @private - */ -remoting.ClientPluginAsync.prototype.API_VERSION_ = 6; - -/** - * The oldest API version that we support. - * This will differ from the |API_VERSION_| if we maintain backward - * compatibility with older API versions. - * - * @const - * @private - */ -remoting.ClientPluginAsync.prototype.API_MIN_VERSION_ = 5; - -/** - * @param {string} messageStr Message from the plugin. - */ -remoting.ClientPluginAsync.prototype.handleMessage_ = function(messageStr) { - var message = /** @type {{method:string, data:Object.<string,string>}} */ - jsonParseSafe(messageStr); - - if (!message || !('method' in message) || !('data' in message)) { - console.error('Received invalid message from the plugin: ' + messageStr); - return; - } - - /** - * Splits a string into a list of words delimited by spaces. - * @param {string} str String that should be split. - * @return {!Array.<string>} List of words. - */ - var tokenize = function(str) { - /** @type {Array.<string>} */ - var tokens = str.match(/\S+/g); - return tokens ? tokens : []; - }; - - if (message.method == 'hello') { - // Reset the size in case we had to enlarge it to support click-to-play. - this.plugin.width = 0; - this.plugin.height = 0; - if (typeof message.data['apiVersion'] != 'number' || - typeof message.data['apiMinVersion'] != 'number') { - console.error('Received invalid hello message: ' + messageStr); - return; - } - this.pluginApiVersion_ = /** @type {number} */ message.data['apiVersion']; - - if (this.pluginApiVersion_ >= 7) { - if (typeof message.data['apiFeatures'] != 'string') { - console.error('Received invalid hello message: ' + messageStr); - return; - } - this.pluginApiFeatures_ = - /** @type {Array.<string>} */ tokenize(message.data['apiFeatures']); - - // Negotiate capabilities. - - /** @type {!Array.<string>} */ - var requestedCapabilities = []; - if ('requestedCapabilities' in message.data) { - if (typeof message.data['requestedCapabilities'] != 'string') { - console.error('Received invalid hello message: ' + messageStr); - return; - } - requestedCapabilities = tokenize(message.data['requestedCapabilities']); - } - - /** @type {!Array.<string>} */ - var supportedCapabilities = []; - if ('supportedCapabilities' in message.data) { - if (typeof message.data['supportedCapabilities'] != 'string') { - console.error('Received invalid hello message: ' + messageStr); - return; - } - supportedCapabilities = tokenize(message.data['supportedCapabilities']); - } - - // At the moment the webapp does not recognize any of - // 'requestedCapabilities' capabilities (so they all should be disabled) - // and do not care about any of 'supportedCapabilities' capabilities (so - // they all can be enabled). - this.capabilities_ = supportedCapabilities; - - // Let the host know that the webapp can be requested to always send - // the client's dimensions. - this.capabilities_.push( - remoting.ClientSession.Capability.SEND_INITIAL_RESOLUTION); - - // Let the host know that we're interested in knowing whether or not - // it rate-limits desktop-resize requests. - this.capabilities_.push( - remoting.ClientSession.Capability.RATE_LIMIT_RESIZE_REQUESTS); - } else if (this.pluginApiVersion_ >= 6) { - this.pluginApiFeatures_ = ['highQualityScaling', 'injectKeyEvent']; - } else { - this.pluginApiFeatures_ = ['highQualityScaling']; - } - this.pluginApiMinVersion_ = - /** @type {number} */ message.data['apiMinVersion']; - this.helloReceived_ = true; - if (this.onInitializedCallback_ != null) { - this.onInitializedCallback_(true); - this.onInitializedCallback_ = null; - } - } else if (message.method == 'sendOutgoingIq') { - if (typeof message.data['iq'] != 'string') { - console.error('Received invalid sendOutgoingIq message: ' + messageStr); - return; - } - this.onOutgoingIqHandler(message.data['iq']); - } else if (message.method == 'logDebugMessage') { - if (typeof message.data['message'] != 'string') { - console.error('Received invalid logDebugMessage message: ' + messageStr); - return; - } - this.onDebugMessageHandler(message.data['message']); - } else if (message.method == 'onConnectionStatus') { - if (typeof message.data['state'] != 'string' || - !remoting.ClientSession.State.hasOwnProperty(message.data['state']) || - typeof message.data['error'] != 'string') { - console.error('Received invalid onConnectionState message: ' + - messageStr); - return; - } - - /** @type {remoting.ClientSession.State} */ - var state = remoting.ClientSession.State[message.data['state']]; - var error; - if (remoting.ClientSession.ConnectionError.hasOwnProperty( - message.data['error'])) { - error = /** @type {remoting.ClientSession.ConnectionError} */ - remoting.ClientSession.ConnectionError[message.data['error']]; - } else { - error = remoting.ClientSession.ConnectionError.UNKNOWN; - } - - this.onConnectionStatusUpdateHandler(state, error); - } else if (message.method == 'onDesktopSize') { - if (typeof message.data['width'] != 'number' || - typeof message.data['height'] != 'number') { - console.error('Received invalid onDesktopSize message: ' + messageStr); - return; - } - this.desktopWidth = /** @type {number} */ message.data['width']; - this.desktopHeight = /** @type {number} */ message.data['height']; - this.desktopXDpi = (typeof message.data['x_dpi'] == 'number') ? - /** @type {number} */ (message.data['x_dpi']) : 96; - this.desktopYDpi = (typeof message.data['y_dpi'] == 'number') ? - /** @type {number} */ (message.data['y_dpi']) : 96; - this.onDesktopSizeUpdateHandler(); - } else if (message.method == 'onPerfStats') { - if (typeof message.data['videoBandwidth'] != 'number' || - typeof message.data['videoFrameRate'] != 'number' || - typeof message.data['captureLatency'] != 'number' || - typeof message.data['encodeLatency'] != 'number' || - typeof message.data['decodeLatency'] != 'number' || - typeof message.data['renderLatency'] != 'number' || - typeof message.data['roundtripLatency'] != 'number') { - console.error('Received incorrect onPerfStats message: ' + messageStr); - return; - } - this.perfStats_ = - /** @type {remoting.ClientSession.PerfStats} */ message.data; - } else if (message.method == 'injectClipboardItem') { - if (typeof message.data['mimeType'] != 'string' || - typeof message.data['item'] != 'string') { - console.error('Received incorrect injectClipboardItem message.'); - return; - } - if (remoting.clipboard) { - remoting.clipboard.fromHost(message.data['mimeType'], - message.data['item']); - } - } else if (message.method == 'onFirstFrameReceived') { - if (remoting.clientSession) { - remoting.clientSession.onFirstFrameReceived(); - } - } else if (message.method == 'onConnectionReady') { - if (typeof message.data['ready'] != 'boolean') { - console.error('Received incorrect onConnectionReady message.'); - return; - } - var ready = /** @type {boolean} */ message.data['ready']; - this.onConnectionReadyHandler(ready); - } else if (message.method == 'fetchPin') { - // The pairingSupported value in the dictionary indicates whether both - // client and host support pairing. If the client doesn't support pairing, - // then the value won't be there at all, so give it a default of false. - /** @type {boolean} */ - var pairingSupported = false; - if ('pairingSupported' in message.data) { - pairingSupported = - /** @type {boolean} */ message.data['pairingSupported']; - if (typeof pairingSupported != 'boolean') { - console.error('Received incorrect fetchPin message.'); - return; - } - } - this.fetchPinHandler(pairingSupported); - } else if (message.method == 'setCapabilities') { - if (typeof message.data['capabilities'] != 'string') { - console.error('Received incorrect setCapabilities message.'); - return; - } - - /** @type {!Array.<string>} */ - var capabilities = tokenize(message.data['capabilities']); - this.onSetCapabilitiesHandler(capabilities); - } else if (message.method == 'fetchThirdPartyToken') { - if (typeof message.data['tokenUrl'] != 'string' || - typeof message.data['hostPublicKey'] != 'string' || - typeof message.data['scope'] != 'string') { - console.error('Received incorrect fetchThirdPartyToken message.'); - return; - } - var tokenUrl = /** @type {string} */ message.data['tokenUrl']; - var hostPublicKey = - /** @type {string} */ message.data['hostPublicKey']; - var scope = /** @type {string} */ message.data['scope']; - this.fetchThirdPartyTokenHandler(tokenUrl, hostPublicKey, scope); - } else if (message.method == 'pairingResponse') { - var clientId = /** @type {string} */ message.data['clientId']; - var sharedSecret = /** @type {string} */ message.data['sharedSecret']; - if (typeof clientId != 'string' || typeof sharedSecret != 'string') { - console.error('Received incorrect pairingResponse message.'); - return; - } - this.onPairingComplete_(clientId, sharedSecret); - } else if (message.method == 'extensionMessage') { - if (typeof(message.data['type']) != 'string' || - typeof(message.data['data']) != 'string') { - console.error('Invalid extension message:', message.data); - return; - } - switch (message.data['type']) { - case 'test-echo-reply': - console.log('Got echo reply: ' + message.data['data']); - break; - default: - console.log('Unexpected message received: ' + - message.data['type'] + ': ' + message.data['data']); - } - } -}; - -/** - * Deletes the plugin. - */ -remoting.ClientPluginAsync.prototype.cleanup = function() { - this.plugin.parentNode.removeChild(this.plugin); -}; - -/** - * @return {HTMLEmbedElement} HTML element that correspods to the plugin. - */ -remoting.ClientPluginAsync.prototype.element = function() { - return this.plugin; -}; - -/** - * @param {function(boolean): void} onDone - */ -remoting.ClientPluginAsync.prototype.initialize = function(onDone) { - if (this.helloReceived_) { - onDone(true); - } else { - this.onInitializedCallback_ = onDone; - } -}; - -/** - * @return {boolean} True if the plugin and web-app versions are compatible. - */ -remoting.ClientPluginAsync.prototype.isSupportedVersion = function() { - if (!this.helloReceived_) { - console.error( - "isSupportedVersion() is called before the plugin is initialized."); - return false; - } - return this.API_VERSION_ >= this.pluginApiMinVersion_ && - this.pluginApiVersion_ >= this.API_MIN_VERSION_; -}; - -/** - * @param {remoting.ClientPlugin.Feature} feature The feature to test for. - * @return {boolean} True if the plugin supports the named feature. - */ -remoting.ClientPluginAsync.prototype.hasFeature = function(feature) { - if (!this.helloReceived_) { - console.error( - "hasFeature() is called before the plugin is initialized."); - return false; - } - return this.pluginApiFeatures_.indexOf(feature) > -1; -}; - -/** - * @return {boolean} True if the plugin supports the injectKeyEvent API. - */ -remoting.ClientPluginAsync.prototype.isInjectKeyEventSupported = function() { - return this.pluginApiVersion_ >= 6; -}; - -/** - * @param {string} iq Incoming IQ stanza. - */ -remoting.ClientPluginAsync.prototype.onIncomingIq = function(iq) { - if (this.plugin && this.plugin.postMessage) { - this.plugin.postMessage(JSON.stringify( - { method: 'incomingIq', data: { iq: iq } })); - } else { - // plugin.onIq may not be set after the plugin has been shut - // down. Particularly this happens when we receive response to - // session-terminate stanza. - console.warn('plugin.onIq is not set so dropping incoming message.'); - } -}; - -/** - * @param {string} hostJid The jid of the host to connect to. - * @param {string} hostPublicKey The base64 encoded version of the host's - * public key. - * @param {string} localJid Local jid. - * @param {string} sharedSecret The access code for IT2Me or the PIN - * for Me2Me. - * @param {string} authenticationMethods Comma-separated list of - * authentication methods the client should attempt to use. - * @param {string} authenticationTag A host-specific tag to mix into - * authentication hashes. - * @param {string} clientPairingId For paired Me2Me connections, the - * pairing id for this client, as issued by the host. - * @param {string} clientPairedSecret For paired Me2Me connections, the - * paired secret for this client, as issued by the host. - */ -remoting.ClientPluginAsync.prototype.connect = function( - hostJid, hostPublicKey, localJid, sharedSecret, - authenticationMethods, authenticationTag, - clientPairingId, clientPairedSecret) { - this.plugin.postMessage(JSON.stringify( - { method: 'connect', data: { - hostJid: hostJid, - hostPublicKey: hostPublicKey, - localJid: localJid, - sharedSecret: sharedSecret, - authenticationMethods: authenticationMethods, - authenticationTag: authenticationTag, - capabilities: this.capabilities_.join(" "), - clientPairingId: clientPairingId, - clientPairedSecret: clientPairedSecret - } - })); -}; - -/** - * Release all currently pressed keys. - */ -remoting.ClientPluginAsync.prototype.releaseAllKeys = function() { - this.plugin.postMessage(JSON.stringify( - { method: 'releaseAllKeys', data: {} })); -}; - -/** - * Send a key event to the host. - * - * @param {number} usbKeycode The USB-style code of the key to inject. - * @param {boolean} pressed True to inject a key press, False for a release. - */ -remoting.ClientPluginAsync.prototype.injectKeyEvent = - function(usbKeycode, pressed) { - this.plugin.postMessage(JSON.stringify( - { method: 'injectKeyEvent', data: { - 'usbKeycode': usbKeycode, - 'pressed': pressed} - })); -}; - -/** - * Remap one USB keycode to another in all subsequent key events. - * - * @param {number} fromKeycode The USB-style code of the key to remap. - * @param {number} toKeycode The USB-style code to remap the key to. - */ -remoting.ClientPluginAsync.prototype.remapKey = - function(fromKeycode, toKeycode) { - this.plugin.postMessage(JSON.stringify( - { method: 'remapKey', data: { - 'fromKeycode': fromKeycode, - 'toKeycode': toKeycode} - })); -}; - -/** - * Enable/disable redirection of the specified key to the web-app. - * - * @param {number} keycode The USB-style code of the key. - * @param {Boolean} trap True to enable trapping, False to disable. - */ -remoting.ClientPluginAsync.prototype.trapKey = function(keycode, trap) { - this.plugin.postMessage(JSON.stringify( - { method: 'trapKey', data: { - 'keycode': keycode, - 'trap': trap} - })); -}; - -/** - * Returns an associative array with a set of stats for this connecton. - * - * @return {remoting.ClientSession.PerfStats} The connection statistics. - */ -remoting.ClientPluginAsync.prototype.getPerfStats = function() { - return this.perfStats_; -}; - -/** - * Sends a clipboard item to the host. - * - * @param {string} mimeType The MIME type of the clipboard item. - * @param {string} item The clipboard item. - */ -remoting.ClientPluginAsync.prototype.sendClipboardItem = - function(mimeType, item) { - if (!this.hasFeature(remoting.ClientPlugin.Feature.SEND_CLIPBOARD_ITEM)) - return; - this.plugin.postMessage(JSON.stringify( - { method: 'sendClipboardItem', - data: { mimeType: mimeType, item: item }})); -}; - -/** - * Notifies the host that the client has the specified size and pixel density. - * - * @param {number} width The available client width in DIPs. - * @param {number} height The available client height in DIPs. - * @param {number} device_scale The number of device pixels per DIP. - */ -remoting.ClientPluginAsync.prototype.notifyClientResolution = - function(width, height, device_scale) { - if (this.hasFeature(remoting.ClientPlugin.Feature.NOTIFY_CLIENT_RESOLUTION)) { - var dpi = Math.floor(device_scale * 96); - this.plugin.postMessage(JSON.stringify( - { method: 'notifyClientResolution', - data: { width: Math.floor(width * device_scale), - height: Math.floor(height * device_scale), - x_dpi: dpi, y_dpi: dpi }})); - } -}; - -/** - * Requests that the host pause or resume sending video updates. - * - * @param {boolean} pause True to suspend video updates, false otherwise. - */ -remoting.ClientPluginAsync.prototype.pauseVideo = - function(pause) { - if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_VIDEO)) - return; - this.plugin.postMessage(JSON.stringify( - { method: 'pauseVideo', data: { pause: pause }})); -}; - -/** - * Requests that the host pause or resume sending audio updates. - * - * @param {boolean} pause True to suspend audio updates, false otherwise. - */ -remoting.ClientPluginAsync.prototype.pauseAudio = - function(pause) { - if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_AUDIO)) - return; - this.plugin.postMessage(JSON.stringify( - { method: 'pauseAudio', data: { pause: pause }})); -}; - -/** - * Called when a PIN is obtained from the user. - * - * @param {string} pin The PIN. - */ -remoting.ClientPluginAsync.prototype.onPinFetched = - function(pin) { - if (!this.hasFeature(remoting.ClientPlugin.Feature.ASYNC_PIN)) { - return; - } - this.plugin.postMessage(JSON.stringify( - { method: 'onPinFetched', data: { pin: pin }})); -}; - -/** - * Tells the plugin to ask for the PIN asynchronously. - */ -remoting.ClientPluginAsync.prototype.useAsyncPinDialog = - function() { - if (!this.hasFeature(remoting.ClientPlugin.Feature.ASYNC_PIN)) { - return; - } - this.plugin.postMessage(JSON.stringify( - { method: 'useAsyncPinDialog', data: {} })); -}; - -/** - * Sets the third party authentication token and shared secret. - * - * @param {string} token The token received from the token URL. - * @param {string} sharedSecret Shared secret received from the token URL. - */ -remoting.ClientPluginAsync.prototype.onThirdPartyTokenFetched = function( - token, sharedSecret) { - this.plugin.postMessage(JSON.stringify( - { method: 'onThirdPartyTokenFetched', - data: { token: token, sharedSecret: sharedSecret}})); -}; - -/** - * Request pairing with the host for PIN-less authentication. - * - * @param {string} clientName The human-readable name of the client. - * @param {function(string, string):void} onDone, Callback to receive the - * client id and shared secret when they are available. - */ -remoting.ClientPluginAsync.prototype.requestPairing = - function(clientName, onDone) { - if (!this.hasFeature(remoting.ClientPlugin.Feature.PINLESS_AUTH)) { - return; - } - this.onPairingComplete_ = onDone; - this.plugin.postMessage(JSON.stringify( - { method: 'requestPairing', data: { clientName: clientName } })); -}; - -/** - * Send an extension message to the host. - * - * @param {string} type The message type. - * @param {Object} message The message payload. - */ -remoting.ClientPluginAsync.prototype.sendClientMessage = - function(type, message) { - if (!this.hasFeature(remoting.ClientPlugin.Feature.EXTENSION_MESSAGE)) { - return; - } - this.plugin.postMessage(JSON.stringify( - { method: 'extensionMessage', - data: { type: type, data: JSON.stringify(message) } })); - -}; - -/** - * If we haven't yet received a "hello" message from the plugin, change its - * size so that the user can confirm it if click-to-play is enabled, or can - * see the "this plugin is disabled" message if it is actually disabled. - * @private - */ -remoting.ClientPluginAsync.prototype.showPluginForClickToPlay_ = function() { - if (!this.helloReceived_) { - var width = 200; - var height = 200; - this.plugin.width = width; - this.plugin.height = height; - // Center the plugin just underneath the "Connnecting..." dialog. - var parentNode = this.plugin.parentNode; - var dialog = document.getElementById('client-dialog'); - var dialogRect = dialog.getBoundingClientRect(); - parentNode.style.top = (dialogRect.bottom + 16) + 'px'; - parentNode.style.left = (window.innerWidth - width) / 2 + 'px'; - } -}; diff --git a/remoting/webapp/client_session.js b/remoting/webapp/client_session.js index a6bc3bd..2d3e88d 100644 --- a/remoting/webapp/client_session.js +++ b/remoting/webapp/client_session.js @@ -322,7 +322,7 @@ remoting.ClientSession.prototype.createClientPlugin_ = function(container, id) { plugin.tabIndex = 0; // Required, otherwise focus() doesn't work. container.appendChild(plugin); - return new remoting.ClientPluginAsync(plugin); + return new remoting.ClientPlugin(plugin); }; /** diff --git a/remoting/webapp/main.html b/remoting/webapp/main.html index a42c353..066d906 100644 --- a/remoting/webapp/main.html +++ b/remoting/webapp/main.html @@ -17,7 +17,6 @@ found in the LICENSE file. <link rel="stylesheet" href="toolbar.css"> <script src="butter_bar.js"></script> <script src="client_plugin.js"></script> - <script src="client_plugin_async.js"></script> <script src="client_screen.js"></script> <script src="client_session.js"></script> <script src="clipboard.js"></script> |