diff options
author | sergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-02-11 00:32:55 +0000 |
---|---|---|
committer | sergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-02-11 00:32:55 +0000 |
commit | 4a3bf111a6fa6828e179698e3bdeb788c8bc4cd4 (patch) | |
tree | 76e0db518e3c04ad70d23c01a9aa736250782e9c /remoting | |
parent | a96e1c4acce2799c5739f674ff6d90d49fd25633 (diff) | |
download | chromium_src-4a3bf111a6fa6828e179698e3bdeb788c8bc4cd4.zip chromium_src-4a3bf111a6fa6828e179698e3bdeb788c8bc4cd4.tar.gz chromium_src-4a3bf111a6fa6828e179698e3bdeb788c8bc4cd4.tar.bz2 |
Add remoting.ClientPlugin class that wraps the client plugin.
The new class will hide differences between various client plugin version
from the remoting.ClientScreen class.
BUG=86353
Review URL: http://codereview.chromium.org/9369056
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@121585 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
-rw-r--r-- | remoting/remoting.gyp | 1 | ||||
-rw-r--r-- | remoting/webapp/choice.html | 1 | ||||
-rw-r--r-- | remoting/webapp/client_plugin.js | 239 | ||||
-rw-r--r-- | remoting/webapp/client_screen.js | 2 | ||||
-rw-r--r-- | remoting/webapp/client_session.js | 200 | ||||
-rw-r--r-- | remoting/webapp/viewer_plugin_proto.js | 10 |
6 files changed, 317 insertions, 136 deletions
diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp index ab5b31b..8e65062 100644 --- a/remoting/remoting.gyp +++ b/remoting/remoting.gyp @@ -108,6 +108,7 @@ 'resources/icon_warning.png', 'webapp/choice.css', 'webapp/choice.html', + 'webapp/client_plugin.js', 'webapp/client_screen.js', 'webapp/client_session.js', 'webapp/cs_oauth2_trampoline.js', diff --git a/remoting/webapp/choice.html b/remoting/webapp/choice.html index cfe18b3..1e897e9 100644 --- a/remoting/webapp/choice.html +++ b/remoting/webapp/choice.html @@ -15,6 +15,7 @@ found in the LICENSE file. <link rel="stylesheet" href="main.css" /> <link rel="stylesheet" href="choice.css" /> <link rel="stylesheet" href="toolbar.css" /> + <script src="client_plugin.js"></script> <script src="client_screen.js"></script> <script src="client_session.js"></script> <script src="debug_log.js"></script> diff --git a/remoting/webapp/client_plugin.js b/remoting/webapp/client_plugin.js new file mode 100644 index 0000000..9430115 --- /dev/null +++ b/remoting/webapp/client_plugin.js @@ -0,0 +1,239 @@ +// 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 {Element} container The element to add the plugin to. + * @param {string} id Id to use for the plugin element . + * @constructor + */ +remoting.ClientPlugin = function(container, id) { + this.plugin = /** @type {remoting.ViewerPlugin} */ + document.createElement('embed'); + + this.plugin.id = id; + this.plugin.src = 'about://none'; + this.plugin.type = 'application/vnd.chromium.remoting-viewer'; + this.plugin.width = 0; + this.plugin.height = 0; + this.plugin.tabIndex = 0; // Required, otherwise focus() doesn't work. + container.appendChild(this.plugin); + + this.desktopWidth = 0; + this.desktopHeight = 0; + + /** @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} status The plugin status. + * @param {number} error The plugin error status, if any. + */ + this.onConnectionStatusUpdateHandler = function(status, error) {}; + this.onDesktopSizeUpdateHandler = function () {}; + + // Connect Event Handlers + + /** @type {remoting.ClientPlugin} */ + var that = this; + + /** @param {string} iq The IQ stanza to send. */ + this.plugin.sendIq = function(iq) { that.onSendIq_(iq); }; + + /** @param {string} msg The message to log. */ + this.plugin.debugInfo = function(msg) { that.onDebugInfo_(msg); }; + + /** + * @param {number} status The plugin status. + * @param {number} error The plugin error status, if any. + */ + this.plugin.connectionInfoUpdate = function(status, error) { + that.onConnectionInfoUpdate_(status, error); + }; + this.plugin.desktopSizeUpdate = function() { that.onDesktopSizeUpdate_(); }; +}; + +/** @param {string} iq The Iq stanza received from the host. */ +remoting.ClientPlugin.prototype.onSendIq_ = function(iq) { + this.onOutgoingIqHandler(iq); +} + + /** @param {string} message The IQ stanza to send. */ +remoting.ClientPlugin.prototype.onDebugInfo_ = function(message) { + this.onDebugMessageHandler(message); +} + +/** + * @param {number} status The plugin status. + * @param {number} error The plugin error status, if any. + */ +remoting.ClientPlugin.prototype.onConnectionInfoUpdate_= + function(status, error) { + // Old plugins didn't pass the status and error values, so get + // them directly. Note that there is a race condition inherent in + // this approach. + if (typeof(status) == 'undefined') + status = this.plugin.status; + if (typeof(error) == 'undefined') + error = this.plugin.error; + this.onConnectionStatusUpdateHandler(status, error); +}; + +remoting.ClientPlugin.prototype.onDesktopSizeUpdate_ = function() { + this.desktopWidth = this.plugin.desktopWidth; + this.desktopHeight = this.plugin.desktopHeight; + this.onDesktopSizeUpdateHandler(); +} + +/** + * 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.API_VERSION_ = 4; + +/** + * 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.API_MIN_VERSION_ = 2; + + +/** + * Deletes the plugin. + */ +remoting.ClientPlugin.prototype.cleanup = function() { + this.plugin.parentNode.removeChild(this.plugin); +}; + +/** + * @return {HTMLElement} HTML element that correspods to the plugin. + */ +remoting.ClientPlugin.prototype.element = function() { + return this.plugin; +}; + +/** + * @return {boolean} True if the plugin was loaded succesfully. + */ +remoting.ClientPlugin.prototype.isLoaded = function() { + return typeof this.plugin.connect === 'function'; +} + +/** + * @return {boolean} True if the plugin and web-app versions are compatible. + */ +remoting.ClientPlugin.prototype.isSupportedVersion = function() { + return this.API_VERSION_ >= this.plugin.apiMinVersion && + this.plugin.apiVersion >= this.API_MIN_VERSION_; +}; + +/** + * @return {boolean} True if the plugin supports high-quality scaling. + */ +remoting.ClientPlugin.prototype.isHiQualityScalingSupported = function() { + return this.plugin.apiVersion >= 3; +}; + +/** + * @param {string} iq Incoming IQ stanza. + */ +remoting.ClientPlugin.prototype.onIncomingIq = function(iq) { + if (this.plugin && this.plugin.onIq) { + this.plugin.onIq(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. + remoting.debug.log( + '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} clientJid 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. + */ +remoting.ClientPlugin.prototype.connect = function( + hostJid, hostPublicKey, clientJid, sharedSecret, + authenticationMethods, authenticationTag) { + if (this.plugin.apiVersion < 4) { + // Client plugin versions prior to 4 didn't support the last two + // parameters. + this.plugin.connect(hostJid, hostPublicKey, clientJid, sharedSecret); + } else { + this.plugin.connect(hostJid, hostPublicKey, clientJid, sharedSecret, + authenticationMethods, authenticationTag); + } +} + +/** + * @param {boolean} scaleToFit True if scale-to-fit should be enabled. + */ +remoting.ClientPlugin.prototype.setScaleToFit = function(scaleToFit) { + // scaleToFit() will be removed in future versions of the plugin. + if (!!this.plugin && typeof this.plugin.setScaleToFit === 'function') + this.plugin.setScaleToFit(scaleToFit); +}; + + +/** + * + */ +remoting.ClientPlugin.prototype.releaseAllKeys = function() { + this.plugin.releaseAllKeys(); +}; + +/** + * Returns an associative array with a set of stats for this connection. + * + * @return {Object.<string, number>} The connection statistics. + */ +remoting.ClientPlugin.prototype.getPerfStats = function() { + var dict = {}; + dict[remoting.ClientSession.STATS_KEY_VIDEO_BANDWIDTH] = + this.plugin.videoBandwidth; + dict[remoting.ClientSession.STATS_KEY_VIDEO_FRAME_RATE] = + this.plugin.videoFrameRate; + dict[remoting.ClientSession.STATS_KEY_CAPTURE_LATENCY] = + this.plugin.videoCaptureLatency; + dict[remoting.ClientSession.STATS_KEY_ENCODE_LATENCY] = + this.plugin.videoEncodeLatency; + dict[remoting.ClientSession.STATS_KEY_DECODE_LATENCY] = + this.plugin.videoDecodeLatency; + dict[remoting.ClientSession.STATS_KEY_RENDER_LATENCY] = + this.plugin.videoRenderLatency; + dict[remoting.ClientSession.STATS_KEY_ROUNDTRIP_LATENCY] = + this.plugin.roundTripLatency; + return dict; +}; diff --git a/remoting/webapp/client_screen.js b/remoting/webapp/client_screen.js index 7479ace..45edbcb 100644 --- a/remoting/webapp/client_screen.js +++ b/remoting/webapp/client_screen.js @@ -397,7 +397,7 @@ function updateStatistics_() { remoting.clientSession.state != remoting.ClientSession.State.CONNECTED) { return; } - var stats = remoting.clientSession.stats(); + var stats = remoting.clientSession.getPerfStats(); remoting.debug.updateStatistics(stats); remoting.clientSession.logStatistics(stats); // Update the stats once per second. diff --git a/remoting/webapp/client_session.js b/remoting/webapp/client_session.js index 9977d58..f6d2e2e 100644 --- a/remoting/webapp/client_session.js +++ b/remoting/webapp/client_session.js @@ -6,9 +6,15 @@ * @fileoverview * Class handling creation and teardown of a remoting client session. * - * This abstracts a <embed> element and controls the plugin which does the - * actual remoting work. There should be no UI code inside this class. It - * should be purely thought of as a controller of sorts. + * The ClientSession class controls lifetime of the client plugin + * object and provides the plugin with the functionality it needs to + * establish connection. Specifically it: + * - Delivers incoming/otgoing signaling messages, + * - Adjusts plugin size and position when destop resolution changes, + * + * This class should not access the plugin directly, instead it should + * do it through ClientPlugin class which abstracts plugin version + * differences. */ 'use strict'; @@ -47,7 +53,7 @@ remoting.ClientSession = function(hostJid, hostPublicKey, sharedSecret, this.mode = mode; this.clientJid = ''; this.sessionId = ''; - /** @type {remoting.ViewerPlugin} */ + /** @type {remoting.ClientPlugin} */ this.plugin = null; this.scaleToFit = false; this.logToServer = new remoting.LogToServer(); @@ -55,7 +61,7 @@ remoting.ClientSession = function(hostJid, hostPublicKey, sharedSecret, /** @type {remoting.ClientSession} */ var that = this; /** @type {function():void} @private */ - this.refocusPlugin_ = function() { that.plugin.focus(); }; + this.refocusPlugin_ = function() { that.plugin.element().focus(); }; }; // Note that the positive values in both of these enums are copied directly @@ -114,26 +120,6 @@ remoting.ClientSession.prototype.error = remoting.ClientSession.ConnectionError.NONE; /** - * Chromoting session API version (for this javascript). - * This is compared with the plugin API version to verify that they are - * compatible. - * - * @const - * @private - */ -remoting.ClientSession.prototype.API_VERSION_ = 4; - -/** - * 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.ClientSession.prototype.API_MIN_VERSION_ = 2; - -/** * The id of the client plugin * * @const @@ -158,56 +144,49 @@ remoting.ClientSession.prototype.onStateChange = */ remoting.ClientSession.prototype.createPluginAndConnect = function(container, oauth2AccessToken) { - this.plugin = /** @type {remoting.ViewerPlugin} */ - document.createElement('embed'); - this.plugin.id = this.PLUGIN_ID; - this.plugin.src = 'about://none'; - this.plugin.type = 'application/vnd.chromium.remoting-viewer'; - this.plugin.width = 0; - this.plugin.height = 0; - this.plugin.tabIndex = 0; // Required, otherwise focus() doesn't work. - container.appendChild(this.plugin); - - this.plugin.focus(); - this.plugin.addEventListener('blur', this.refocusPlugin_, false); - - if (!this.isPluginVersionSupported_(this.plugin)) { - // TODO(ajwong): Remove from parent. + this.plugin = new remoting.ClientPlugin(container, this.PLUGIN_ID); + + this.plugin.element().focus(); + this.plugin.element().addEventListener('blur', this.refocusPlugin_, false); + + if (!this.plugin.isLoaded()) { + remoting.debug.log('ERROR: remoting plugin not loaded'); + this.plugin.cleanup(); + delete this.plugin; + this.setState_(remoting.ClientSession.State.UNKNOWN_PLUGIN_ERROR); + return; + } + + if (!this.plugin.isSupportedVersion()) { + this.plugin.cleanup(); delete this.plugin; this.setState_(remoting.ClientSession.State.BAD_PLUGIN_VERSION); return; } // Enable scale-to-fit if the plugin is new enough for high-quality scaling. - this.setScaleToFit(this.plugin.apiVersion >= 3); + this.setScaleToFit(this.plugin.isHiQualityScalingSupported()); /** @type {remoting.ClientSession} */ var that = this; /** @param {string} msg The IQ stanza to send. */ - this.plugin.sendIq = function(msg) { that.sendIq_(msg); }; + this.plugin.onOutgoingIqHandler = function(msg) { that.sendIq_(msg); }; /** @param {string} msg The message to log. */ - this.plugin.debugInfo = function(msg) { + this.plugin.onDebugMessageHandler = function(msg) { remoting.debug.log('plugin: ' + msg); }; - // TODO(ajwong): Is it even worth having this class handle these events? - // Or would it be better to just allow users to pass in their own handlers - // and leave these blank by default? /** * @param {number} status The plugin status. * @param {number} error The plugin error status, if any. */ - this.plugin.connectionInfoUpdate = function(status, error) { - that.connectionInfoUpdateCallback(status, error); + this.plugin.onConnectionStatusUpdateHandler = function(status, error) { + that.connectionStatusUpdateCallback(status, error); + }; + this.plugin.onDesktopSizeUpdateHandler = function() { + that.onDesktopSizeChanged_(); }; - this.plugin.desktopSizeUpdate = function() { that.onDesktopSizeChanged_(); }; - // TODO(garykac): Clean exit if |connect| isn't a function. - if (typeof this.plugin.connect === 'function') { - this.connectPluginToWcs_(oauth2AccessToken); - } else { - remoting.debug.log('ERROR: remoting plugin not loaded'); - this.setState_(remoting.ClientSession.State.UNKNOWN_PLUGIN_ERROR); - } + this.connectPluginToWcs_(oauth2AccessToken); }; /** @@ -219,9 +198,9 @@ remoting.ClientSession.prototype.createPluginAndConnect = */ remoting.ClientSession.prototype.removePlugin = function() { if (this.plugin) { - this.plugin.removeEventListener('blur', this.refocusPlugin_, false); - var parentNode = this.plugin.parentNode; - parentNode.removeChild(this.plugin); + this.plugin.element().removeEventListener( + 'blur', this.refocusPlugin_, false); + this.plugin.cleanup(); this.plugin = null; } }; @@ -307,16 +286,6 @@ remoting.ClientSession.prototype.sendIq_ = function(msg) { }; /** - * @private - * @param {remoting.ViewerPlugin} plugin The embed element for the plugin. - * @return {boolean} True if the plugin and web-app versions are compatible. - */ -remoting.ClientSession.prototype.isPluginVersionSupported_ = function(plugin) { - return this.API_VERSION_ >= plugin.apiMinVersion && - plugin.apiVersion >= this.API_MIN_VERSION_; -}; - -/** * Connects the plugin to WCS. * * @private @@ -333,29 +302,14 @@ remoting.ClientSession.prototype.connectPluginToWcs_ = /** @type {remoting.ClientSession} */ var that = this; /** @param {string} stanza The IQ stanza received. */ - var onIq = function(stanza) { + var onIncomingIq = function(stanza) { remoting.debug.logIq(false, stanza); - if (that.plugin.onIq) { - that.plugin.onIq(stanza); - } 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. - remoting.debug.log( - 'plugin.onIq is not set so dropping incoming message.'); - } - } - remoting.wcs.setOnIq(onIq); - if (that.plugin.apiVersion < 4) { - // Client plugin versions prior to 4 didn't support the last two - // parameters. - that.plugin.connect(this.hostJid, this.hostPublicKey, this.clientJid, - this.sharedSecret); - } else { - that.plugin.connect(this.hostJid, this.hostPublicKey, this.clientJid, - this.sharedSecret, this.authenticationMethods, - this.authenticationTag); + that.plugin.onIncomingIq(stanza); } + remoting.wcs.setOnIq(onIncomingIq); + that.plugin.connect(this.hostJid, this.hostPublicKey, this.clientJid, + this.sharedSecret, this.authenticationMethods, + this.authenticationTag); }; /** @@ -365,20 +319,11 @@ remoting.ClientSession.prototype.connectPluginToWcs_ = * @param {number} status The plugin's status. * @param {number} error The plugin's error state, if any. */ -remoting.ClientSession.prototype.connectionInfoUpdateCallback = +remoting.ClientSession.prototype.connectionStatusUpdateCallback = function(status, error) { - // Old plugins didn't pass the status and error values, so get them directly. - // Note that there is a race condition inherent in this approach. - if (typeof(status) == 'undefined') { - status = this.plugin.status; - } - if (typeof(error) == 'undefined') { - error = this.plugin.error; - } - - if (status == this.plugin.STATUS_CONNECTED) { + if (status == remoting.ClientSession.State.CONNECTED) { this.onDesktopSizeChanged_(); - } else if (status == this.plugin.STATUS_FAILED) { + } else if (status == remoting.ClientSession.State.CONNECTION_FAILED) { this.error = /** @type {remoting.ClientSession.ConnectionError} */ (error); } this.setState_(/** @type {remoting.ClientSession.State} */ (status)); @@ -430,39 +375,47 @@ remoting.ClientSession.prototype.onDesktopSizeChanged_ = function() { */ remoting.ClientSession.prototype.updateDimensions = function() { if (this.plugin.desktopWidth == 0 || - this.plugin.desktopHeight == 0) + this.plugin.desktopHeight == 0) { return; + } var windowWidth = window.innerWidth; var windowHeight = window.innerHeight; var scale = 1.0; if (this.getScaleToFit()) { - var scaleFitHeight = 1.0 * windowHeight / this.plugin.desktopHeight; var scaleFitWidth = 1.0 * windowWidth / this.plugin.desktopWidth; + var scaleFitHeight = 1.0 * windowHeight / this.plugin.desktopHeight; scale = Math.min(1.0, scaleFitHeight, scaleFitWidth); } + var width = this.plugin.desktopWidth * scale; + var height = this.plugin.desktopHeight * scale; + // Resize the plugin if necessary. - this.plugin.width = this.plugin.desktopWidth * scale; - this.plugin.height = this.plugin.desktopHeight * scale; + this.plugin.element().width = width; + this.plugin.element().height = height; // Position the container. // TODO(wez): We should take into account scrollbars when positioning. - var parentNode = this.plugin.parentNode; - if (this.plugin.width < windowWidth) - parentNode.style.left = (windowWidth - this.plugin.width) / 2 + 'px'; - else + var parentNode = this.plugin.element().parentNode; + + if (width < windowWidth) { + parentNode.style.left = (windowWidth - width) / 2 + 'px'; + } else { parentNode.style.left = '0'; - if (this.plugin.height < windowHeight) - parentNode.style.top = (windowHeight - this.plugin.height) / 2 + 'px'; - else + } + + if (height < windowHeight) { + parentNode.style.top = (windowHeight - height) / 2 + 'px'; + } else { parentNode.style.top = '0'; + } remoting.debug.log('plugin dimensions: ' + parentNode.style.left + ',' + parentNode.style.top + '-' + - this.plugin.width + 'x' + this.plugin.height + '.'); + width + 'x' + height + '.'); this.plugin.setScaleToFit(this.getScaleToFit()); }; @@ -471,23 +424,8 @@ remoting.ClientSession.prototype.updateDimensions = function() { * * @return {Object.<string, number>} The connection statistics. */ -remoting.ClientSession.prototype.stats = function() { - var dict = {}; - dict[remoting.ClientSession.STATS_KEY_VIDEO_BANDWIDTH] = - this.plugin.videoBandwidth; - dict[remoting.ClientSession.STATS_KEY_VIDEO_FRAME_RATE] = - this.plugin.videoFrameRate; - dict[remoting.ClientSession.STATS_KEY_CAPTURE_LATENCY] = - this.plugin.videoCaptureLatency; - dict[remoting.ClientSession.STATS_KEY_ENCODE_LATENCY] = - this.plugin.videoEncodeLatency; - dict[remoting.ClientSession.STATS_KEY_DECODE_LATENCY] = - this.plugin.videoDecodeLatency; - dict[remoting.ClientSession.STATS_KEY_RENDER_LATENCY] = - this.plugin.videoRenderLatency; - dict[remoting.ClientSession.STATS_KEY_ROUNDTRIP_LATENCY] = - this.plugin.roundTripLatency; - return dict; +remoting.ClientSession.prototype.getPerfStats = function() { + return this.plugin.getPerfStats(); }; /** diff --git a/remoting/webapp/viewer_plugin_proto.js b/remoting/webapp/viewer_plugin_proto.js index 7ed0b57..df725a5 100644 --- a/remoting/webapp/viewer_plugin_proto.js +++ b/remoting/webapp/viewer_plugin_proto.js @@ -11,14 +11,11 @@ var remoting = remoting || {}; /** @constructor * @extends HTMLElement */ -remoting.ViewerPlugin = function() {}; +remoting.ViewerPlugin = function() { }; /** @param {string} iq The Iq stanza received from the host. */ remoting.ViewerPlugin.prototype.onIq = function(iq) {}; -/** @param {boolean} scale True to enable scaling, false to disable. */ -remoting.ViewerPlugin.prototype.setScaleToFit = function(scale) {}; - /** Release all keys currently pressed by this client. */ remoting.ViewerPlugin.prototype.releaseAllKeys = function() {}; @@ -38,6 +35,11 @@ remoting.ViewerPlugin.prototype.connect = function(hostJid, hostPublicKey, clientJid, sharedSecret, authenticationMethod, authenticationTag) {}; +/** + * @param {boolean} scaleToFit New scaleToFit value. + */ +remoting.ViewerPlugin.prototype.setScaleToFit = function(scaleToFit) {}; + /** @type {function(number, number): void} State change callback function. */ remoting.ViewerPlugin.prototype.connectionInfoUpdate; |