Upstream version 11.39.266.0
[platform/framework/web/crosswalk.git] / src / remoting / webapp / client_plugin.js
index 65db925..106fc78 100644 (file)
@@ -1,14 +1,10 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2014 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.
+ * Interface abstracting the ClientPlugin functionality.
  */
 
 'use strict';
 var remoting = remoting || {};
 
 /**
- * @param {remoting.ViewerPlugin} plugin The plugin embed element.
- * @param {function(string, string):boolean} onExtensionMessage The handler for
- *     protocol extension messages. Returns true if a message is recognized;
- *     false otherwise.
- * @constructor
- */
-remoting.ClientPlugin = function(plugin, onExtensionMessage) {
-  this.plugin = plugin;
-  this.onExtensionMessage_ = onExtensionMessage;
-
-  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) {};
-  /** @param {string} data Remote gnubbyd data. */
-  this.onGnubbyAuthHandler = function(data) {};
-
-  /** @type {remoting.MediaSourceRenderer} */
-  this.mediaSourceRenderer_ = null;
-
-  /** @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.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.
- *
- * @enum {string}
+ * @interface
+ * @extends {base.Disposable}
  */
-remoting.ClientPlugin.Feature = {
-  INJECT_KEY_EVENT: 'injectKeyEvent',
-  NOTIFY_CLIENT_RESOLUTION: 'notifyClientResolution',
-  ASYNC_PIN: 'asyncPin',
-  PAUSE_VIDEO: 'pauseVideo',
-  PAUSE_AUDIO: 'pauseAudio',
-  REMAP_KEY: 'remapKey',
-  SEND_CLIPBOARD_ITEM: 'sendClipboardItem',
-  THIRD_PARTY_AUTH: 'thirdPartyAuth',
-  TRAP_KEY: 'trapKey',
-  PINLESS_AUTH: 'pinlessAuth',
-  EXTENSION_MESSAGE: 'extensionMessage',
-  MEDIA_SOURCE_RENDERING: 'mediaSourceRendering'
-};
-
-/**
- * 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_ = 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.ClientPlugin.prototype.API_MIN_VERSION_ = 5;
-
-/**
- * @param {string|{method:string, data:Object.<string,*>}}
- *    rawMessage Message from the plugin.
- * @private
- */
-remoting.ClientPlugin.prototype.handleMessage_ = function(rawMessage) {
-  var message =
-      /** @type {{method:string, data:Object.<string,*>}} */
-      ((typeof(rawMessage) == 'string') ? jsonParseSafe(rawMessage)
-                                        : rawMessage);
-
-  if (!message || !('method' in message) || !('data' in message)) {
-    console.error('Received invalid message from the plugin:', rawMessage);
-    return;
-  }
-
-  try {
-    this.handleMessageMethod_(message);
-  } catch(e) {
-    console.error(/** @type {*} */ (e));
-  }
-}
-
-/**
- * @param {{method:string, data:Object.<string,*>}}
- *    message Parsed message from the plugin.
- * @private
- */
-remoting.ClientPlugin.prototype.handleMessageMethod_ = function(message) {
-  /**
-   * 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;
-    this.pluginApiVersion_ = getNumberAttr(message.data, 'apiVersion');
-    this.pluginApiMinVersion_ = getNumberAttr(message.data, 'apiMinVersion');
-
-    if (this.pluginApiVersion_ >= 7) {
-      this.pluginApiFeatures_ =
-          tokenize(getStringAttr(message.data, 'apiFeatures'));
-
-      // Negotiate capabilities.
-
-      /** @type {!Array.<string>} */
-      var requestedCapabilities = [];
-      if ('requestedCapabilities' in message.data) {
-        requestedCapabilities =
-            tokenize(getStringAttr(message.data, 'requestedCapabilities'));
-      }
-
-      /** @type {!Array.<string>} */
-      var supportedCapabilities = [];
-      if ('supportedCapabilities' in message.data) {
-        supportedCapabilities =
-            tokenize(getStringAttr(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.helloReceived_ = true;
-    if (this.onInitializedCallback_ != null) {
-      this.onInitializedCallback_(true);
-      this.onInitializedCallback_ = null;
-    }
-
-  } else if (message.method == 'sendOutgoingIq') {
-    this.onOutgoingIqHandler(getStringAttr(message.data, 'iq'));
-
-  } else if (message.method == 'logDebugMessage') {
-    this.onDebugMessageHandler(getStringAttr(message.data, 'message'));
-
-  } else if (message.method == 'onConnectionStatus') {
-    var state = remoting.ClientSession.State.fromString(
-        getStringAttr(message.data, 'state'))
-    var error = remoting.ClientSession.ConnectionError.fromString(
-        getStringAttr(message.data, 'error'));
-    this.onConnectionStatusUpdateHandler(state, error);
-
-  } else if (message.method == 'onDesktopSize') {
-    this.desktopWidth = getNumberAttr(message.data, 'width');
-    this.desktopHeight = getNumberAttr(message.data, 'height');
-    this.desktopXDpi = getNumberAttr(message.data, 'x_dpi', 96);
-    this.desktopYDpi = getNumberAttr(message.data, 'y_dpi', 96);
-    this.onDesktopSizeUpdateHandler();
-
-  } else if (message.method == 'onPerfStats') {
-    // Return value is ignored. These calls will throw an error if the value
-    // is not a number.
-    getNumberAttr(message.data, 'videoBandwidth');
-    getNumberAttr(message.data, 'videoFrameRate');
-    getNumberAttr(message.data, 'captureLatency');
-    getNumberAttr(message.data, 'encodeLatency');
-    getNumberAttr(message.data, 'decodeLatency');
-    getNumberAttr(message.data, 'renderLatency');
-    getNumberAttr(message.data, 'roundtripLatency');
-    this.perfStats_ =
-        /** @type {remoting.ClientSession.PerfStats} */ message.data;
-
-  } else if (message.method == 'injectClipboardItem') {
-    var mimetype = getStringAttr(message.data, 'mimeType');
-    var item = getStringAttr(message.data, 'item');
-    if (remoting.clipboard) {
-      remoting.clipboard.fromHost(mimetype, item);
-    }
-
-  } else if (message.method == 'onFirstFrameReceived') {
-    if (remoting.clientSession) {
-      remoting.clientSession.onFirstFrameReceived();
-    }
-
-  } else if (message.method == 'onConnectionReady') {
-    var ready = getBooleanAttr(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.
-    var pairingSupported = getBooleanAttr(message.data, 'pairingSupported',
-                                          false)
-    this.fetchPinHandler(pairingSupported);
-
-  } else if (message.method == 'setCapabilities') {
-    /** @type {!Array.<string>} */
-    var capabilities = tokenize(getStringAttr(message.data, 'capabilities'));
-    this.onSetCapabilitiesHandler(capabilities);
-
-  } else if (message.method == 'fetchThirdPartyToken') {
-    var tokenUrl = getStringAttr(message.data, 'tokenUrl');
-    var hostPublicKey = getStringAttr(message.data, 'hostPublicKey');
-    var scope = getStringAttr(message.data, 'scope');
-    this.fetchThirdPartyTokenHandler(tokenUrl, hostPublicKey, scope);
-
-  } else if (message.method == 'pairingResponse') {
-    var clientId = getStringAttr(message.data, 'clientId');
-    var sharedSecret = getStringAttr(message.data, 'sharedSecret');
-    this.onPairingComplete_(clientId, sharedSecret);
-
-  } else if (message.method == 'extensionMessage') {
-    var extMsgType = getStringAttr(message.data, 'type');
-    var extMsgData = getStringAttr(message.data, 'data');
-    switch (extMsgType) {
-      case 'gnubby-auth':
-        this.onGnubbyAuthHandler(extMsgData);
-        break;
-      case 'test-echo-reply':
-        console.log('Got echo reply: ' + extMsgData);
-        break;
-      default:
-        if (!this.onExtensionMessage_(extMsgType, extMsgData)) {
-          console.log('Unexpected message received: ' +
-                      extMsgType + ': ' + extMsgData);
-        }
-    }
-
-  } else if (message.method == 'mediaSourceReset') {
-    if (!this.mediaSourceRenderer_) {
-      console.error('Unexpected mediaSourceReset.');
-      return;
-    }
-    this.mediaSourceRenderer_.reset(getStringAttr(message.data, 'format'))
-
-  } else if (message.method == 'mediaSourceData') {
-    if (!(message.data['buffer'] instanceof ArrayBuffer)) {
-      console.error('Invalid mediaSourceData message:', message.data);
-      return;
-    }
-    if (!this.mediaSourceRenderer_) {
-      console.error('Unexpected mediaSourceData.');
-      return;
-    }
-    this.mediaSourceRenderer_.onIncomingData(
-        (/** @type {ArrayBuffer} */ message.data['buffer']));
-  }
-};
+remoting.ClientPlugin = function() {};
 
 /**
- * Deletes the plugin.
+ * @return {number} The width of the remote desktop, in pixels.
  */
-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;
-};
+remoting.ClientPlugin.prototype.getDesktopWidth = function() {};
 
 /**
- * @param {function(boolean): void} onDone
+ * @return {number} The height of the remote desktop, in pixels.
  */
-remoting.ClientPlugin.prototype.initialize = function(onDone) {
-  if (this.helloReceived_) {
-    onDone(true);
-  } else {
-    this.onInitializedCallback_ = onDone;
-  }
-};
+remoting.ClientPlugin.prototype.getDesktopHeight = function() {};
 
 /**
- * @return {boolean} True if the plugin and web-app versions are compatible.
+ * @return {number} The x-DPI of the remote desktop.
  */
-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_;
-};
+remoting.ClientPlugin.prototype.getDesktopXDpi = function() {};
 
 /**
- * @param {remoting.ClientPlugin.Feature} feature The feature to test for.
- * @return {boolean} True if the plugin supports the named feature.
+ * @return {number} The y-DPI of the remote desktop.
  */
-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;
-};
+remoting.ClientPlugin.prototype.getDesktopYDpi = function() {};
 
 /**
- * @return {boolean} True if the plugin supports the injectKeyEvent API.
+ * @return {HTMLElement} The DOM element representing the remote session.
  */
-remoting.ClientPlugin.prototype.isInjectKeyEventSupported = function() {
-  return this.pluginApiVersion_ >= 6;
-};
+remoting.ClientPlugin.prototype.element = function() {};
 
 /**
- * @param {string} iq Incoming IQ stanza.
+ * @param {function():void} onDone Completion callback.
  */
-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.');
-  }
-};
+remoting.ClientPlugin.prototype.initialize = function(onDone) {};
 
 /**
  * @param {string} hostJid The jid of the host to connect to.
@@ -423,82 +67,57 @@ remoting.ClientPlugin.prototype.onIncomingIq = function(iq) {
 remoting.ClientPlugin.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
-      }
-    }));
-};
+    clientPairingId, clientPairedSecret) {};
 
 /**
- * Release all currently pressed keys.
+ * @param {number} key The keycode to inject.
+ * @param {boolean} down True for press; false for a release.
  */
-remoting.ClientPlugin.prototype.releaseAllKeys = function() {
-  this.plugin.postMessage(JSON.stringify(
-      { method: 'releaseAllKeys', data: {} }));
-};
+remoting.ClientPlugin.prototype.injectKeyEvent =
+    function(key, down) {};
 
 /**
- * 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.
+ * @param {number} from
+ * @param {number} to
  */
-remoting.ClientPlugin.prototype.injectKeyEvent =
-    function(usbKeycode, pressed) {
-  this.plugin.postMessage(JSON.stringify(
-      { method: 'injectKeyEvent', data: {
-          'usbKeycode': usbKeycode,
-          'pressed': pressed}
-      }));
-};
+remoting.ClientPlugin.prototype.remapKey = function(from, to) {};
 
 /**
- * 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.ClientPlugin.prototype.remapKey =
-    function(fromKeycode, toKeycode) {
-  this.plugin.postMessage(JSON.stringify(
-      { method: 'remapKey', data: {
-          'fromKeycode': fromKeycode,
-          'toKeycode': toKeycode}
-      }));
-};
+ * Release all keys currently being pressed.
+ */
+remoting.ClientPlugin.prototype.releaseAllKeys = function() {};
 
 /**
- * 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.ClientPlugin.prototype.trapKey = function(keycode, trap) {
-  this.plugin.postMessage(JSON.stringify(
-      { method: 'trapKey', data: {
-          'keycode': keycode,
-          'trap': trap}
-      }));
-};
+ * @param {number} width
+ * @param {number} height
+ * @param {number} dpi
+ */
+remoting.ClientPlugin.prototype.notifyClientResolution =
+    function(width, height, dpi) {};
+
+/**
+ * @param {string} iq
+ */
+remoting.ClientPlugin.prototype.onIncomingIq = function(iq) {};
+
+/**
+ * @return {boolean} True if the web-app and plugin are compatible.
+ */
+remoting.ClientPlugin.prototype.isSupportedVersion = function() {};
+
+/**
+ * @param {remoting.ClientPlugin.Feature} feature
+ * @return {boolean} True if the plugin support the specified feature.
+ */
+remoting.ClientPlugin.prototype.hasFeature = function(feature) {};
 
 /**
- * Returns an associative array with a set of stats for this connecton.
+ * Enable MediaSource rendering via the specified renderer.
  *
- * @return {remoting.ClientSession.PerfStats} The connection statistics.
+ * @param {remoting.MediaSourceRenderer} mediaSourceRenderer
  */
-remoting.ClientPlugin.prototype.getPerfStats = function() {
-  return this.perfStats_;
-};
+remoting.ClientPlugin.prototype.enableMediaSourceRendering =
+    function(mediaSourceRenderer) {};
 
 /**
  * Sends a clipboard item to the host.
@@ -507,86 +126,29 @@ remoting.ClientPlugin.prototype.getPerfStats = function() {
  * @param {string} item The clipboard 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 }}));
-};
+    function(mimeType, 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.
+ * Tell the plugin to request a PIN asynchronously.
  */
-remoting.ClientPlugin.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.ClientPlugin.prototype.pauseVideo =
-    function(pause) {
-  if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_VIDEO)) {
-    return;
-  }
-  this.plugin.postMessage(JSON.stringify(
-      { method: 'pauseVideo', data: { pause: pause }}));
-};
+remoting.ClientPlugin.prototype.useAsyncPinDialog = function() {};
 
 /**
- * Requests that the host pause or resume sending audio updates.
+ * Request that this client be paired with the current host.
  *
- * @param {boolean} pause True to suspend audio updates, false otherwise.
- */
-remoting.ClientPlugin.prototype.pauseAudio =
-    function(pause) {
-  if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_AUDIO)) {
-    return;
-  }
-  this.plugin.postMessage(JSON.stringify(
-      { method: 'pauseAudio', data: { pause: pause }}));
-};
+ * @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.ClientPlugin.prototype.requestPairing =
+    function(clientName, onDone) {};
 
 /**
  * Called when a PIN is obtained from the user.
  *
  * @param {string} pin The 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() {
-  if (!this.hasFeature(remoting.ClientPlugin.Feature.ASYNC_PIN)) {
-    return;
-  }
-  this.plugin.postMessage(JSON.stringify(
-      { method: 'useAsyncPinDialog', data: {} }));
-};
+remoting.ClientPlugin.prototype.onPinFetched = function(pin) {};
 
 /**
  * Sets the third party authentication token and shared secret.
@@ -594,79 +156,160 @@ remoting.ClientPlugin.prototype.useAsyncPinDialog =
  * @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) {
-  this.plugin.postMessage(JSON.stringify(
-    { method: 'onThirdPartyTokenFetched',
-      data: { token: token, sharedSecret: sharedSecret}}));
-};
+remoting.ClientPlugin.prototype.onThirdPartyTokenFetched =
+    function(token, 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.
+ * @param {boolean} pause True to pause the audio stream; false to resume it.
  */
-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 } }));
-};
+remoting.ClientPlugin.prototype.pauseAudio = function(pause) {};
+
+/**
+ * @param {boolean} pause True to pause the video stream; false to resume it.
+ */
+remoting.ClientPlugin.prototype.pauseVideo = function(pause) {};
+
+/**
+ * @return {remoting.ClientSession.PerfStats} A summary of the connection
+ *     performance.
+ */
+remoting.ClientPlugin.prototype.getPerfStats = function() {};
 
 /**
  * Send an extension message to the host.
  *
- * @param {string} type The message type.
- * @param {string} message The message payload.
+ * @param {string} name
+ * @param {string} data
  */
 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: message } }));
+    function(name, data) {};
 
-};
+/**
+ * @param {function(string):void} handler Callback for sending an IQ stanza.
+ */
+remoting.ClientPlugin.prototype.setOnOutgoingIqHandler =
+    function(handler) {};
+
+/**
+ * @param {function(string):void} handler Callback for logging debug messages.
+ */
+remoting.ClientPlugin.prototype.setOnDebugMessageHandler =
+    function(handler) {};
+
+/**
+ * @param {function(number, number):void} handler Callback for connection status
+ *     update notifications. The first parameter is the connection state; the
+ *     second is the error code, if any.
+ */
+remoting.ClientPlugin.prototype.setConnectionStatusUpdateHandler =
+    function(handler) {};
+
+/**
+ * @param {function(boolean):void} handler Callback for connection readiness
+ *     notifications.
+ */
+remoting.ClientPlugin.prototype.setConnectionReadyHandler =
+    function(handler) {};
+
+/**
+ * @param {function():void} handler Callback for desktop size change
+ *     notifications.
+ */
+remoting.ClientPlugin.prototype.setDesktopSizeUpdateHandler =
+    function(handler) {};
+
+/**
+ * @param {function(!Array.<string>):void} handler Callback to inform of
+ *     capabilities negotiated between host and client.
+ */
+remoting.ClientPlugin.prototype.setCapabilitiesHandler =
+    function(handler) {};
+
+/**
+ * @param {function(string):void} handler Callback for processing security key
+ *     (Gnubby) protocol messages.
+ */
+remoting.ClientPlugin.prototype.setGnubbyAuthHandler =
+    function(handler) {};
+
+/**
+ * @param {function(string):void} handler Callback for processing Cast protocol
+ *     messages.
+ */
+remoting.ClientPlugin.prototype.setCastExtensionHandler =
+    function(handler) {};
+
+/**
+ * @param {function(string, number, number):void} handler Callback for
+ *     processing large mouse cursor images. The first parameter is a data:
+ *     URL encoding the mouse cursor; the second and third parameters are
+ *     the cursor hotspot's x- and y-coordinates, respectively.
+ */
+remoting.ClientPlugin.prototype.setMouseCursorHandler =
+    function(handler) {};
+
+/**
+ * @param {function(string, string, string):void} handler Callback for
+ *     fetching third-party tokens. The first parameter is the token URL; the
+ *     second is the public key of the host; the third is the OAuth2 scope
+ *     being requested.
+ */
+remoting.ClientPlugin.prototype.setFetchThirdPartyTokenHandler =
+    function(handler) {};
 
 /**
- * Request MediaStream-based rendering.
+ * @param {function(boolean):void} handler Callback for fetching a PIN from
+ *     the user. The parameter is true if PIN pairing is supported by the
+ *     host, or false otherwise.
+ */
+remoting.ClientPlugin.prototype.setFetchPinHandler =
+    function(handler) {};
+
+
+/**
+ * Set of features for which hasFeature() can be used to test.
  *
- * @param {remoting.MediaSourceRenderer} mediaSourceRenderer
+ * @enum {string}
  */
-remoting.ClientPlugin.prototype.enableMediaSourceRendering =
-    function(mediaSourceRenderer) {
-  if (!this.hasFeature(remoting.ClientPlugin.Feature.MEDIA_SOURCE_RENDERING)) {
-    return;
-  }
-  this.mediaSourceRenderer_ = mediaSourceRenderer;
-  this.plugin.postMessage(JSON.stringify(
-      { method: 'enableMediaSourceRendering', data: {} }));
+remoting.ClientPlugin.Feature = {
+  INJECT_KEY_EVENT: 'injectKeyEvent',
+  NOTIFY_CLIENT_RESOLUTION: 'notifyClientResolution',
+  ASYNC_PIN: 'asyncPin',
+  PAUSE_VIDEO: 'pauseVideo',
+  PAUSE_AUDIO: 'pauseAudio',
+  REMAP_KEY: 'remapKey',
+  SEND_CLIPBOARD_ITEM: 'sendClipboardItem',
+  THIRD_PARTY_AUTH: 'thirdPartyAuth',
+  TRAP_KEY: 'trapKey',
+  PINLESS_AUTH: 'pinlessAuth',
+  EXTENSION_MESSAGE: 'extensionMessage',
+  MEDIA_SOURCE_RENDERING: 'mediaSourceRendering',
+  VIDEO_CONTROL: 'videoControl'
 };
 
+
 /**
- * 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';
-  }
-};
+ * @interface
+ */
+remoting.ClientPluginFactory = function() {};
+
+/**
+ * @param {Element} container The container for the embed element.
+ * @param {function(string, string):boolean} onExtensionMessage The handler for
+ *     protocol extension messages. Returns true if a message is recognized;
+ *     false otherwise.
+ * @return {remoting.ClientPlugin} A new client plugin instance.
+ */
+remoting.ClientPluginFactory.prototype.createPlugin =
+    function(container, onExtensionMessage) {};
+
+/**
+ * Preload the plugin to make instantiation faster when the user tries
+ * to connect.
+ */
+remoting.ClientPluginFactory.prototype.preloadPlugin = function() {};
+
+/**
+ * @type {remoting.ClientPluginFactory}
+ */
+remoting.ClientPlugin.factory = null;