Bug 1209744 - Implement canTrickleIceCandidates attribute, r=bwc,khuey
authorMartin Thomson <martin.thomson@gmail.com>
Sat, 20 Feb 2016 16:48:20 -0800
changeset 285004 d034fcc088310cfb3dbcd9b02bb1468725207c66
parent 285003 32dc11f5f92e014d30c18d3a4045d5f8363694c2
child 285005 71da07ec8ce3dc9bac895a1c999de35439b0ab91
push id30018
push userphilringnalda@gmail.com
push dateMon, 22 Feb 2016 03:49:51 +0000
treeherdermozilla-central@08d7af2bfe30 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbwc, khuey
bugs1209744
milestone47.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 1209744 - Implement canTrickleIceCandidates attribute, r=bwc,khuey MozReview-Commit-ID: 838B1zSIGLj
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/media/tests/mochitest/test_peerConnection_syncSetDescription.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) {
@@ -300,16 +309,21 @@ var commandsPeerConnectionOfferAnswer = 
   function PC_REMOTE_SET_REMOTE_DESCRIPTION(test) {
     return test.setRemoteDescription(test.pcRemote, test._local_offer, HAVE_REMOTE_OFFER)
       .then(() => {
         is(test.pcRemote.signalingState, HAVE_REMOTE_OFFER,
            "signalingState after remote setRemoteDescription is 'have-remote-offer'");
       });
   },
 
+  function PC_REMOTE_CHECK_CAN_TRICKLE_SYNC(test) {
+    is(test.pcRemote._pc.canTrickleIceCandidates, true,
+       "Remote thinks that local can trickle");
+  },
+
   function PC_LOCAL_SANE_LOCAL_SDP(test) {
     test.pcLocal.localRequiresTrickleIce =
       sdputils.verifySdp(test._local_offer, "offer",
                          test._offer_constraints, test._offer_options,
                          test.testOptions);
   },
 
   function PC_REMOTE_SANE_REMOTE_SDP(test) {
@@ -371,16 +385,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/media/tests/mochitest/test_peerConnection_syncSetDescription.html
+++ b/dom/media/tests/mochitest/test_peerConnection_syncSetDescription.html
@@ -36,16 +36,18 @@ function PC_LOCAL_SET_REMOTE_DESCRIPTION
     generateErrorCallback("pcLocal._pc.setRemoteDescription() sync failed"));
 }
 
 runNetworkTest(() => {
   var test = new PeerConnectionTest();
   test.setMediaConstraints([{video: true}], [{video: true}]);
   test.chain.replace("PC_LOCAL_SET_LOCAL_DESCRIPTION", PC_LOCAL_SET_LOCAL_DESCRIPTION_SYNC);
   test.chain.replace("PC_REMOTE_SET_REMOTE_DESCRIPTION", PC_REMOTE_SET_REMOTE_DESCRIPTION_SYNC);
+  test.chain.remove("PC_REMOTE_CHECK_CAN_TRICKLE_SYNC");
   test.chain.replace("PC_REMOTE_SET_LOCAL_DESCRIPTION", PC_REMOTE_SET_LOCAL_DESCRIPTION_SYNC);
   test.chain.replace("PC_LOCAL_SET_REMOTE_DESCRIPTION", PC_LOCAL_SET_REMOTE_DESCRIPTION_SYNC);
+  test.chain.remove("PC_LOCAL_CHECK_CAN_TRICKLE_SYNC");
   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)]