Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / test / data / webrtc / message_handling.js
index 377f95f..6329ed3 100644 (file)
  */
 
 // This file requires these functions to be defined globally by someone else:
-// function handleMessage(peerConnection, message)
 // function createPeerConnection(stun_server, useRtpDataChannel)
-// function setupCall(peerConnection)
-// function answerCall(peerConnection, message)
+// function createOffer(peerConnection, constraints, callback)
+// function receiveOffer(peerConnection, offer, constraints, callback)
+// function receiveAnswer(peerConnection, answer, callback)
 
 // Currently these functions are supplied by jsep01_call.js.
 
 /**
- * This object represents the call.
- * @private
- */
-var gPeerConnection = null;
-
-/**
- * True if we are accepting incoming calls.
- * @private
- */
-var gAcceptsIncomingCalls = true;
-
-/**
- * Our peer id as assigned by the peerconnection_server.
- * @private
- */
-var gOurPeerId = null;
-
-/**
- * The client id we use to identify to peerconnection_server.
- * @private
- */
-var gOurClientName = null;
-
-/**
- * The URL to the peerconnection_server.
- * @private
- */
-var gServerUrl = null;
-
-/**
- * The remote peer's id. We receive this one either when we connect (in the case
- * our peer connects before us) or in a notification later.
+ * We need a STUN server for some API calls.
  * @private
  */
-var gRemotePeerId = null;
+var STUN_SERVER = 'stun.l.google.com:19302';
 
 /**
- * Whether or not to auto-respond by adding our local stream when we are called.
+ * This object represents the call.
  * @private
  */
-var gAutoAddLocalToPeerConnectionStreamWhenCalled = true;
+var gPeerConnection = null;
 
 /**
- * The one and only data channel.
- * @private
+ * If true, any created peer connection will use RTP data
+ * channels. Otherwise it will use SCTP data channels.
  */
-var gDataChannel = null;
+var gUseRtpDataChannels = true;
 
 /**
- * The DTMF sender.
+ * This stores ICE candidates generated on this side.
  * @private
  */
-var gDtmfSender = null;
+var gIceCandidates = [];
 
-/**
- * We need a STUN server for some API calls.
- * @private
- */
-var STUN_SERVER = 'stun.l.google.com:19302';
+// Public interface to tests. These are expected to be called with
+// ExecuteJavascript invocations from the browser tests and will return answers
+// through the DOM automation controller.
 
 /**
- * If true, any created peer connection will use RTP data
- * channels. Otherwise it will use SCTP data channels.
+ * Creates a peer connection. Must be called before most other public functions
+ * in this file.
  */
-var gUseRtpDataChannels = true;
-
-// Public interface to tests.
+function preparePeerConnection() {
+  if (gPeerConnection != null)
+    throw failTest('creating peer connection, but we already have one.');
 
+  gPeerConnection = createPeerConnection(STUN_SERVER, gUseRtpDataChannels);
+  returnToTest('ok-peerconnection-created');
+}
 
 /**
- * Connects to the provided peerconnection_server.
+ * Asks this page to create a local offer.
  *
- * @param{string} serverUrl The server URL in string form without an ending
- *     slash, something like http://localhost:8888.
- * @param{string} clientName The name to use when connecting to the server.
+ * Returns a string on the format ok-(JSON encoded session description).
+ *
+ * @param {!object} constraints Any createOffer constraints.
  */
-function connect(serverUrl, clientName) {
-  if (gOurPeerId != null)
-    throw failTest('connecting, but is already connected.');
-
-  debug('Connecting to ' + serverUrl + ' as ' + clientName);
-  gServerUrl = serverUrl;
-  gOurClientName = clientName;
-
-  request = new XMLHttpRequest();
-  request.open('GET', serverUrl + '/sign_in?' + clientName, true);
-  debug(serverUrl + '/sign_in?' + clientName);
-  request.onreadystatechange = function() {
-    connectCallback_(request);
-  }
-  request.send();
-}
+function createLocalOffer(constraints) {
+  if (gPeerConnection == null)
+    throw failTest('Negotiating call, but we have no peer connection.');
 
-/**
- * Checks if the remote peer has connected. Returns peer-connected if that is
- * the case, otherwise no-peer-connected.
- */
-function remotePeerIsConnected() {
-  if (gRemotePeerId == null)
-    returnToTest('no-peer-connected');
-  else
-    returnToTest('peer-connected');
+  // TODO(phoglund): move jsep01.call stuff into this file and remove need
+  // of the createOffer method, etc.
+  createOffer(gPeerConnection, constraints, function(localOffer) {
+    returnToTest('ok-' + JSON.stringify(localOffer));
+  });
 }
 
 /**
- * Set if RTP data channels should be used for peerconnections.
- * @param{boolean} useRtpDataChannel
+ * Asks this page to accept an offer and generate an answer.
+ *
+ * Returns a string on the format ok-(JSON encoded session description).
+ *
+ * @param {!string} sessionDescJson A JSON-encoded session description of type
+ *     'offer'.
+ * @param {!object} constraints Any createAnswer constraints.
  */
-function useRtpDataChannelsForNewPeerConnections(useRtpDataChannels) {
-  gUseRtpDataChannels = useRtpDataChannels;
-}
+function receiveOfferFromPeer(sessionDescJson, constraints) {
+  if (gPeerConnection == null)
+    throw failTest('Receiving offer, but we have no peer connection.');
 
-/**
- * Creates a peer connection. Must be called before most other public functions
- * in this file.
- */
-function preparePeerConnection() {
-  if (gPeerConnection != null)
-    throw failTest('creating peer connection, but we already have one.');
+  offer = parseJson_(sessionDescJson);
+  if (!offer.type)
+    failTest('Got invalid session description from peer: ' + sessionDescJson);
+  if (offer.type != 'offer')
+    failTest('Expected to receive offer from peer, got ' + offer.type);
 
-  gPeerConnection = createPeerConnection(STUN_SERVER, gUseRtpDataChannels);
-  returnToTest('ok-peerconnection-created');
+  receiveOffer(gPeerConnection, offer , constraints, function(answer) {
+    returnToTest('ok-' + JSON.stringify(answer));
+  });
 }
 
 /**
- * Negotiates a call with the other side. This will create a peer connection on
- * the other side if there isn't one. The other side will automatically add any
- * stream it has unless doNotAutoAddLocalStreamWhenCalled() has been called.
+ * Asks this page to accept an answer generated by the peer in response to a
+ * previous offer by this page
  *
- * To call this method we need to be aware of the other side, e.g. we must be
- * connected to peerconnection_server and we must have exactly one peer on that
- * server.
+ * Returns a string ok-accepted-answer on success.
  *
- * This method may be called any number of times. If you haven't added any
- * streams to the call, an "empty" call will result. The method will return
- * ok-negotiating immediately to the test if the negotiation was successfully
- * sent.
+ * @param {!string} sessionDescJson A JSON-encoded session description of type
+ *     'answer'.
  */
-function negotiateCall() {
+function receiveAnswerFromPeer(sessionDescJson) {
   if (gPeerConnection == null)
-    throw failTest('negotiating call, but we have no peer connection.');
-  if (gOurPeerId == null)
-    throw failTest('negotiating call, but not connected.');
-  if (gRemotePeerId == null)
-    throw failTest('negotiating call, but missing remote peer.');
-
-  setupCall(gPeerConnection);
-  returnToTest('ok-negotiating');
+    throw failTest('Receiving offer, but we have no peer connection.');
+
+  answer = parseJson_(sessionDescJson);
+  if (!answer.type)
+    failTest('Got invalid session description from peer: ' + sessionDescJson);
+  if (answer.type != 'answer')
+    failTest('Expected to receive answer from peer, got ' + answer.type);
+
+  receiveAnswer(gPeerConnection, answer, function() {
+    returnToTest('ok-accepted-answer');
+  });
 }
 
 /**
@@ -226,374 +181,86 @@ function playAudioFile() {
 }
 
 /**
- * Removes the local stream from the peer connection. You will have to
- * re-negotiate the call for this to take effect in the call.
- */
-function removeLocalStream() {
-  if (gPeerConnection == null)
-    throw failTest('attempting to remove local stream, but no call is up');
-
-  removeLocalStreamFromPeerConnection(gPeerConnection);
-  returnToTest('ok-local-stream-removed');
-}
-
-/**
- * (see getReadyState)
- */
-function getPeerConnectionReadyState() {
-  returnToTest(getReadyState());
-}
-
-/**
- * Toggles the remote audio stream's enabled state on the peer connection, given
- * that a call is active. Returns ok-[typeToToggle]-toggled-to-[true/false]
- * on success.
- *
- * @param selectAudioOrVideoTrack: A function that takes a remote stream as
- *     argument and returns a track (e.g. either the video or audio track).
- * @param typeToToggle: Either "audio" or "video" depending on what the selector
- *     function selects.
- */
-function toggleRemoteStream(selectAudioOrVideoTrack, typeToToggle) {
-  if (gPeerConnection == null)
-    throw failTest('Tried to toggle remote stream, ' +
-                   'but have no peer connection.');
-  if (gPeerConnection.getRemoteStreams().length == 0)
-    throw failTest('Tried to toggle remote stream, ' +
-                   'but not receiving any stream.');
-
-  var track = selectAudioOrVideoTrack(gPeerConnection.getRemoteStreams()[0]);
-  toggle_(track, 'remote', typeToToggle);
-}
-
-/**
- * See documentation on toggleRemoteStream (this function is the same except
- * we are looking at local streams).
- */
-function toggleLocalStream(selectAudioOrVideoTrack, typeToToggle) {
-  if (gPeerConnection == null)
-    throw failTest('Tried to toggle local stream, ' +
-                   'but have no peer connection.');
-  if (gPeerConnection.getLocalStreams().length == 0)
-    throw failTest('Tried to toggle local stream, but there is no local ' +
-                   'stream in the call.');
-
-  var track = selectAudioOrVideoTrack(gPeerConnection.getLocalStreams()[0]);
-  toggle_(track, 'local', typeToToggle);
-}
-
-/**
- * Hangs up a started call. Returns ok-call-hung-up on success. This tab will
- * not accept any incoming calls after this call.
+ * Hangs up a started call. Returns ok-call-hung-up on success.
  */
 function hangUp() {
   if (gPeerConnection == null)
     throw failTest('hanging up, but has no peer connection');
-  if (getReadyState() != 'active')
-    throw failTest('hanging up, but ready state is not active (no call up).');
-  sendToPeer(gRemotePeerId, 'BYE');
-  closeCall_();
-  gAcceptsIncomingCalls = false;
+  gPeerConnection.close();
+  gPeerConnection = null;
   returnToTest('ok-call-hung-up');
 }
 
 /**
- * Start accepting incoming calls again after a hangup.
- */
-function acceptIncomingCallsAgain() {
-  gAcceptsIncomingCalls = true;
-}
-
-/**
- * Do not auto-add the local stream when called.
- */
-function doNotAutoAddLocalStreamWhenCalled() {
-  gAutoAddLocalToPeerConnectionStreamWhenCalled = false;
-}
-
-/**
- * Disconnects from the peerconnection server. Returns ok-disconnected on
- * success.
- */
-function disconnect() {
-  if (gOurPeerId == null)
-    throw failTest('Disconnecting, but we are not connected.');
-
-  request = new XMLHttpRequest();
-  request.open('GET', gServerUrl + '/sign_out?peer_id=' + gOurPeerId, false);
-  request.send();
-  gOurPeerId = null;
-  returnToTest('ok-disconnected');
-}
-
-/**
- * Creates a DataChannel on the current PeerConnection. Only one DataChannel can
- * be created on each PeerConnection.
- * Returns ok-datachannel-created on success.
+ * Retrieves all ICE candidates generated on this side. Must be called after
+ * ICE candidate generation is triggered (for instance by running a call
+ * negotiation). This function will wait if necessary if we're not done
+ * generating ICE candidates on this side.
+ *
+ * Returns a JSON-encoded array of RTCIceCandidate instances to the test.
  */
-function createDataChannelOnPeerConnection() {
+function getAllIceCandidates() {
   if (gPeerConnection == null)
-    throw failTest('Tried to create data channel, ' +
-        'but have no peer connection.');
+    throw failTest('Trying to get ICE candidates, but has no peer connection.');
 
-  createDataChannel(gPeerConnection, gOurClientName);
-  returnToTest('ok-datachannel-created');
-}
-
-/**
- * Close the DataChannel on the current PeerConnection.
- * Returns ok-datachannel-close on success.
- */
-function closeDataChannelOnPeerConnection() {
-  if (gPeerConnection == null)
-    throw failTest('Tried to close data channel, ' +
-        'but have no peer connection.');
+  if (gPeerConnection.iceGatheringState != 'complete') {
+    console.log('Still ICE gathering - waiting...');
+    setTimeout(getAllIceCandidates, 100);
+    return;
+  }
 
-  closeDataChannel(gPeerConnection);
-  returnToTest('ok-datachannel-close');
+  returnToTest(JSON.stringify(gIceCandidates));
 }
 
 /**
- * Creates a DTMF sender on the current PeerConnection.
- * Returns ok-dtmfsender-created on success.
+ * Receives ICE candidates from the peer.
+ *
+ * Returns ok-received-candidates to the test on success.
+ *
+ * @param iceCandidatesJson a JSON-encoded array of RTCIceCandidate instances.
  */
-function createDtmfSenderOnPeerConnection() {
+function receiveIceCandidates(iceCandidatesJson) {
   if (gPeerConnection == null)
-    throw failTest('Tried to create DTMF sender, ' +
-        'but have no peer connection.');
+    throw failTest('Received ICE candidate, but has no peer connection');
 
-  createDtmfSender(gPeerConnection);
-  returnToTest('ok-dtmfsender-created');
-}
+  var iceCandidates = parseJson_(iceCandidatesJson);
+  if (!iceCandidates.length)
+    throw failTest('Received invalid ICE candidate list from peer: ' +
+      iceCandidatesJson);
 
-/**
- * Send DTMF tones on the gDtmfSender.
- * Returns ok-dtmf-sent on success.
- */
-function insertDtmfOnSender(tones, duration, interToneGap) {
-  if (gDtmfSender == null)
-    throw failTest('Tried to insert DTMF tones, ' +
-        'but have no DTMF sender.');
+  iceCandidates.forEach(function(iceCandidate) {
+    if (!iceCandidate.candidate)
+      failTest('Received invalid ICE candidate from peer: ' +
+        iceCandidatesJson);
+
+    gPeerConnection.addIceCandidate(new RTCIceCandidate(iceCandidate));
+  });
 
-  insertDtmf(tones, duration, interToneGap);
-  returnToTest('ok-dtmf-sent');
+  returnToTest('ok-received-candidates');
 }
 
 // Public interface to signaling implementations, such as JSEP.
 
 /**
- * Sends a message to a peer through the peerconnection_server.
- */
-function sendToPeer(peer, message) {
-  var messageToLog = message.sdp ? message.sdp : message;
-  debug('Sending message ' + messageToLog + ' to peer ' + peer + '.');
-
-  var request = new XMLHttpRequest();
-  var url = gServerUrl + '/message?peer_id=' + gOurPeerId + '&to=' + peer;
-  request.open('POST', url, false);
-  request.setRequestHeader('Content-Type', 'text/plain');
-  request.send(message);
-}
-
-/**
- * Returns true if we are disconnected from peerconnection_server.
+ * Enqueues an ICE candidate for sending to the peer.
+ *
+ * @param {!RTCIceCandidate} The ICE candidate to send.
  */
-function isDisconnected() {
-  return gOurPeerId == null;
+function sendIceCandidate(message) {
+  gIceCandidates.push(message);
 }
 
 /**
- * @return {!string} The current peer connection's ready state, or
- *     'no-peer-connection' if there is no peer connection up.
- *
- * NOTE: The PeerConnection states are changing and until chromium has
- *       implemented the new states we have to use this interim solution of
- *       always assuming that the PeerConnection is 'active'.
+ * Parses JSON-encoded session descriptions and ICE candidates.
+ * @private
  */
-function getReadyState() {
-  if (gPeerConnection == null)
-    return 'no-peer-connection';
-
-  return 'active';
-}
-
-// Internals.
-
-/** @private */
-function toggle_(track, localOrRemote, audioOrVideo) {
-  if (!track)
-    throw failTest('Tried to toggle ' + localOrRemote + ' ' + audioOrVideo +
-             ' stream, but has no such stream.');
-
-  track.enabled = !track.enabled;
-  returnToTest('ok-' + audioOrVideo + '-toggled-to-' + track.enabled);
-}
-
-/** @private */
-function connectCallback_(request) {
-  debug('Connect callback: ' + request.status + ', ' + request.readyState);
-  if (request.status == 0) {
-    debug('peerconnection_server doesn\'t seem to be up.');
-    returnToTest('failed-to-connect');
-  }
-  if (request.readyState == 4 && request.status == 200) {
-    gOurPeerId = parseOurPeerId_(request.responseText);
-    gRemotePeerId = parseRemotePeerIdIfConnected_(request.responseText);
-    startHangingGet_(gServerUrl, gOurPeerId);
-    returnToTest('ok-connected');
-  }
-}
-
-/** @private */
-function parseOurPeerId_(responseText) {
-  // According to peerconnection_server's protocol.
-  var peerList = responseText.split('\n');
-  return parseInt(peerList[0].split(',')[1]);
-}
-
-/** @private */
-function parseRemotePeerIdIfConnected_(responseText) {
-  var peerList = responseText.split('\n');
-  if (peerList.length == 1) {
-    // No peers have connected yet - we'll get their id later in a notification.
-    return null;
-  }
-  var remotePeerId = null;
-  for (var i = 0; i < peerList.length; i++) {
-    if (peerList[i].length == 0)
-      continue;
-
-    var parsed = peerList[i].split(',');
-    var name = parsed[0];
-    var id = parsed[1];
-
-    if (id != gOurPeerId) {
-      debug('Found remote peer with name ' + name + ', id ' +
-            id + ' when connecting.');
-
-      // There should be at most one remote peer in this test.
-      if (remotePeerId != null)
-        throw failTest('Expected just one remote peer in this test: ' +
-                       'found several.');
-
-      // Found a remote peer.
-      remotePeerId = id;
-    }
-  }
-  return remotePeerId;
-}
-
-/** @private */
-function startHangingGet_(server, ourId) {
-  if (isDisconnected())
-    return;
-  hangingGetRequest = new XMLHttpRequest();
-  hangingGetRequest.onreadystatechange = function() {
-    hangingGetCallback_(hangingGetRequest, server, ourId);
-  }
-  hangingGetRequest.ontimeout = function() {
-    hangingGetTimeoutCallback_(hangingGetRequest, server, ourId);
-  }
-  callUrl = server + '/wait?peer_id=' + ourId;
-  debug('Sending ' + callUrl);
-  hangingGetRequest.open('GET', callUrl, true);
-  hangingGetRequest.send();
-}
-
-/** @private */
-function hangingGetCallback_(hangingGetRequest, server, ourId) {
-  if (hangingGetRequest.readyState != 4 || hangingGetRequest.status == 0) {
-    // Code 0 is not possible if the server actually responded. Ignore.
-    return;
-  }
-  if (hangingGetRequest.status != 200) {
-    throw failTest('Error ' + hangingGetRequest.status + ' from server: ' +
-             hangingGetRequest.statusText);
-  }
-  var targetId = readResponseHeader_(hangingGetRequest, 'Pragma');
-  if (targetId == ourId)
-    handleServerNotification_(hangingGetRequest.responseText);
-  else
-    handlePeerMessage_(targetId, hangingGetRequest.responseText);
-
-  hangingGetRequest.abort();
-  restartHangingGet_(server, ourId);
-}
-
-/** @private */
-function hangingGetTimeoutCallback_(hangingGetRequest, server, ourId) {
-  debug('Hanging GET times out, re-issuing...');
-  hangingGetRequest.abort();
-  restartHangingGet_(server, ourId);
-}
-
-/** @private */
-function handleServerNotification_(message) {
-  var parsed = message.split(',');
-  if (parseInt(parsed[2]) == 1) {
-    // Peer connected - this must be our remote peer, and it must mean we
-    // connected before them (except if we happened to connect to the server
-    // at precisely the same moment).
-    debug('Found remote peer with name ' + parsed[0] + ', id ' +
-          parsed[1] + ' when connecting.');
-    gRemotePeerId = parseInt(parsed[1]);
-  }
-}
-
-/** @private */
-function closeCall_() {
-  if (gPeerConnection == null)
-    throw failTest('Closing call, but no call active.');
-  gPeerConnection.close();
-  gPeerConnection = null;
-}
-
-/** @private */
-function handlePeerMessage_(peerId, message) {
-  debug('Received message from peer ' + peerId + ': ' + message);
-  if (peerId != gRemotePeerId) {
-    throw failTest('Received notification from unknown peer ' + peerId +
-                   ' (only know about ' + gRemotePeerId + '.');
-  }
-  if (message.search('BYE') == 0) {
-    debug('Received BYE from peer: closing call');
-    closeCall_();
-    return;
-  }
-  if (gPeerConnection == null && gAcceptsIncomingCalls) {
-    // The other side is calling us.
-    debug('We are being called: answer...');
-
-    gPeerConnection = createPeerConnection(STUN_SERVER, gUseRtpDataChannels);
-    if (gAutoAddLocalToPeerConnectionStreamWhenCalled &&
-        obtainGetUserMediaResult() == 'ok-got-stream') {
-      debug('We have a local stream, so hook it up automatically.');
-      addLocalStreamToPeerConnection(gPeerConnection);
-    }
-    answerCall(gPeerConnection, message);
-    return;
-  }
-  if (gPeerConnection == null) {
-    debug('Discarding message ' + message + '; already disconnected.');
-    return;
-  }
-
-  handleMessage(gPeerConnection, message);
-}
-
-/** @private */
-function restartHangingGet_(server, ourId) {
-  window.setTimeout(function() {
-    startHangingGet_(server, ourId);
-  }, 0);
-}
-
-/** @private */
-function readResponseHeader_(request, key) {
-  var value = request.getResponseHeader(key)
-  if (value == null || value.length == 0) {
-    throw failTest('Received empty value ' + value +
-                   ' for response header key ' + key + '.');
+function parseJson_(json) {
+  // Escape since the \r\n in the SDP tend to get unescaped.
+  jsonWithEscapedLineBreaks = json.replace(/\r\n/g, '\\r\\n');
+  try {
+    return JSON.parse(jsonWithEscapedLineBreaks);
+  } catch (exception) {
+    failTest('Failed to parse JSON: ' + jsonWithEscapedLineBreaks + ', got ' +
+             exception);
   }
-  return parseInt(value);
 }