Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / remoting / webapp / client_plugin.js
index cc621f2..824098b 100644 (file)
@@ -18,10 +18,14 @@ 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) {
+remoting.ClientPlugin = function(plugin, onExtensionMessage) {
   this.plugin = plugin;
+  this.onExtensionMessage_ = onExtensionMessage;
 
   this.desktopWidth = 0;
   this.desktopHeight = 0;
@@ -39,6 +43,7 @@ remoting.ClientPlugin = function(plugin) {
   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.
@@ -51,6 +56,9 @@ remoting.ClientPlugin = function(plugin) {
   this.onSetCapabilitiesHandler = function (capabilities) {};
   this.fetchPinHandler = function (supportsPairing) {};
 
+  /** @type {remoting.MediaSourceRenderer} */
+  this.mediaSourceRenderer_ = null;
+
   /** @type {number} */
   this.pluginApiVersion_ = -1;
   /** @type {Array.<string>} */
@@ -93,7 +101,8 @@ remoting.ClientPlugin.Feature = {
   THIRD_PARTY_AUTH: 'thirdPartyAuth',
   TRAP_KEY: 'trapKey',
   PINLESS_AUTH: 'pinlessAuth',
-  EXTENSION_MESSAGE: 'extensionMessage'
+  EXTENSION_MESSAGE: 'extensionMessage',
+  MEDIA_SOURCE_RENDERING: 'mediaSourceRendering'
 };
 
 /**
@@ -117,17 +126,34 @@ remoting.ClientPlugin.prototype.API_VERSION_ = 6;
 remoting.ClientPlugin.prototype.API_MIN_VERSION_ = 5;
 
 /**
- * @param {string} messageStr Message from the plugin.
+ * @param {string|{method:string, data:Object.<string,*>}}
+ *    rawMessage Message from the plugin.
+ * @private
  */
-remoting.ClientPlugin.prototype.handleMessage_ = function(messageStr) {
-  var message = /** @type {{method:string, data:Object.<string,string>}} */
-      jsonParseSafe(messageStr);
+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: ' + messageStr);
+    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.
@@ -143,41 +169,27 @@ remoting.ClientPlugin.prototype.handleMessage_ = function(messageStr) {
     // 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'];
+    this.pluginApiVersion_ = getNumberAttr(message.data, 'apiVersion');
+    this.pluginApiMinVersion_ = getNumberAttr(message.data, 'apiMinVersion');
 
     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']);
+          tokenize(getStringAttr(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']);
+        requestedCapabilities =
+            tokenize(getStringAttr(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']);
+        supportedCapabilities =
+            tokenize(getStringAttr(message.data, 'supportedCapabilities'));
       }
 
       // At the moment the webapp does not recognize any of
@@ -200,151 +212,117 @@ remoting.ClientPlugin.prototype.handleMessage_ = function(messageStr) {
     } 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;
-    }
+    this.onOutgoingIqHandler(getStringAttr(message.data, 'iq'));
 
-    /** @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;
-    }
+  } 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') {
-    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.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') {
-    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;
-    }
+    // 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') {
-    if (typeof message.data['mimeType'] != 'string' ||
-        typeof message.data['item'] != 'string') {
-      console.error('Received incorrect injectClipboardItem message.');
-      return;
-    }
+    var mimetype = getStringAttr(message.data, 'mimeType');
+    var item = getStringAttr(message.data, 'item');
     if (remoting.clipboard) {
-      remoting.clipboard.fromHost(message.data['mimeType'],
-                                  message.data['item']);
+      remoting.clipboard.fromHost(mimetype, 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'];
+    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.
-    /** @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;
-      }
-    }
+    var pairingSupported = getBooleanAttr(message.data, 'pairingSupported',
+                                          false)
     this.fetchPinHandler(pairingSupported);
-  } else if (message.method == 'setCapabilities') {
-    if (typeof message.data['capabilities'] != 'string') {
-      console.error('Received incorrect setCapabilities message.');
-      return;
-    }
 
+  } else if (message.method == 'setCapabilities') {
     /** @type {!Array.<string>} */
-    var capabilities = tokenize(message.data['capabilities']);
+    var capabilities = tokenize(getStringAttr(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'];
+    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 = /** @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;
-    }
+    var clientId = getStringAttr(message.data, 'clientId');
+    var sharedSecret = getStringAttr(message.data, 'sharedSecret');
     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']) {
+    var extMsgType = getStringAttr(message, 'type');
+    var extMsgData = getStringAttr(message, 'data');
+    switch (extMsgType) {
       case 'test-echo-reply':
-        console.log('Got echo reply: ' + message.data['data']);
+        console.log('Got echo reply: ' + extMsgData);
         break;
       default:
-        console.log('Unexpected message received: ' +
-                    message.data['type'] + ': ' + message.data['data']);
+        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']));
   }
 };
 
@@ -558,8 +536,9 @@ remoting.ClientPlugin.prototype.notifyClientResolution =
  */
 remoting.ClientPlugin.prototype.pauseVideo =
     function(pause) {
-  if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_VIDEO))
+  if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_VIDEO)) {
     return;
+  }
   this.plugin.postMessage(JSON.stringify(
       { method: 'pauseVideo', data: { pause: pause }}));
 };
@@ -571,8 +550,9 @@ remoting.ClientPlugin.prototype.pauseVideo =
  */
 remoting.ClientPlugin.prototype.pauseAudio =
     function(pause) {
-  if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_AUDIO))
+  if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_AUDIO)) {
     return;
+  }
   this.plugin.postMessage(JSON.stringify(
       { method: 'pauseAudio', data: { pause: pause }}));
 };
@@ -651,6 +631,21 @@ remoting.ClientPlugin.prototype.sendClientMessage =
 };
 
 /**
+ * Request MediaStream-based rendering.
+ *
+ * @param {remoting.MediaSourceRenderer} mediaSourceRenderer
+ */
+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: {} }));
+};
+
+/**
  * 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.