Bug 1217683 - Add sendIceCandidate and implement |close(aReason)| in TCPControlChannel. r=fabrice
authorJunior Hsu <juhsu@mozilla.com>
Mon, 25 May 2015 15:23:26 +0800
changeset 304671 4633017e40fbb7748c774a6a96f24fda1bf44cc0
parent 304670 3ede2a3f0399e61cff6202497384f3b98b24677e
child 304672 724ce80732b9b15b137123bfb01d4391a9abbf1b
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice
bugs1217683
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1217683 - Add sendIceCandidate and implement |close(aReason)| in TCPControlChannel. r=fabrice
dom/presentation/PresentationSessionInfo.cpp
dom/presentation/interfaces/nsIPresentationControlChannel.idl
dom/presentation/provider/TCPPresentationServer.js
dom/presentation/tests/xpcshell/test_tcp_control_channel.js
--- a/dom/presentation/PresentationSessionInfo.cpp
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -541,16 +541,23 @@ PresentationControllingInfo::GetAddress(
       this,
       &PresentationControllingInfo::OnGetAddress,
       EmptyCString()));
 #endif
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+PresentationControllingInfo::OnIceCandidate(const nsAString& aCandidate)
+{
+  MOZ_ASSERT(false, "Should not receive ICE candidates.");
+  return NS_ERROR_FAILURE;
+}
+
 nsresult
 PresentationControllingInfo::OnGetAddress(const nsACString& aAddress)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Prepare and send the offer.
   int32_t port;
   nsresult rv = mServerSocket->GetPort(&port);
@@ -847,16 +854,23 @@ PresentationPresentingInfo::OnOffer(nsIP
 NS_IMETHODIMP
 PresentationPresentingInfo::OnAnswer(nsIPresentationChannelDescription* aDescription)
 {
   MOZ_ASSERT(false, "Receiver side should not receive answer.");
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
+PresentationPresentingInfo::OnIceCandidate(const nsAString& aCandidate)
+{
+  MOZ_ASSERT(false, "Should not receive ICE candidates.");
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
 PresentationPresentingInfo::NotifyOpened()
 {
   // Do nothing.
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationPresentingInfo::NotifyClosed(nsresult aReason)
--- a/dom/presentation/interfaces/nsIPresentationControlChannel.idl
+++ b/dom/presentation/interfaces/nsIPresentationControlChannel.idl
@@ -27,74 +27,82 @@ interface nsIPresentationChannelDescript
   // SDP for Data Channel.
   // Should only be used while type == TYPE_DATACHANNEL.
   readonly attribute DOMString dataChannelSDP;
 };
 
 /*
  * The callbacks for events on control channel.
  */
-[scriptable, uuid(d0cdc638-a9d5-4bcd-838c-3aed7c3f2a6b)]
+[scriptable, uuid(96dd548f-7d0f-43c1-b1ad-28e666cf1e82)]
 interface nsIPresentationControlChannelListener: nsISupports
 {
   /*
    * Callback for receiving offer from remote endpoint.
    * @param offer The received offer.
    */
   void onOffer(in nsIPresentationChannelDescription offer);
 
   /*
    * Callback for receiving answer from remote endpoint.
    * @param answer The received answer.
    */
   void onAnswer(in nsIPresentationChannelDescription answer);
 
   /*
+   * Callback for receiving ICE candidate from remote endpoint.
+   * @param answer The received answer.
+   */
+  void onIceCandidate(in DOMString candidate);
+
+  /*
    * The callback for notifying channel opened.
    */
   void notifyOpened();
 
   /*
    * The callback for notifying channel closed.
    * @param reason The reason of channel close, NS_OK represents normal close.
    */
   void notifyClosed(in nsresult reason);
 };
 
 /*
  * The control channel for establishing RTCPeerConnection for a presentation
  * session. SDP Offer/Answer will be exchanged through this interface.
  */
-[scriptable, uuid(2c8ec493-4e5b-4df7-bedc-7ab25af323f0)]
+[scriptable, uuid(e60e208c-a9f5-4bc6-9a3e-47f3e4ae9c57)]
 interface nsIPresentationControlChannel: nsISupports
 {
   // The listener for handling events of this control channel.
   // All the events should be pending until listener is assigned.
   attribute nsIPresentationControlChannelListener listener;
 
   /*
-   * Send offer to remote endpiont. |onOffer| should be invoked
-   * on remote endpoint.
+   * Send offer to remote endpoint. |onOffer| should be invoked on remote
+   * endpoint.
    * @param offer The offer to send.
    * @throws  NS_ERROR_FAILURE on failure
    */
   void sendOffer(in nsIPresentationChannelDescription offer);
 
   /*
-   * Send answer to remote endpiont. |onAnswer| should
-   * be invoked on remote endpoint.
+   * Send answer to remote endpoint. |onAnswer| should be invoked on remote
+   * endpoint.
    * @param answer The answer to send.
    * @throws  NS_ERROR_FAILURE on failure
    */
   void sendAnswer(in nsIPresentationChannelDescription answer);
 
   /*
-   * Notify the app-to-app connection is fully established. (Only used at the
-   * receiver side.)
+   * Send ICE candidate to remote endpoint. |onIceCandidate| should be invoked
+   * on remote endpoint.
+   * @param candidate The candidate to send
+   * @throws NS_ERROR_FAILURE on failure
    */
-  void sendReceiverReady();
+  void sendIceCandidate(in DOMString candidate);
 
   /*
    * Close the transport channel.
    * @param reason The reason of channel close; NS_OK represents normal.
    */
   void close(in nsresult reason);
 };
--- a/dom/presentation/provider/TCPPresentationServer.js
+++ b/dom/presentation/provider/TCPPresentationServer.js
@@ -369,16 +369,24 @@ TCPControlChannel.prototype = {
     let msg = {
       type: "requestSession:Answer",
       presentationId: this.presentationId,
       answer: discriptionAsJson(aAnswer),
     };
     this._sendMessage("answer", msg);
   },
 
+  sendIceCandidate: function(aCandidate) {
+    let msg = {
+      type: "requestSession:IceCandidate",
+      presentationId: this.presentationId,
+      iceCandidate: aCandidate,
+    };
+    this._sendMessage("iceCandidate", msg);
+  },
   // may throw an exception
   _send: function(aMsg) {
     DEBUG && log("TCPControlChannel - Send: " + JSON.stringify(aMsg, null, 2));
 
     /**
      * XXX In TCP streaming, it is possible that more than one message in one
      * TCP packet. We use line delimited JSON to identify where one JSON encoded
      * object ends and the next begins. Therefore, we do not allow newline
@@ -433,17 +441,17 @@ TCPControlChannel.prototype = {
     DEBUG && log("TCPControlChannel - onStartRequest with role: "
                  + this._direction);
   },
 
   // nsIRequestObserver (Triggered by nsIInputStreamPump.asyncRead)
   onStopRequest: function(aRequest, aContext, aStatus) {
     DEBUG && log("TCPControlChannel - onStopRequest: " + aStatus
                  + " with role: " + this._direction);
-    this.close();
+    this.close(Cr.NS_OK);
     this._notifyClosed(aStatus);
   },
 
   // nsIStreamListener (Triggered by nsIInputStreamPump.asyncRead)
   onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
     let data = NetUtil.readInputStreamToString(aInputStream,
                                                aInputStream.available());
     DEBUG && log("TCPControlChannel - onDataAvailable: " + data);
@@ -491,16 +499,24 @@ TCPControlChannel.prototype = {
       case "requestSession:Offer": {
         this._onOffer(aMsg.offer);
         break;
       }
       case "requestSession:Answer": {
         this._onAnswer(aMsg.answer);
         break;
       }
+      case "requestSession:IceCandidate": {
+        this._listener.onIceCandidate(aMsg.iceCandidate);
+        break;
+      }
+      case "requestSession:CloseReason": {
+        this._pendingCloseReason = aMsg.reason;
+        break;
+      }
     }
   },
 
   get listener() {
     return this._listener;
   },
 
   set listener(aListener) {
@@ -568,17 +584,17 @@ TCPControlChannel.prototype = {
     DEBUG && log("TCPControlChannel - notify answer: "
                  + JSON.stringify(aAnswer));
     this._listener.onAnswer(new ChannelDescription(aAnswer));
   },
 
   _notifyOpened: function() {
     this._connected = true;
     this._pendingClose = false;
-    this._pendingCloseReason = null;
+    this._pendingCloseReason = Cr.NS_OK;
 
     if (!this._listener) {
       this._pendingOpen = true;
       return;
     }
 
     DEBUG && log("TCPControlChannel - notify opened with role: "
                  + this._direction);
@@ -586,30 +602,47 @@ TCPControlChannel.prototype = {
   },
 
   _notifyClosed: function(aReason) {
     this._connected = false;
     this._pendingOpen = false;
     this._pendingOffer = null;
     this._pendingAnswer = null;
 
+    // Remote endpoint closes the control channel with abnormal reason.
+    if (aReason == Cr.NS_OK && this._pendingCloseReason != Cr.NS_OK) {
+      aReason = this._pendingCloseReason;
+    }
+
     if (!this._listener) {
-     this._pendingClose = true;
-     this._pendingCloseReason = aReason;
-     return;
+      this._pendingClose = true;
+      this._pendingCloseReason = aReason;
+      return;
     }
 
     DEBUG && log("TCPControlChannel - notify closed with role: "
                  + this._direction);
     this._listener.notifyClosed(aReason);
   },
 
-  close: function() {
-    DEBUG && log("TCPControlChannel - close");
+  close: function(aReason) {
+    DEBUG && log("TCPControlChannel - close with reason: " + aReason);
+
     if (this._connected) {
+      // default reason is NS_OK
+      if (typeof aReason !== "undefined" && aReason !== Cr.NS_OK) {
+        let msg = {
+          type: "requestSession:CloseReason",
+          presentationId: this.presentationId,
+          reason: aReason,
+        };
+        this._sendMessage("close", msg);
+        this._pendingCloseReason = aReason;
+      }
+
       this._transport.setEventSink(null, null);
       this._pump = null;
 
       this._input.close();
       this._output.close();
       this._presentationServer.releaseControlChannel(this);
 
       this._connected = false;
--- a/dom/presentation/tests/xpcshell/test_tcp_control_channel.js
+++ b/dom/presentation/tests/xpcshell/test_tcp_control_channel.js
@@ -39,16 +39,19 @@ function TestDescription(aType, aTcpAddr
 
 TestDescription.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationChannelDescription]),
 }
 
 const CONTROLLER_CONTROL_CHANNEL_PORT = 36777;
 const PRESENTER_CONTROL_CHANNEL_PORT = 36888;
 
+var CLOSE_CONTROL_CHANNEL_REASON = Cr.NS_OK;
+var candidate;
+
 // presenter's presentation channel description
 const OFFER_ADDRESS = '192.168.123.123';
 const OFFER_PORT = 123;
 
 // controller's presentation channel description
 const ANSWER_ADDRESS = '192.168.321.321';
 const ANSWER_PORT = 321;
 
@@ -93,23 +96,33 @@ function testPresentationServer() {
             controllerControlChannel.sendAnswer(answer);
           } catch (e) {
             Assert.ok(false, 'sending answer fails' + e);
           }
         },
         onAnswer: function(aAnswer) {
           Assert.ok(false, 'get answer');
         },
+        onIceCandidate: function(aCandidate) {
+          Assert.ok(true, '3. controllerControlChannel: get ice candidate, close channel');
+          let recvCandidate = JSON.parse(aCandidate);
+          for (let key in recvCandidate) {
+            if (typeof(recvCandidate[key]) !== "function") {
+              Assert.equal(recvCandidate[key], candidate[key], "key " + key + " should match.");
+            }
+          }
+          controllerControlChannel.close(CLOSE_CONTROL_CHANNEL_REASON);
+        },
         notifyOpened: function() {
           Assert.equal(this.status, 'created', '0. controllerControlChannel: opened');
           this.status = 'opened';
         },
         notifyClosed: function(aReason) {
-          Assert.equal(this.status, 'onOffer', '3. controllerControlChannel: closed');
-          Assert.equal(aReason, Cr.NS_OK, 'presenterControlChannel notify closed NS_OK');
+          Assert.equal(this.status, 'onOffer', '4. controllerControlChannel: closed');
+          Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, 'presenterControlChannel notify closed');
           this.status = 'closed';
           yayFuncs.controllerControlChannelClose();
         },
         QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
       };
     },
 
     QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPPresentationServerListener]),
@@ -127,40 +140,47 @@ function testPresentationServer() {
                                                    'testPresentationId');
 
   presenterControlChannel.listener = {
     status: 'created',
     onOffer: function(offer) {
       Assert.ok(false, 'get offer');
     },
     onAnswer: function(aAnswer) {
-      Assert.equal(this.status, 'opened', '2. presenterControlChannel: get answer, close channel');
+      Assert.equal(this.status, 'opened', '2. presenterControlChannel: get answer, send ICE candidate');
 
       let answer = aAnswer.QueryInterface(Ci.nsIPresentationChannelDescription);
       Assert.strictEqual(answer.tcpAddress.queryElementAt(0,Ci.nsISupportsCString).data,
                          ANSWER_ADDRESS,
                          'expected answer address array');
       Assert.equal(answer.tcpPort, ANSWER_PORT, 'expected answer port');
-
-      presenterControlChannel.close(Cr.NS_OK);
+      candidate = {
+        candidate: "1 1 UDP 1 127.0.0.1 34567 type host",
+        sdpMid: "helloworld",
+        sdpMLineIndex: 1
+      };
+      presenterControlChannel.sendIceCandidate(JSON.stringify(candidate));
+    },
+    onIceCandidate: function(aCandidate) {
+      Assert.ok(false, 'get ICE candidate');
     },
     notifyOpened: function() {
       Assert.equal(this.status, 'created', '0. presenterControlChannel: opened, send offer');
       this.status = 'opened';
       try {
         let tcpType = Ci.nsIPresentationChannelDescription.TYPE_TCP;
         let offer = new TestDescription(tcpType, [OFFER_ADDRESS], OFFER_PORT)
         presenterControlChannel.sendOffer(offer);
       } catch (e) {
         Assert.ok(false, 'sending offer fails:' + e);
       }
     },
     notifyClosed: function(aReason) {
       this.status = 'closed';
-      Assert.equal(aReason, Cr.NS_OK, '3. presenterControlChannel notify closed NS_OK');
+      Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, '4. presenterControlChannel notify closed');
       yayFuncs.presenterControlChannelClose();
     },
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
   };
 }
 
 function setOffline() {
   let expectedReason;
@@ -193,18 +213,25 @@ function shutdown()
     onClose: function(aReason) {
       Assert.equal(aReason, Cr.NS_OK, 'TCPPresentationServer close success');
       run_next_test();
     },
   }
   tps.close();
 }
 
+// Test manually close control channel with NS_ERROR_FAILURE
+function changeCloseReason() {
+  CLOSE_CONTROL_CHANNEL_REASON = Cr.NS_ERROR_FAILURE;
+  run_next_test();
+}
+
 add_test(loopOfferAnser);
 add_test(setOffline);
+add_test(changeCloseReason);
 add_test(oneMoreLoop);
 add_test(shutdown);
 
 function run_test() {
   Services.prefs.setBoolPref("dom.presentation.tcp_server.debug", true);
 
   do_register_cleanup(() => {
     Services.prefs.clearUserPref("dom.presentation.tcp_server.debug");