Index: remoting/webapp/client_plugin.js |
diff --git a/remoting/webapp/client_plugin.js b/remoting/webapp/client_plugin.js |
index 8cd8f1cc33e9194a101559031f4e688fd9a56504..cc621f2cf35ba5206e4ec76a790f718e36560394 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'; |
+ } |
+}; |