Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / remoting / webapp / client_session.js
index 85610cd..ac27d01 100644 (file)
 var remoting = remoting || {};
 
 /**
+ * True if Cast capability is supported.
+ *
+ * @type {boolean}
+ */
+remoting.enableCast = false;
+
+/**
+ * @param {remoting.SignalStrategy} signalStrategy Signal strategy.
  * @param {HTMLElement} container Container element for the client view.
  * @param {string} hostDisplayName A human-readable name for the host.
  * @param {string} accessCode The IT2Me access code. Blank for Me2Me.
@@ -48,8 +56,8 @@ var remoting = remoting || {};
  * @constructor
  * @extends {base.EventSource}
  */
-remoting.ClientSession = function(container, hostDisplayName, accessCode,
-                                  fetchPin, fetchThirdPartyToken,
+remoting.ClientSession = function(signalStrategy, container, hostDisplayName,
+                                  accessCode, fetchPin, fetchThirdPartyToken,
                                   authenticationMethods, hostId, hostJid,
                                   hostPublicKey, mode, clientPairingId,
                                   clientPairedSecret) {
@@ -100,6 +108,14 @@ remoting.ClientSession = function(container, hostDisplayName, accessCode,
   this.hasReceivedFrame_ = false;
   this.logToServer = new remoting.LogToServer();
 
+  /** @private */
+  this.signalStrategy_ = signalStrategy;
+  base.debug.assert(this.signalStrategy_.getState() ==
+                    remoting.SignalStrategy.State.CONNECTED);
+  this.signalStrategy_.setIncomingStanzaCallback(
+      this.onIncomingMessage_.bind(this));
+  remoting.formatIq.setJids(this.signalStrategy_.getJid(), hostJid);
+
   /** @type {number?} @private */
   this.notifyClientResolutionTimer_ = null;
   /** @type {number?} @private */
@@ -126,20 +142,8 @@ remoting.ClientSession = function(container, hostDisplayName, accessCode,
   /** @private */
   this.callPluginGotFocus_ = this.pluginGotFocus_.bind(this);
   /** @private */
-  this.callToggleFullScreen_ = remoting.fullscreen.toggle.bind(
-      remoting.fullscreen);
-  /** @private */
   this.callOnFullScreenChanged_ = this.onFullScreenChanged_.bind(this)
 
-  /** @private */
-  this.screenOptionsMenu_ = new remoting.MenuButton(
-      document.getElementById('screen-options-menu'),
-      this.onShowOptionsMenu_.bind(this));
-  /** @private */
-  this.sendKeysMenu_ = new remoting.MenuButton(
-      document.getElementById('send-keys-menu')
-  );
-
   /** @type {HTMLMediaElement} @private */
   this.video_ = null;
 
@@ -155,26 +159,15 @@ remoting.ClientSession = function(container, hostDisplayName, accessCode,
     img.style.left = event.x + 'px';
   };
 
-  /** @type {HTMLElement} @private */
-  this.resizeToClientButton_ =
-      document.getElementById('screen-resize-to-client');
-  /** @type {HTMLElement} @private */
-  this.shrinkToFitButton_ = document.getElementById('screen-shrink-to-fit');
-  /** @type {HTMLElement} @private */
-  this.fullScreenButton_ = document.getElementById('toggle-full-screen');
-
   /** @type {remoting.GnubbyAuthHandler} @private */
   this.gnubbyAuthHandler_ = null;
 
-  if (this.mode_ == remoting.ClientSession.Mode.IT2ME) {
-    // Resize-to-client is not supported for IT2Me hosts.
-    this.resizeToClientButton_.hidden = true;
-  } else {
-    this.resizeToClientButton_.hidden = false;
-  }
+  /** @type {remoting.CastExtensionHandler} @private */
+  this.castExtensionHandler_ = null;
+
+  /** @type {remoting.VideoFrameRecorder} @private */
+  this.videoFrameRecorder_ = null;
 
-  this.fullScreenButton_.addEventListener(
-      'click', this.callToggleFullScreen_, false);
   this.defineEvents(Object.keys(remoting.ClientSession.Events));
 };
 
@@ -211,15 +204,15 @@ remoting.ClientSession.prototype.updateScrollbarVisibility = function() {
     // Determine whether or not horizontal or vertical scrollbars are
     // required, taking into account their width.
     var clientArea = this.getClientArea_();
-    needsVerticalScroll = clientArea.height < this.plugin_.desktopHeight;
-    needsHorizontalScroll = clientArea.width < this.plugin_.desktopWidth;
+    needsVerticalScroll = clientArea.height < this.plugin_.getDesktopHeight();
+    needsHorizontalScroll = clientArea.width < this.plugin_.getDesktopWidth();
     var kScrollBarWidth = 16;
     if (needsHorizontalScroll && !needsVerticalScroll) {
       needsVerticalScroll =
-          clientArea.height - kScrollBarWidth < this.plugin_.desktopHeight;
+          clientArea.height - kScrollBarWidth < this.plugin_.getDesktopHeight();
     } else if (!needsHorizontalScroll && needsVerticalScroll) {
       needsHorizontalScroll =
-          clientArea.width - kScrollBarWidth < this.plugin_.desktopWidth;
+          clientArea.width - kScrollBarWidth < this.plugin_.getDesktopWidth();
     }
   }
 
@@ -366,7 +359,8 @@ remoting.ClientSession.Capability = {
   // this.plugin_.notifyClientResolution().
   SEND_INITIAL_RESOLUTION: 'sendInitialResolution',
   RATE_LIMIT_RESIZE_REQUESTS: 'rateLimitResizeRequests',
-  VIDEO_RECORDER: 'videoRecorder'
+  VIDEO_RECORDER: 'videoRecorder',
+  CAST: 'casting'
 };
 
 /**
@@ -423,7 +417,7 @@ remoting.ClientSession.prototype.pluginLostFocus_ = function() {
  */
 remoting.ClientSession.prototype.createPluginAndConnect =
     function(onExtensionMessage) {
-  this.plugin_ = new remoting.ClientPlugin(
+  this.plugin_ = remoting.ClientPlugin.factory.createPlugin(
       this.container_.querySelector('.client-plugin-container'),
       onExtensionMessage);
   remoting.HostSettings.load(this.hostId_,
@@ -475,7 +469,8 @@ remoting.ClientSession.prototype.setFocusHandlers_ = function() {
  * @param {remoting.Error} error
  */
 remoting.ClientSession.prototype.resetWithError_ = function(error) {
-  this.plugin_.cleanup();
+  this.signalStrategy_.setIncomingStanzaCallback(null);
+  this.plugin_.dispose();
   this.plugin_ = null;
   this.error_ = error;
   this.setState_(remoting.ClientSession.State.FAILED);
@@ -512,7 +507,6 @@ remoting.ClientSession.prototype.onPluginInitialized_ = function(initialized) {
     this.applyRemapKeys_(true);
   }
 
-
   // Enable MediaSource-based rendering on Chrome 37 and above.
   var chromeVersionMajor =
       parseInt((remoting.getChromeVersion() || '0').split('.')[0], 10);
@@ -533,21 +527,24 @@ remoting.ClientSession.prototype.onPluginInitialized_ = function(initialized) {
     this.container_.classList.remove('mediasource-rendering');
   }
 
-  /** @param {string} msg The IQ stanza to send. */
-  this.plugin_.onOutgoingIqHandler = this.sendIq_.bind(this);
-  /** @param {string} msg The message to log. */
-  this.plugin_.onDebugMessageHandler = function(msg) {
-    console.log('plugin: ' + msg.trimRight());
-  };
+  this.plugin_.setOnOutgoingIqHandler(this.sendIq_.bind(this));
+  this.plugin_.setOnDebugMessageHandler(
+      /** @param {string} msg */
+      function(msg) {
+        console.log('plugin: ' + msg.trimRight());
+      });
 
-  this.plugin_.onConnectionStatusUpdateHandler =
-      this.onConnectionStatusUpdate_.bind(this);
-  this.plugin_.onConnectionReadyHandler = this.onConnectionReady_.bind(this);
-  this.plugin_.onDesktopSizeUpdateHandler =
-      this.onDesktopSizeChanged_.bind(this);
-  this.plugin_.onSetCapabilitiesHandler = this.onSetCapabilities_.bind(this);
-  this.plugin_.onGnubbyAuthHandler = this.processGnubbyAuthMessage_.bind(this);
-  this.plugin_.updateMouseCursorImage = this.updateMouseCursorImage_.bind(this);
+  this.plugin_.setConnectionStatusUpdateHandler(
+      this.onConnectionStatusUpdate_.bind(this));
+  this.plugin_.setConnectionReadyHandler(this.onConnectionReady_.bind(this));
+  this.plugin_.setDesktopSizeUpdateHandler(
+      this.onDesktopSizeChanged_.bind(this));
+  this.plugin_.setCapabilitiesHandler(this.onSetCapabilities_.bind(this));
+  this.plugin_.setGnubbyAuthHandler(
+      this.processGnubbyAuthMessage_.bind(this));
+  this.plugin_.setMouseCursorHandler(this.updateMouseCursorImage_.bind(this));
+  this.plugin_.setCastExtensionHandler(
+      this.processCastExtensionMessage_.bind(this));
   this.initiateConnection_();
 };
 
@@ -564,17 +561,12 @@ remoting.ClientSession.prototype.removePlugin = function() {
         'focus', this.callPluginGotFocus_, false);
     this.plugin_.element().removeEventListener(
         'blur', this.callPluginLostFocus_, false);
-    this.plugin_.cleanup();
+    this.plugin_.dispose();
     this.plugin_ = null;
   }
 
-  // Delete event handlers that aren't relevent when not connected.
-  this.fullScreenButton_.removeEventListener(
-      'click', this.callToggleFullScreen_, false);
-
   // Leave full-screen mode, and stop listening for related events.
   var listener = this.callOnFullScreenChanged_;
-  remoting.fullscreen.syncWithMaximize(false);
   remoting.fullscreen.activate(
       false,
       function() {
@@ -585,6 +577,8 @@ remoting.ClientSession.prototype.removePlugin = function() {
   } else {
     remoting.toolbar.setClientSession(null);
   }
+  remoting.optionsMenu.setClientSession(null);
+  document.body.classList.remove('connected');
 
   // Remove mediasource-rendering class from the container - this will also
   // hide the <video> element.
@@ -622,7 +616,6 @@ remoting.ClientSession.prototype.disconnect = function(error) {
  * @return {void} Nothing.
  */
 remoting.ClientSession.prototype.cleanup = function() {
-  remoting.wcsSandbox.setOnIq(null);
   this.sendIq_(
       '<cli:iq ' +
           'to="' + this.hostJid_ + '" ' +
@@ -815,16 +808,16 @@ remoting.ClientSession.prototype.hasReceivedFrame = function() {
 };
 
 /**
- * Sends an IQ stanza via the http xmpp proxy.
+ * Sends a signaling message.
  *
  * @private
- * @param {string} msg XML string of IQ stanza to send to server.
+ * @param {string} message XML string of IQ stanza to send to server.
  * @return {void} Nothing.
  */
-remoting.ClientSession.prototype.sendIq_ = function(msg) {
+remoting.ClientSession.prototype.sendIq_ = function(message) {
   // Extract the session id, so we can close the session later.
   var parser = new DOMParser();
-  var iqNode = parser.parseFromString(msg, 'text/xml').firstChild;
+  var iqNode = parser.parseFromString(message, 'text/xml').firstChild;
   var jingleNode = iqNode.firstChild;
   if (jingleNode) {
     var action = jingleNode.getAttribute('action');
@@ -833,76 +826,46 @@ remoting.ClientSession.prototype.sendIq_ = function(msg) {
     }
   }
 
-  // HACK: Add 'x' prefix to the IDs of the outgoing messages to make sure that
-  // stanza IDs used by host and client do not match. This is necessary to
-  // workaround bug in the signaling endpoint used by chromoting.
-  // TODO(sergeyu): Remove this hack once the server-side bug is fixed.
-  var type = iqNode.getAttribute('type');
-  if (type == 'set') {
-    var id = iqNode.getAttribute('id');
-    iqNode.setAttribute('id', 'x' + id);
-    msg = (new XMLSerializer()).serializeToString(iqNode);
+  console.log(remoting.timestamp(), remoting.formatIq.prettifySendIq(message));
+  if (this.signalStrategy_.getState() !=
+      remoting.SignalStrategy.State.CONNECTED) {
+    console.log("Message above is dropped because signaling is not connected.");
+    return;
   }
 
-  console.log(remoting.timestamp(), remoting.formatIq.prettifySendIq(msg));
-
-  // Send the stanza.
-  remoting.wcsSandbox.sendIq(msg);
+  this.signalStrategy_.sendMessage(message);
 };
 
-remoting.ClientSession.prototype.initiateConnection_ = function() {
-  /** @type {remoting.ClientSession} */
-  var that = this;
-
-  remoting.wcsSandbox.connect(onWcsConnected, this.resetWithError_.bind(this));
-
-  /** @param {string} localJid Local JID. */
-  function onWcsConnected(localJid) {
-    that.connectPluginToWcs_(localJid);
-    that.getSharedSecret_(onSharedSecretReceived.bind(null, localJid));
+/**
+ * @private
+ * @param {Element} message
+ */
+remoting.ClientSession.prototype.onIncomingMessage_ = function(message) {
+  if (!this.plugin_) {
+    return;
   }
-
-  /** @param {string} localJid Local JID.
-    * @param {string} sharedSecret Shared secret. */
-  function onSharedSecretReceived(localJid, sharedSecret) {
-    that.plugin_.connect(
-        that.hostJid_, that.hostPublicKey_, localJid, sharedSecret,
-        that.authenticationMethods_, that.hostId_, that.clientPairingId_,
-        that.clientPairedSecret_);
-  };
+  var formatted = new XMLSerializer().serializeToString(message);
+  console.log(remoting.timestamp(),
+              remoting.formatIq.prettifyReceiveIq(formatted));
+  this.plugin_.onIncomingIq(formatted);
 }
 
 /**
- * Connects the plugin to WCS.
- *
  * @private
- * @param {string} localJid Local JID.
- * @return {void} Nothing.
  */
-remoting.ClientSession.prototype.connectPluginToWcs_ = function(localJid) {
-  remoting.formatIq.setJids(localJid, this.hostJid_);
-  var forwardIq = this.plugin_.onIncomingIq.bind(this.plugin_);
-  /** @param {string} stanza The IQ stanza received. */
-  var onIncomingIq = function(stanza) {
-    // HACK: Remove 'x' prefix added to the id in sendIq_().
-    try {
-      var parser = new DOMParser();
-      var iqNode = parser.parseFromString(stanza, 'text/xml').firstChild;
-      var type = iqNode.getAttribute('type');
-      var id = iqNode.getAttribute('id');
-      if (type != 'set' && id.charAt(0) == 'x') {
-        iqNode.setAttribute('id', id.substr(1));
-        stanza = (new XMLSerializer()).serializeToString(iqNode);
-      }
-    } catch (err) {
-      // Pass message as is when it is malformed.
-    }
+remoting.ClientSession.prototype.initiateConnection_ = function() {
+  /** @type {remoting.ClientSession} */
+  var that = this;
 
-    console.log(remoting.timestamp(),
-                remoting.formatIq.prettifyReceiveIq(stanza));
-    forwardIq(stanza);
+  /** @param {string} sharedSecret Shared secret. */
+  function onSharedSecretReceived(sharedSecret) {
+    that.plugin_.connect(
+        that.hostJid_, that.hostPublicKey_, that.signalStrategy_.getJid(),
+        sharedSecret, that.authenticationMethods_, that.hostId_,
+        that.clientPairingId_, that.clientPairedSecret_);
   };
-  remoting.wcsSandbox.setOnIq(onIncomingIq);
+
+  this.getSharedSecret_(onSharedSecretReceived);
 }
 
 /**
@@ -922,7 +885,7 @@ remoting.ClientSession.prototype.getSharedSecret_ = function(callback) {
           tokenUrl, hostPublicKey, scope,
           that.plugin_.onThirdPartyTokenFetched.bind(that.plugin_));
     };
-    this.plugin_.fetchThirdPartyTokenHandler = fetchThirdPartyToken;
+    this.plugin_.setFetchThirdPartyTokenHandler(fetchThirdPartyToken);
   }
   if (this.accessCode_) {
     // Shared secret was already supplied before connecting (It2Me case).
@@ -936,7 +899,7 @@ remoting.ClientSession.prototype.getSharedSecret_ = function(callback) {
       that.fetchPin_(pairingSupported,
                      that.plugin_.onPinFetched.bind(that.plugin_));
     };
-    this.plugin_.fetchPinHandler = fetchPin;
+    this.plugin_.setFetchPinHandler(fetchPin);
     callback('');
   } else {
     // Clients that don't support asking for a PIN asynchronously also don't
@@ -966,12 +929,13 @@ remoting.ClientSession.prototype.onConnectionStatusUpdate_ =
     }
     // Activate full-screen related UX.
     remoting.fullscreen.addListener(this.callOnFullScreenChanged_);
-    remoting.fullscreen.syncWithMaximize(true);
     if (remoting.windowFrame) {
       remoting.windowFrame.setClientSession(this);
     } else {
       remoting.toolbar.setClientSession(this);
     }
+    remoting.optionsMenu.setClientSession(this);
+    document.body.classList.add('connected');
 
     this.container_.addEventListener('mousemove',
                                      this.updateMouseCursorPosition_,
@@ -1041,6 +1005,10 @@ remoting.ClientSession.prototype.onSetCapabilities_ = function(capabilities) {
                                         clientArea.height,
                                         window.devicePixelRatio);
   }
+  if (this.hasCapability_(
+      remoting.ClientSession.Capability.VIDEO_RECORDER)) {
+    this.videoFrameRecorder_ = new remoting.VideoFrameRecorder(this.plugin_);
+  }
 };
 
 /**
@@ -1070,6 +1038,7 @@ remoting.ClientSession.prototype.setState_ = function(newState) {
   this.logToServer.logClientSessionStateChange(state, this.error_, this.mode_);
   if (this.state_ == remoting.ClientSession.State.CONNECTED) {
     this.createGnubbyAuthHandler_();
+    this.createCastExtensionHandler_();
   }
 
   this.raiseEvent(remoting.ClientSession.Events.stateChanged,
@@ -1147,10 +1116,10 @@ remoting.ClientSession.prototype.pauseAudio = function(pause) {
  */
 remoting.ClientSession.prototype.onDesktopSizeChanged_ = function() {
   console.log('desktop size changed: ' +
-              this.plugin_.desktopWidth + 'x' +
-              this.plugin_.desktopHeight +' @ ' +
-              this.plugin_.desktopXDpi + 'x' +
-              this.plugin_.desktopYDpi + ' DPI');
+              this.plugin_.getDesktopWidth() + 'x' +
+              this.plugin_.getDesktopHeight() +' @ ' +
+              this.plugin_.getDesktopXDpi() + 'x' +
+              this.plugin_.getDesktopYDpi() + ' DPI');
   this.updateDimensions();
   this.updateScrollbarVisibility();
 };
@@ -1162,14 +1131,14 @@ remoting.ClientSession.prototype.onDesktopSizeChanged_ = function() {
  * @return {void} Nothing.
  */
 remoting.ClientSession.prototype.updateDimensions = function() {
-  if (this.plugin_.desktopWidth == 0 ||
-      this.plugin_.desktopHeight == 0) {
+  if (this.plugin_.getDesktopWidth() == 0 ||
+      this.plugin_.getDesktopHeight() == 0) {
     return;
   }
 
   var clientArea = this.getClientArea_();
-  var desktopWidth = this.plugin_.desktopWidth;
-  var desktopHeight = this.plugin_.desktopHeight;
+  var desktopWidth = this.plugin_.getDesktopWidth();
+  var desktopHeight = this.plugin_.getDesktopHeight();
 
   // When configured to display a host at its original size, we aim to display
   // it as close to its physical size as possible, without losing data:
@@ -1183,8 +1152,8 @@ remoting.ClientSession.prototype.updateDimensions = function() {
   // an initial scale factor based on the client devicePixelRatio and host DPI.
 
   // Determine the effective device pixel ratio of the host, based on DPI.
-  var hostPixelRatioX = Math.ceil(this.plugin_.desktopXDpi / 96);
-  var hostPixelRatioY = Math.ceil(this.plugin_.desktopYDpi / 96);
+  var hostPixelRatioX = Math.ceil(this.plugin_.getDesktopXDpi() / 96);
+  var hostPixelRatioY = Math.ceil(this.plugin_.getDesktopYDpi() / 96);
   var hostPixelRatio = Math.min(hostPixelRatioX, hostPixelRatioY);
 
   // Down-scale by the smaller of the client and host ratios.
@@ -1304,19 +1273,6 @@ remoting.ClientSession.prototype.onFullScreenChanged_ = function (fullscreen) {
 };
 
 /**
- * Updates the options menu to reflect the current scale-to-fit and full-screen
- * settings.
- * @return {void} Nothing.
- * @private
- */
-remoting.ClientSession.prototype.onShowOptionsMenu_ = function() {
-  remoting.MenuButton.select(this.resizeToClientButton_, this.resizeToClient_);
-  remoting.MenuButton.select(this.shrinkToFitButton_, this.shrinkToFit_);
-  remoting.MenuButton.select(this.fullScreenButton_,
-                             remoting.fullscreen.isActive());
-};
-
-/**
  * Scroll the client plugin by the specified amount, keeping it visible.
  * Note that this is only used in content full-screen mode (not windowed or
  * browser full-screen modes), where window.scrollBy and the scrollTop and
@@ -1535,3 +1491,84 @@ remoting.ClientSession.prototype.getPluginPositionForTesting = function() {
     left: parseFloat(style.marginLeft)
   };
 };
+
+/**
+ * Send a Cast extension message to the host.
+ * @param {Object} data The cast message data.
+ */
+remoting.ClientSession.prototype.sendCastExtensionMessage = function(data) {
+  if (!this.plugin_)
+    return;
+  this.plugin_.sendClientMessage('cast_message', JSON.stringify(data));
+};
+
+/**
+ * Process a remote Cast extension message from the host.
+ * @param {string} data Remote cast extension data message.
+ * @private
+ */
+remoting.ClientSession.prototype.processCastExtensionMessage_ = function(data) {
+  if (this.castExtensionHandler_) {
+    try {
+      this.castExtensionHandler_.onMessage(data);
+    } catch (err) {
+      console.error('Failed to process cast message: ',
+          /** @type {*} */ (err));
+    }
+  } else {
+    console.error('Received unexpected cast message');
+  }
+};
+
+/**
+ * Create a CastExtensionHandler and inform the host that cast extension
+ * is supported.
+ * @private
+ */
+remoting.ClientSession.prototype.createCastExtensionHandler_ = function() {
+  if (remoting.enableCast && this.mode_ == remoting.ClientSession.Mode.ME2ME) {
+    this.castExtensionHandler_ = new remoting.CastExtensionHandler(this);
+  }
+};
+
+/**
+ * Returns true if the ClientSession can record video frames to a file.
+ * @return {boolean}
+ */
+remoting.ClientSession.prototype.canRecordVideo = function() {
+  return !!this.videoFrameRecorder_;
+}
+
+/**
+ * Returns true if the ClientSession is currently recording video frames.
+ * @return {boolean}
+ */
+remoting.ClientSession.prototype.isRecordingVideo = function() {
+  if (!this.videoFrameRecorder_) {
+    return false;
+  }
+  return this.videoFrameRecorder_.isRecording();
+}
+
+/**
+ * Starts or stops recording of video frames.
+ */
+remoting.ClientSession.prototype.startStopRecording = function() {
+  if (this.videoFrameRecorder_) {
+    this.videoFrameRecorder_.startStopRecording();
+  }
+}
+
+/**
+ * Handles protocol extension messages.
+ * @param {string} type Type of extension message.
+ * @param {string} data Contents of the extension message.
+ * @return {boolean} True if the message was recognized, false otherwise.
+ */
+remoting.ClientSession.prototype.handleExtensionMessage =
+    function(type, data) {
+  if (this.videoFrameRecorder_) {
+    return this.videoFrameRecorder_.handleMessage(type, data);
+  }
+  return false;
+}