Bug 1209744 - Implement canTrickleIceCandidates attribute, r=bwc,khuey draft
authorMartin Thomson <martin.thomson@gmail.com>
Mon, 15 Feb 2016 11:51:33 +1100
changeset 330947 cd1e89ce1b58461aaf32905d2044ee64051ea338
parent 330946 14b934e83447666d9387751db3f943e07253c59e
child 330948 204442f0090300895202f70f2d16c1c245545377
push id10862
push usermartin.thomson@gmail.com
push dateMon, 15 Feb 2016 01:23:49 +0000
reviewersbwc, khuey
bugs1209744
milestone47.0a1
Bug 1209744 - Implement canTrickleIceCandidates attribute, r=bwc,khuey MozReview-Commit-ID: BblTl0v4OT4
dom/media/PeerConnection.js
dom/media/tests/mochitest/nonTrickleIce.js
dom/media/tests/mochitest/templates.js
dom/media/tests/mochitest/test_peerConnection_remoteRollback.html
dom/webidl/RTCPeerConnection.webidl
testing/web-platform/meta/webrtc/rtcpeerconnection/rtcpeerconnection-idl.html.ini
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -336,16 +336,20 @@ function RTCPeerConnection() {
   this._onGetStatsFailure = null;
   this._onReplaceTrackSender = null;
   this._onReplaceTrackWithTrack = null;
   this._onReplaceTrackSuccess = null;
   this._onReplaceTrackFailure = null;
 
   this._localType = null;
   this._remoteType = null;
+  // http://rtcweb-wg.github.io/jsep/#rfc.section.4.1.9
+  // canTrickle == null means unknown; when a remote description is received it
+  // is set to true or false based on the presence of the "trickle" ice-option
+  this._canTrickle = null;
 
   // States
   this._iceGatheringState = this._iceConnectionState = "new";
 }
 RTCPeerConnection.prototype = {
   classDescription: "RTCPeerConnection",
   classID: PC_CID,
   contractID: PC_CONTRACT,
@@ -931,17 +935,17 @@ RTCPeerConnection.prototype = {
       let origin = Cu.getWebIDLCallerPrincipal().origin;
 
       return this._chain(() => {
         let setRem = this.getPermission()
           .then(() => new this._win.Promise((resolve, reject) => {
             this._onSetRemoteDescriptionSuccess = resolve;
             this._onSetRemoteDescriptionFailure = reject;
             this._impl.setRemoteDescription(type, desc.sdp);
-          }));
+          })).then(() => { this._updateCanTrickle(); });
 
         if (desc.type === "rollback") {
           return setRem;
         }
 
         // Do setRemoteDescription and identity validation in parallel
         let validId = this._validateIdentity(desc.sdp, origin);
         return this._win.Promise.all([setRem, validId])
@@ -959,21 +963,50 @@ RTCPeerConnection.prototype = {
     let origin = Cu.getWebIDLCallerPrincipal().origin;
     return this._chain(
       () => this._certificateReady.then(
         () => this._localIdp.getIdentityAssertion(this._impl.fingerprint, origin)
       )
     );
   },
 
-  updateIce: function(config) {
-    throw new this._win.DOMException("updateIce not yet implemented",
-                                     "NotSupportedError");
+  get canTrickleIceCandidates() {
+    return this._canTrickle;
   },
 
+  _updateCanTrickle: function() {
+    let containsTrickle = section => {
+      let lines = section.toLowerCase().split(/(?:\r\n?|\n)/);
+      return lines.some(line => {
+        let prefix = "a=ice-options:";
+        if (line.substring(0, prefix.length) !== prefix) {
+          return false;
+        }
+        let tokens = line.substring(prefix.length).split(" ");
+        return tokens.some(x => x === "trickle");
+      });
+    };
+
+    let desc = null;
+    try {
+      // The getter for remoteDescription can throw if the pc is closed.
+      desc = this.remoteDescription;
+    } catch (e) {}
+    if (!desc) {
+      this._canTrickle = null;
+      return;
+    }
+
+    let sections = desc.sdp.split(/(?:\r\n?|\n)m=/);
+    let topSection = sections.shift();
+    this._canTrickle =
+      containsTrickle(topSection) || sections.every(containsTrickle);
+  },
+
+
   addIceCandidate: function(c, onSuccess, onError) {
     return this._legacyCatch(onSuccess, onError, () => {
       if (!c.candidate && !c.sdpMLineIndex) {
         throw new this._win.DOMException("Invalid candidate passed to addIceCandidate!",
                                          "InvalidParameterError");
       }
       return this._chain(() => new this._win.Promise((resolve, reject) => {
         this._onAddIceCandidateSuccess = resolve;
--- a/dom/media/tests/mochitest/nonTrickleIce.js
+++ b/dom/media/tests/mochitest/nonTrickleIce.js
@@ -1,60 +1,71 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+function removeTrickleOption(desc) {
+  var sdp = desc.sdp.replace(/\r\na=ice-options:trickle\r\n/, "\r\n");
+  return new mozRTCSessionDescription({ type: desc.type, sdp: sdp });
+}
+
 function makeOffererNonTrickle(chain) {
   chain.replace('PC_LOCAL_SETUP_ICE_HANDLER', [
     function PC_LOCAL_SETUP_NOTRICKLE_ICE_HANDLER(test) {
       // We need to install this callback before calling setLocalDescription
       // otherwise we might miss callbacks
       test.pcLocal.setupIceCandidateHandler(test, () => {});
       // We ignore ICE candidates because we want the full offer
     }
   ]);
   chain.replace('PC_REMOTE_GET_OFFER', [
     function PC_REMOTE_GET_FULL_OFFER(test) {
       return test.pcLocal.endOfTrickleIce.then(() => {
-        test._local_offer = test.pcLocal.localDescription;
+        test._local_offer = removeTrickleOption(test.pcLocal.localDescription);
         test._offer_constraints = test.pcLocal.constraints;
         test._offer_options = test.pcLocal.offerOptions;
       });
     }
   ]);
   chain.insertAfter('PC_REMOTE_SANE_REMOTE_SDP', [
     function PC_REMOTE_REQUIRE_REMOTE_SDP_CANDIDATES(test) {
       info("test.pcLocal.localDescription.sdp: " + JSON.stringify(test.pcLocal.localDescription.sdp));
       info("test._local_offer.sdp" + JSON.stringify(test._local_offer.sdp));
+      is(test.pcRemote._pc.canTrickleIceCandidates, false,
+         "Remote thinks that trickle isn't supported");
       ok(!test.localRequiresTrickleIce, "Local does NOT require trickle");
       ok(test._local_offer.sdp.includes("a=candidate"), "offer has ICE candidates")
       ok(test._local_offer.sdp.includes("a=end-of-candidates"), "offer has end-of-candidates");
     }
   ]);
+  chain.remove('PC_REMOTE_CHECK_CAN_TRICKLE_SYNC');
 }
 
 function makeAnswererNonTrickle(chain) {
   chain.replace('PC_REMOTE_SETUP_ICE_HANDLER', [
     function PC_REMOTE_SETUP_NOTRICKLE_ICE_HANDLER(test) {
       // We need to install this callback before calling setLocalDescription
       // otherwise we might miss callbacks
       test.pcRemote.setupIceCandidateHandler(test, () => {});
       // We ignore ICE candidates because we want the full offer
     }
   ]);
   chain.replace('PC_LOCAL_GET_ANSWER', [
     function PC_LOCAL_GET_FULL_ANSWER(test) {
       return test.pcRemote.endOfTrickleIce.then(() => {
-        test._remote_answer = test.pcRemote.localDescription;
+        test._remote_answer = removeTrickleOption(test.pcRemote.localDescription);
         test._answer_constraints = test.pcRemote.constraints;
       });
     }
   ]);
   chain.insertAfter('PC_LOCAL_SANE_REMOTE_SDP', [
     function PC_LOCAL_REQUIRE_REMOTE_SDP_CANDIDATES(test) {
       info("test.pcRemote.localDescription.sdp: " + JSON.stringify(test.pcRemote.localDescription.sdp));
       info("test._remote_answer.sdp" + JSON.stringify(test._remote_answer.sdp));
+      is(test.pcLocal._pc.canTrickleIceCandidates, false,
+         "Local thinks that trickle isn't supported");
       ok(!test.remoteRequiresTrickleIce, "Remote does NOT require trickle");
       ok(test._remote_answer.sdp.includes("a=candidate"), "answer has ICE candidates")
       ok(test._remote_answer.sdp.includes("a=end-of-candidates"), "answer has end-of-candidates");
     }
   ]);
+  chain.remove('PC_LOCAL_CHECK_CAN_TRICKLE_SYNC');
 }
--- a/dom/media/tests/mochitest/templates.js
+++ b/dom/media/tests/mochitest/templates.js
@@ -188,16 +188,25 @@ var commandsPeerConnectionInitial = [
        "Initial local ICE connection state is 'new'");
   },
 
   function PC_REMOTE_CHECK_INITIAL_ICE_STATE(test) {
     is(test.pcRemote.iceConnectionState, ICE_NEW,
        "Initial remote ICE connection state is 'new'");
   },
 
+  function PC_LOCAL_CHECK_INITIAL_CAN_TRICKLE_SYNC(test) {
+    is(test.pcLocal._pc.canTrickleIceCandidates, null,
+       "Local trickle status should start out unknown");
+  },
+
+  function PC_REMOTE_CHECK_INITIAL_CAN_TRICKLE_SYNC(test) {
+    is(test.pcRemote._pc.canTrickleIceCandidates, null,
+       "Remote trickle status should start out unknown");
+  },
 ];
 
 var commandsGetUserMedia = [
   function PC_LOCAL_GUM(test) {
     return test.pcLocal.getAllUserMedia(test.pcLocal.constraints);
   },
 
   function PC_REMOTE_GUM(test) {
@@ -371,16 +380,21 @@ var commandsPeerConnectionOfferAnswer = 
   },
   function PC_LOCAL_SANE_REMOTE_SDP(test) {
     test.pcLocal.remoteRequiresTrickleIce =
       sdputils.verifySdp(test._remote_answer, "answer",
                          test._offer_constraints, test._offer_options,
                          test.testOptions);
   },
 
+  function PC_LOCAL_CHECK_CAN_TRICKLE_SYNC(test) {
+    is(test.pcLocal._pc.canTrickleIceCandidates, true,
+       "Local thinks that remote can trickle");
+  },
+
   function PC_LOCAL_WAIT_FOR_ICE_CONNECTED(test) {
     return waitForIceConnected(test, test.pcLocal);
   },
 
   function PC_REMOTE_WAIT_FOR_ICE_CONNECTED(test) {
     return waitForIceConnected(test, test.pcRemote);
   },
 
--- a/dom/media/tests/mochitest/test_peerConnection_remoteRollback.html
+++ b/dom/media/tests/mochitest/test_peerConnection_remoteRollback.html
@@ -10,41 +10,45 @@
     bug: "952145",
     title: "Rollback remote offer"
   });
 
   var test;
   runNetworkTest(function (options) {
     test = new PeerConnectionTest(options);
     test.setMediaConstraints([{audio: true}], [{audio: true}]);
-    test.chain.removeAfter('PC_REMOTE_SET_REMOTE_DESCRIPTION');
+    test.chain.removeAfter('PC_REMOTE_CHECK_CAN_TRICKLE_SYNC');
     test.chain.append([
-        function PC_REMOTE_ROLLBACK(test) {
-          // We still haven't negotiated the tracks
-          test.pcRemote.expectNegotiationNeeded();
-          return test.setRemoteDescription(
-              test.pcRemote,
-              new RTCSessionDescription({ type: "rollback" }),
-              STABLE);
-        },
+      function PC_REMOTE_ROLLBACK(test) {
+        // We still haven't negotiated the tracks
+        test.pcRemote.expectNegotiationNeeded();
+        return test.setRemoteDescription(
+          test.pcRemote,
+          new RTCSessionDescription({ type: "rollback" }),
+          STABLE);
+      },
 
-        function PC_LOCAL_ROLLBACK(test) {
-          // We still haven't negotiated the tracks
-          test.pcLocal.expectNegotiationNeeded();
-          return test.setLocalDescription(
-              test.pcLocal,
-              new RTCSessionDescription({ type: "rollback", sdp: ""}),
-              STABLE);
-        },
+      function PC_REMOTE_CHECK_CAN_TRICKLE_REVERT_SYNC(test) {
+        is(test.pcRemote._pc.canTrickleIceCandidates, null,
+           "Remote canTrickleIceCandidates is reverted to null");
+      },
 
-        // Rolling back should shut down gathering
-        function PC_LOCAL_WAIT_FOR_END_OF_TRICKLE(test) {
-          return test.pcLocal.endOfTrickleIce;
-        },
+      function PC_LOCAL_ROLLBACK(test) {
+        // We still haven't negotiated the tracks
+        test.pcLocal.expectNegotiationNeeded();
+        return test.setLocalDescription(
+          test.pcLocal,
+          new RTCSessionDescription({ type: "rollback", sdp: ""}),
+          STABLE);
+      },
+
+      // Rolling back should shut down gathering
+      function PC_LOCAL_WAIT_FOR_END_OF_TRICKLE(test) {
+        return test.pcLocal.endOfTrickleIce;
+      },
     ]);
     test.chain.append(commandsPeerConnectionOfferAnswer);
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/webidl/RTCPeerConnection.webidl
+++ b/dom/webidl/RTCPeerConnection.webidl
@@ -1,15 +1,15 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  *
  * The origin of this IDL file is
- * http://dev.w3.org/2011/webrtc/editor/webrtc.html#idl-def-RTCPeerConnection
+ * http://w3c.github.io/webrtc-pc/#interface-definition
  */
 
 callback RTCSessionDescriptionCallback = void (RTCSessionDescription sdp);
 callback RTCPeerConnectionErrorCallback = void (DOMError error);
 callback VoidFunction = void ();
 callback RTCStatsCallback = void (RTCStatsReport report);
 
 enum RTCSignalingState {
@@ -98,18 +98,18 @@ interface RTCPeerConnection : EventTarge
   Promise<DOMString> getIdentityAssertion();
   Promise<RTCSessionDescription> createOffer (optional RTCOfferOptions options);
   Promise<RTCSessionDescription> createAnswer (optional RTCAnswerOptions options);
   Promise<void> setLocalDescription (RTCSessionDescription description);
   Promise<void> setRemoteDescription (RTCSessionDescription description);
   readonly attribute RTCSessionDescription? localDescription;
   readonly attribute RTCSessionDescription? remoteDescription;
   readonly attribute RTCSignalingState signalingState;
-  void updateIce (optional RTCConfiguration configuration);
   Promise<void> addIceCandidate (RTCIceCandidate candidate);
+  readonly attribute boolean? canTrickleIceCandidates;
   readonly attribute RTCIceGatheringState iceGatheringState;
   readonly attribute RTCIceConnectionState iceConnectionState;
   [Pref="media.peerconnection.identity.enabled"]
   readonly attribute Promise<RTCIdentityAssertion> peerIdentity;
   [Pref="media.peerconnection.identity.enabled"]
   readonly attribute DOMString? idpLoginUrl;
 
   [ChromeOnly]
--- a/testing/web-platform/meta/webrtc/rtcpeerconnection/rtcpeerconnection-idl.html.ini
+++ b/testing/web-platform/meta/webrtc/rtcpeerconnection/rtcpeerconnection-idl.html.ini
@@ -1,12 +1,12 @@
 [rtcpeerconnection-idl.html]
   type: testharness
   [RTCPeerConnection interface: attribute canTrickleIceCandidates]
-    expected: FAIL
+    expected: PASS
 
   [RTCPeerConnection interface: attribute onicegatheringstatechange]
     expected: FAIL
 
   [RTCPeerConnection interface: operation createOffer(RTCSessionDescriptionCallback,RTCPeerConnectionErrorCallback,RTCOfferOptions)]
     expected: FAIL
 
   [RTCPeerConnection interface: operation setLocalDescription(RTCSessionDescription,VoidFunction,RTCPeerConnectionErrorCallback)]
@@ -71,17 +71,17 @@
 
   [RTCPeerConnection interface: pc must inherit property "iceGatheringState" with the proper type (12)]
     expected: FAIL
 
   [RTCPeerConnection interface: pc must inherit property "iceConnectionState" with the proper type (13)]
     expected: FAIL
 
   [RTCPeerConnection interface: pc must inherit property "canTrickleIceCandidates" with the proper type (14)]
-    expected: FAIL
+    expected: PASS
 
   [RTCPeerConnection interface: pc must inherit property "setConfiguration" with the proper type (16)]
     expected: FAIL
 
   [RTCPeerConnection interface: calling setConfiguration(RTCConfiguration) on pc with too few arguments must throw TypeError]
     expected: FAIL
 
   [RTCPeerConnection interface: pc must inherit property "onnegotiationneeded" with the proper type (18)]
@@ -113,17 +113,17 @@
 
   [RTCPeerConnection interface: operation createAnswer()]
     expected: FAIL
 
   [RTCPeerConnection interface: operation updateIce(RTCConfiguration)]
     expected: FAIL
 
   [RTCPeerConnection interface: attribute canTrickleIceCandidates]
-    expected: FAIL
+    expected: PASS
 
   [RTCPeerConnection interface: attribute onicegatheringstatechange]
     expected: FAIL
 
   [RTCPeerConnection interface: operation createOffer(RTCSessionDescriptionCallback,RTCPeerConnectionErrorCallback,RTCOfferOptions)]
     expected: FAIL
 
   [RTCPeerConnection interface: operation setLocalDescription(RTCSessionDescription,VoidFunction,RTCPeerConnectionErrorCallback)]
@@ -167,17 +167,17 @@
 
   [RTCPeerConnection interface: pc must inherit property "iceGatheringState" with the proper type (9)]
     expected: FAIL
 
   [RTCPeerConnection interface: pc must inherit property "iceConnectionState" with the proper type (10)]
     expected: FAIL
 
   [RTCPeerConnection interface: pc must inherit property "canTrickleIceCandidates" with the proper type (11)]
-    expected: FAIL
+    expected: PASS
 
   [RTCPeerConnection interface: pc must inherit property "onnegotiationneeded" with the proper type (14)]
     expected: FAIL
 
   [RTCPeerConnection interface: pc must inherit property "onicecandidate" with the proper type (15)]
     expected: FAIL
 
   [RTCPeerConnection interface: pc must inherit property "onsignalingstatechange" with the proper type (16)]