Bug 1490658: Support RTCIceCandidate.usernameFragment. r=mjf,smaug
authorByron Campen [:bwc] <docfaraday@gmail.com>
Fri, 08 Mar 2019 15:24:27 +0000
changeset 463486 034dbb475f6dc7bb2f839a6ead75d4fd0abf8e2a
parent 463485 c97ec09c6d152ae6401554b1d55e39b928cc74dd
child 463487 5a14eec92d6e422f452505dbffbf84862fc74af9
push id35686
push userbtara@mozilla.com
push dateTue, 12 Mar 2019 09:50:48 +0000
treeherdermozilla-central@7196b821847c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmjf, smaug
bugs1490658
milestone67.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 1490658: Support RTCIceCandidate.usernameFragment. r=mjf,smaug Differential Revision: https://phabricator.services.mozilla.com/D21803
dom/media/PeerConnection.jsm
dom/media/webrtc/MediaTransportParent.h
dom/media/webrtc/PMediaTransport.ipdl
dom/media/webrtc/WebrtcIPCTraits.h
dom/webidl/PeerConnectionImpl.webidl
dom/webidl/PeerConnectionObserver.webidl
dom/webidl/RTCIceCandidate.webidl
media/mtransport/nricectx.cpp
media/mtransport/nricemediastream.cpp
media/mtransport/nricemediastream.h
media/mtransport/test/ice_unittest.cpp
media/mtransport/test/transport_unittests.cpp
media/webrtc/signaling/gtest/mediapipeline_unittest.cpp
media/webrtc/signaling/src/common/CandidateInfo.h
media/webrtc/signaling/src/peerconnection/MediaTransportHandler.cpp
media/webrtc/signaling/src/peerconnection/MediaTransportHandler.h
media/webrtc/signaling/src/peerconnection/MediaTransportHandlerIPC.cpp
media/webrtc/signaling/src/peerconnection/MediaTransportHandlerIPC.h
media/webrtc/signaling/src/peerconnection/MediaTransportParent.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
testing/web-platform/meta/webrtc/RTCIceCandidate-constructor.html.ini
--- a/dom/media/PeerConnection.jsm
+++ b/dom/media/PeerConnection.jsm
@@ -1103,23 +1103,24 @@ class RTCPeerConnection {
     if (cand === null) {
       throw new this._win.DOMException(
         "Empty candidate can not be added.",
         "TypeError");
     }
     return this._auto(onSucc, onErr, () => cand && this._addIceCandidate(cand));
   }
 
-  async _addIceCandidate({ candidate, sdpMid, sdpMLineIndex }) {
+  async _addIceCandidate({ candidate, sdpMid, sdpMLineIndex, usernameFragment }) {
     this._checkClosed();
     return this._chain(() => {
       return new Promise((resolve, reject) => {
         this._onAddIceCandidateSuccess = resolve;
         this._onAddIceCandidateError = reject;
-        this._impl.addIceCandidate(candidate, sdpMid || "", sdpMLineIndex);
+        this._impl.addIceCandidate(
+          candidate, sdpMid || "", usernameFragment || "", sdpMLineIndex);
       });
     });
   }
 
   addStream(stream) {
     stream.getTracks().forEach(track => this.addTrack(track, stream));
   }
 
@@ -1675,23 +1676,23 @@ class PeerConnectionObserver {
   onAddIceCandidateSuccess() {
     this._dompc._onAddIceCandidateSuccess();
   }
 
   onAddIceCandidateError(code, message) {
     this._dompc._onAddIceCandidateError(this.newError(message, code));
   }
 
-  onIceCandidate(sdpMLineIndex, sdpMid, candidate) {
+  onIceCandidate(sdpMLineIndex, sdpMid, candidate, ufrag) {
     let win = this._dompc._win;
     if (candidate) {
       if (candidate.includes(" typ relay ")) {
         this._dompc._iceGatheredRelayCandidates = true;
       }
-      candidate = new win.RTCIceCandidate({ candidate, sdpMid, sdpMLineIndex });
+      candidate = new win.RTCIceCandidate({ candidate, sdpMid, sdpMLineIndex, ufrag });
     } else {
       candidate = null;
     }
     this.dispatchEvent(new win.RTCPeerConnectionIceEvent("icecandidate",
                                                          { candidate }));
   }
 
   // This method is primarily responsible for updating iceConnectionState.
--- a/dom/media/webrtc/MediaTransportParent.h
+++ b/dom/media/webrtc/MediaTransportParent.h
@@ -41,17 +41,18 @@ class MediaTransportParent : public dom:
   mozilla::ipc::IPCResult RecvRemoveTransportsExcept(
       const StringVector& transportIds);
   mozilla::ipc::IPCResult RecvStartIceChecks(const bool& isControlling,
                                              const bool& isOfferer,
                                              const StringVector& iceOptions);
   mozilla::ipc::IPCResult RecvSendPacket(const string& transportId,
                                          const MediaPacket& packet);
   mozilla::ipc::IPCResult RecvAddIceCandidate(const string& transportId,
-                                              const string& candidate);
+                                              const string& candidate,
+                                              const string& ufrag);
   mozilla::ipc::IPCResult RecvUpdateNetworkState(const bool& online);
   mozilla::ipc::IPCResult RecvGetIceStats(
       const string& transportId, const double& now,
       const RTCStatsReportInternal& reportIn, GetIceStatsResolver&& aResolve);
 
   void ActorDestroy(ActorDestroyReason aWhy);
 
  private:
--- a/dom/media/webrtc/PMediaTransport.ipdl
+++ b/dom/media/webrtc/PMediaTransport.ipdl
@@ -71,17 +71,18 @@ parent:
 
   async StartIceChecks(bool isControlling,
                        bool isOfferer,
                        StringVector iceOptions);
 
   async SendPacket(string transportId, MediaPacket packet);
 
   async AddIceCandidate(string transportId,
-                        string candidate);
+                        string candidate,
+                        string ufrag);
 
   async UpdateNetworkState(bool online);
 
   async GetIceStats(string transportId,
                     double now,
                     RTCStatsReportInternal reportIn) returns (MovableRTCStatsReportInternal reportOut);
 
 child:
--- a/dom/media/webrtc/WebrtcIPCTraits.h
+++ b/dom/media/webrtc/WebrtcIPCTraits.h
@@ -151,17 +151,17 @@ static bool ReadParams(const Message* aM
                         MOZ_FOR_EACH_SEPARATED(ACCESS_PARAM_FIELD, (, ), (), \
                                                (__VA_ARGS__)));              \
     }                                                                        \
   };
 
 DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCIceServer, mCredential,
                                   mCredentialType, mUrl, mUrls, mUsername)
 
-DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::CandidateInfo, mCandidate,
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::CandidateInfo, mCandidate, mUfrag,
                                   mDefaultHostRtp, mDefaultPortRtp,
                                   mDefaultHostRtcp, mDefaultPortRtcp)
 
 DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::DtlsDigest, algorithm_, value_)
 
 }  // namespace IPC
 
 #endif  // _WEBRTC_IPC_TRAITS_H_
--- a/dom/webidl/PeerConnectionImpl.webidl
+++ b/dom/webidl/PeerConnectionImpl.webidl
@@ -86,17 +86,20 @@ interface PeerConnectionImpl  {
                          boolean sending);
 
   /* As the ICE candidates roll in this one should be called each time
    * in order to keep the candidate list up-to-date for the next SDP-related
    * call PeerConnectionImpl does not parse ICE candidates, just sticks them
    * into the SDP.
    */
   [Throws]
-  void addIceCandidate(DOMString candidate, DOMString mid, unsigned short? level);
+  void addIceCandidate(DOMString candidate,
+                       DOMString mid,
+                       DOMString ufrag,
+                       unsigned short? level);
 
   /* Shuts down threads, deletes state */
   [Throws]
   void close();
 
   /* Notify DOM window if this plugin crash is ours. */
   boolean pluginCrash(unsigned long long pluginId, DOMString name);
 
--- a/dom/webidl/PeerConnectionObserver.webidl
+++ b/dom/webidl/PeerConnectionObserver.webidl
@@ -17,17 +17,17 @@ interface PeerConnectionObserver
   void onCreateAnswerSuccess(DOMString answer);
   void onCreateAnswerError(unsigned long name, DOMString message);
   void onSetLocalDescriptionSuccess();
   void onSetRemoteDescriptionSuccess();
   void onSetLocalDescriptionError(unsigned long name, DOMString message);
   void onSetRemoteDescriptionError(unsigned long name, DOMString message);
   void onAddIceCandidateSuccess();
   void onAddIceCandidateError(unsigned long name, DOMString message);
-  void onIceCandidate(unsigned short level, DOMString mid, DOMString candidate);
+  void onIceCandidate(unsigned short level, DOMString mid, DOMString candidate, DOMString ufrag);
 
   /* Stats callbacks */
   void onGetStatsSuccess(optional RTCStatsReportInternal report);
   void onGetStatsError(unsigned long name, DOMString message);
 
   /* Data channel callbacks */
   void notifyDataChannel(RTCDataChannel channel);
 
--- a/dom/webidl/RTCIceCandidate.webidl
+++ b/dom/webidl/RTCIceCandidate.webidl
@@ -6,20 +6,21 @@
  * The origin of this IDL file is
  * http://dev.w3.org/2011/webrtc/editor/webrtc.html#idl-def-RTCIceCandidate
  */
 
 dictionary RTCIceCandidateInit {
   required DOMString candidate;
   DOMString? sdpMid = null;
   unsigned short? sdpMLineIndex = null;
+  DOMString? usernameFragment = null;
 };
 
 [Pref="media.peerconnection.enabled",
  JSImplementation="@mozilla.org/dom/rtcicecandidate;1",
  Constructor(RTCIceCandidateInit candidateInitDict)]
 interface RTCIceCandidate {
   attribute DOMString       candidate;
   attribute DOMString?      sdpMid;
   attribute unsigned short? sdpMLineIndex;
-
+  attribute DOMString? usernameFragment;
   [Default] object toJSON();
 };
--- a/media/mtransport/nricectx.cpp
+++ b/media/mtransport/nricectx.cpp
@@ -432,17 +432,17 @@ void NrIceCtx::trickle_cb(void *arg, nr_
   int r = nr_ice_format_candidate_attribute(candidate, candidate_str,
                                             sizeof(candidate_str));
   MOZ_ASSERT(!r);
   if (r) return;
 
   MOZ_MTLOG(ML_INFO, "NrIceCtx(" << ctx->name_ << "): trickling candidate "
                                  << candidate_str);
 
-  s->SignalCandidate(s, candidate_str);
+  s->SignalCandidate(s, candidate_str, stream->ufrag);
 }
 
 void NrIceCtx::InitializeGlobals(bool allow_loopback, bool tcp_enabled,
                                  bool allow_link_local) {
   RLogConnector::CreateInstance();
   // Initialize the crypto callbacks and logging stuff
   if (!initialized) {
     NR_reg_init(NR_REG_MODE_LOCAL);
--- a/media/mtransport/nricemediastream.cpp
+++ b/media/mtransport/nricemediastream.cpp
@@ -264,27 +264,30 @@ nsresult NrIceMediaStream::SetIceCredent
     return NS_ERROR_FAILURE;
   }
 
   state_ = ICE_CONNECTING;
   return NS_OK;
 }
 
 // Parse trickle ICE candidate
-nsresult NrIceMediaStream::ParseTrickleCandidate(const std::string& candidate) {
-  // TODO(bug 1490658): This needs to take ufrag into account. For now, trickle
-  // candidates will land on the most recently-created ICE stream.
-  int r;
+nsresult NrIceMediaStream::ParseTrickleCandidate(const std::string& candidate,
+                                                 const std::string& ufrag) {
+  nr_ice_media_stream* stream = GetStreamForRemoteUfrag(ufrag);
+  if (!stream) {
+    return NS_ERROR_FAILURE;
+  }
 
   MOZ_MTLOG(ML_DEBUG, "NrIceCtx(" << ctx_->label << ")/STREAM(" << name()
                                   << ") : parsing trickle candidate "
                                   << candidate);
 
-  r = nr_ice_peer_ctx_parse_trickle_candidate(
-      ctx_peer_, stream_, const_cast<char*>(candidate.c_str()));
+  int r = nr_ice_peer_ctx_parse_trickle_candidate(
+      ctx_peer_, stream, const_cast<char*>(candidate.c_str()));
+
   if (r) {
     if (r == R_ALREADY) {
       MOZ_MTLOG(ML_INFO, "Trickle candidate is redundant for stream '"
                              << name_
                              << "' because it is completed: " << candidate);
     } else if (r == R_REJECTED) {
       MOZ_MTLOG(ML_INFO,
                 "Trickle candidate is ignored for stream '"
@@ -665,9 +668,31 @@ void NrIceMediaStream::CloseStream(nr_ic
 }
 
 void NrIceMediaStream::DeferredCloseOldStream(const nr_ice_media_stream* old) {
   if (old == old_stream_) {
     CloseStream(&old_stream_);
   }
 }
 
+nr_ice_media_stream* NrIceMediaStream::GetStreamForRemoteUfrag(
+    const std::string& aUfrag) {
+  if (aUfrag.empty()) {
+    return stream_;
+  }
+
+  nr_ice_media_stream* peer_stream = nullptr;
+
+  if (!nr_ice_peer_ctx_find_pstream(ctx_peer_, stream_, &peer_stream) &&
+      aUfrag == peer_stream->ufrag) {
+    return stream_;
+  }
+
+  if (old_stream_ &&
+      !nr_ice_peer_ctx_find_pstream(ctx_peer_, old_stream_, &peer_stream) &&
+      aUfrag == peer_stream->ufrag) {
+    return old_stream_;
+  }
+
+  return nullptr;
+}
+
 }  // namespace mozilla
--- a/media/mtransport/nricemediastream.h
+++ b/media/mtransport/nricemediastream.h
@@ -147,17 +147,18 @@ class NrIceMediaStream {
 
   // Get all candidate pairs, whether in the check list or triggered check
   // queue, in priority order. |out_pairs| is cleared before being filled.
   nsresult GetCandidatePairs(std::vector<NrIceCandidatePair>* out_pairs) const;
 
   nsresult GetDefaultCandidate(int component, NrIceCandidate* candidate) const;
 
   // Parse trickle ICE candidate
-  nsresult ParseTrickleCandidate(const std::string& candidate);
+  nsresult ParseTrickleCandidate(const std::string& candidate,
+                                 const std::string& ufrag);
 
   // Disable a component
   nsresult DisableComponent(int component);
 
   // Get the candidate pair currently active. It's the
   // caller's responsibility to free these.
   nsresult GetActivePair(int component, UniquePtr<NrIceCandidate>* local,
                          UniquePtr<NrIceCandidate>* remote);
@@ -185,33 +186,34 @@ class NrIceMediaStream {
   // might be holding RefPtrs but we want those writes to fail once
   // the context has been destroyed.
   void Close();
 
   // So the receiver of SignalCandidate can determine which transport
   // the candidate belongs to.
   const std::string& GetId() const { return id_; }
 
-  sigslot::signal2<NrIceMediaStream*, const std::string&>
+  sigslot::signal3<NrIceMediaStream*, const std::string&, const std::string&>
       SignalCandidate;  // A new ICE candidate:
 
   sigslot::signal1<NrIceMediaStream*> SignalReady;   // Candidate pair ready.
   sigslot::signal1<NrIceMediaStream*> SignalFailed;  // Candidate pair failed.
   sigslot::signal4<NrIceMediaStream*, int, const unsigned char*, int>
       SignalPacketReceived;  // Incoming packet
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NrIceMediaStream)
 
  private:
   ~NrIceMediaStream();
 
   DISALLOW_COPY_ASSIGN(NrIceMediaStream);
 
   void CloseStream(nr_ice_media_stream** stream);
   void DeferredCloseOldStream(const nr_ice_media_stream* old);
+  nr_ice_media_stream* GetStreamForRemoteUfrag(const std::string& ufrag);
 
   State state_;
   nr_ice_ctx* ctx_;
   nr_ice_peer_ctx* ctx_peer_;
   const std::string name_;
   const size_t components_;
   nr_ice_media_stream* stream_;
   nr_ice_media_stream* old_stream_;
--- a/media/mtransport/test/ice_unittest.cpp
+++ b/media/mtransport/test/ice_unittest.cpp
@@ -297,20 +297,22 @@ class IceCandidatePairCompare {
 };
 
 class IceTestPeer;
 
 class SchedulableTrickleCandidate {
  public:
   SchedulableTrickleCandidate(IceTestPeer* peer, size_t stream,
                               const std::string& candidate,
+                              const std::string& ufrag,
                               MtransportTestUtils* utils)
       : peer_(peer),
         stream_(stream),
         candidate_(candidate),
+        ufrag_(ufrag),
         timer_handle_(nullptr),
         test_utils_(utils) {}
 
   ~SchedulableTrickleCandidate() {
     if (timer_handle_) NR_async_timer_cancel(timer_handle_);
   }
 
   void Schedule(unsigned int ms) {
@@ -345,16 +347,17 @@ class SchedulableTrickleCandidate {
   bool IsRelay() const {
     return candidate_.find("typ relay") != std::string::npos;
   }
 
  private:
   IceTestPeer* peer_;
   size_t stream_;
   std::string candidate_;
+  std::string ufrag_;
   void* timer_handle_;
   MtransportTestUtils* test_utils_;
 
   DISALLOW_COPY_ASSIGN(SchedulableTrickleCandidate);
 };
 
 class IceTestPeer : public sigslot::has_slots<> {
  public:
@@ -786,31 +789,32 @@ class IceTestPeer : public sigslot::has_
   std::vector<SchedulableTrickleCandidate*>& ControlTrickle(size_t stream) {
     std::cerr << "Doing controlled trickle for stream " << stream << std::endl;
 
     std::vector<std::string> attributes = remote_->GetAttributes(stream);
 
     for (const auto& attribute : attributes) {
       if (attribute.find("candidate:") != std::string::npos) {
         controlled_trickle_candidates_[stream].push_back(
-            new SchedulableTrickleCandidate(this, stream, attribute,
+            new SchedulableTrickleCandidate(this, stream, attribute, "",
                                             test_utils_));
       }
     }
 
     return controlled_trickle_candidates_[stream];
   }
 
-  nsresult TrickleCandidate_s(const std::string& candidate, size_t index) {
+  nsresult TrickleCandidate_s(const std::string& candidate,
+                              const std::string& ufrag, size_t index) {
     auto stream = GetStream_s(index);
     if (!stream) {
       // stream might have gone away before the trickle timer popped
       return NS_OK;
     }
-    return stream->ParseTrickleCandidate(candidate);
+    return stream->ParseTrickleCandidate(candidate, ufrag);
   }
 
   void DumpCandidate(std::string which, const NrIceCandidate& cand) {
     std::string type;
     std::string tcp_type;
 
     std::string addr;
     int port;
@@ -974,34 +978,35 @@ class IceTestPeer : public sigslot::has_
       for (const auto& attribute : attributes) {
         std::cerr << attribute << std::endl;
       }
     }
     std::cerr << std::endl;
   }
 
   void CandidateInitialized(NrIceMediaStream* stream,
-                            const std::string& raw_candidate) {
+                            const std::string& raw_candidate,
+                            const std::string& ufrag) {
     std::string candidate(FilterCandidate(raw_candidate));
     if (candidate.empty()) {
       return;
     }
     std::cerr << "Candidate for stream " << stream->name()
               << " initialized: " << candidate << std::endl;
     candidates_[stream->name()].push_back(candidate);
 
     // If we are connected, then try to trickle to the other side.
     if (remote_ && remote_->remote_ && (trickle_mode_ != TRICKLE_SIMULATE)) {
       // first, find the index of the stream we've been given so
       // we can get the corresponding stream on the remote side
       for (size_t i = 0; i < stream_counter_; ++i) {
         if (GetStream_s(i) == stream) {
           ASSERT_GT(remote_->stream_counter_, i);
           nsresult res =
-              remote_->GetStream_s(i)->ParseTrickleCandidate(candidate);
+              remote_->GetStream_s(i)->ParseTrickleCandidate(candidate, ufrag);
           ASSERT_TRUE(NS_SUCCEEDED(res));
           return;
         }
       }
       ADD_FAILURE() << "No matching stream found for " << stream;
     }
   }
 
@@ -1211,17 +1216,17 @@ class IceTestPeer : public sigslot::has_
 
   void SetCandidateFilter(CandidateFilter filter) {
     candidate_filter_ = filter;
   }
 
   void ParseCandidate_s(size_t i, const std::string& candidate) {
     auto media_stream = GetStream_s(i);
     ASSERT_TRUE(media_stream.get()) << "No such stream " << i;
-    media_stream->ParseTrickleCandidate(candidate);
+    media_stream->ParseTrickleCandidate(candidate, "");
   }
 
   void ParseCandidate(size_t i, const std::string& candidate) {
     test_utils_->sts_target()->Dispatch(
         WrapRunnable(this, &IceTestPeer::ParseCandidate_s, i, candidate),
         NS_DISPATCH_SYNC);
   }
 
@@ -1362,17 +1367,17 @@ class IceTestPeer : public sigslot::has_
   TrickleMode trickle_mode_;
   bool simulate_ice_lite_;
   RefPtr<mozilla::TestNat> nat_;
   MtransportTestUtils* test_utils_;
 };
 
 void SchedulableTrickleCandidate::Trickle() {
   timer_handle_ = nullptr;
-  nsresult res = peer_->TrickleCandidate_s(candidate_, stream_);
+  nsresult res = peer_->TrickleCandidate_s(candidate_, ufrag_, stream_);
   ASSERT_TRUE(NS_SUCCEEDED(res));
 }
 
 class WebRtcIceGatherTest : public StunTest {
  public:
   void SetUp() override {
     StunTest::SetUp();
 
@@ -2933,35 +2938,35 @@ void AddNonPairableCandidates(
     std::vector<SchedulableTrickleCandidate*>& candidates, IceTestPeer* peer,
     size_t stream, int net_type, MtransportTestUtils* test_utils_) {
   for (int i = 1; i < 5; i++) {
     if (net_type == i) continue;
     switch (i) {
       case 1:
         candidates.push_back(new SchedulableTrickleCandidate(
             peer, stream,
-            "candidate:0 1 UDP 2113601790 10.0.0.1 12345 typ host",
+            "candidate:0 1 UDP 2113601790 10.0.0.1 12345 typ host", "",
             test_utils_));
         break;
       case 2:
         candidates.push_back(new SchedulableTrickleCandidate(
             peer, stream,
-            "candidate:0 1 UDP 2113601791 172.16.1.1 12345 typ host",
+            "candidate:0 1 UDP 2113601791 172.16.1.1 12345 typ host", "",
             test_utils_));
         break;
       case 3:
         candidates.push_back(new SchedulableTrickleCandidate(
             peer, stream,
-            "candidate:0 1 UDP 2113601792 192.168.0.1 12345 typ host",
+            "candidate:0 1 UDP 2113601792 192.168.0.1 12345 typ host", "",
             test_utils_));
         break;
       case 4:
         candidates.push_back(new SchedulableTrickleCandidate(
             peer, stream,
-            "candidate:0 1 UDP 2113601793 100.64.1.1 12345 typ host",
+            "candidate:0 1 UDP 2113601793 100.64.1.1 12345 typ host", "",
             test_utils_));
         break;
       default:
         UNIMPLEMENTED;
     }
   }
 
   for (auto i = candidates.rbegin(); i != candidates.rend(); ++i) {
--- a/media/mtransport/test/transport_unittests.cpp
+++ b/media/mtransport/test/transport_unittests.cpp
@@ -628,18 +628,20 @@ class TransportTestPeer : public sigslot
   void ConnectIce(TransportTestPeer* peer) {
     peer_ = peer;
 
     // If gathering is already complete, push the candidates over
     if (gathering_complete_) GatheringComplete();
   }
 
   // New candidate
-  void GotCandidate(NrIceMediaStream* stream, const std::string& candidate) {
-    std::cerr << "Got candidate " << candidate << std::endl;
+  void GotCandidate(NrIceMediaStream* stream, const std::string& candidate,
+                    const std::string& ufrag) {
+    std::cerr << "Got candidate " << candidate << " (ufrag=" << ufrag << ")"
+              << std::endl;
   }
 
   void GatheringStateChange(NrIceCtx* ctx, NrIceCtx::GatheringState state) {
     (void)ctx;
     if (state == NrIceCtx::ICE_CTX_GATHER_COMPLETE) {
       GatheringComplete();
     }
   }
--- a/media/webrtc/signaling/gtest/mediapipeline_unittest.cpp
+++ b/media/webrtc/signaling/gtest/mediapipeline_unittest.cpp
@@ -193,17 +193,18 @@ class LoopbackTransport : public MediaTr
 
   void RemoveTransportsExcept(
       const std::set<std::string>& aTransportIds) override {}
 
   void StartIceChecks(bool aIsControlling, bool aIsOfferer,
                       const std::vector<std::string>& aIceOptions) override {}
 
   void AddIceCandidate(const std::string& aTransportId,
-                       const std::string& aCandidate) override {}
+                       const std::string& aCandidate,
+                       const std::string& aUfrag) override {}
 
   void UpdateNetworkState(bool aOnline) override {}
 
   RefPtr<StatsPromise> GetIceStats(
       const std::string& aTransportId, DOMHighResTimeStamp aNow,
       std::unique_ptr<dom::RTCStatsReportInternal>&& aReport) override {
     return nullptr;
   }
--- a/media/webrtc/signaling/src/common/CandidateInfo.h
+++ b/media/webrtc/signaling/src/common/CandidateInfo.h
@@ -8,16 +8,17 @@
 #include <string>
 #include <cstdint>
 
 namespace mozilla {
 
 // This is used both by IPDL code, and by signaling code.
 struct CandidateInfo {
   std::string mCandidate;
+  std::string mUfrag;
   std::string mDefaultHostRtp;
   uint16_t mDefaultPortRtp = 0;
   std::string mDefaultHostRtcp;
   uint16_t mDefaultPortRtcp = 0;
 };
 
 }  // namespace mozilla
 
--- a/media/webrtc/signaling/src/peerconnection/MediaTransportHandler.cpp
+++ b/media/webrtc/signaling/src/peerconnection/MediaTransportHandler.cpp
@@ -92,17 +92,18 @@ class MediaTransportHandlerSTS : public 
 
   void RemoveTransportsExcept(
       const std::set<std::string>& aTransportIds) override;
 
   void StartIceChecks(bool aIsControlling, bool aIsOfferer,
                       const std::vector<std::string>& aIceOptions) override;
 
   void AddIceCandidate(const std::string& aTransportId,
-                       const std::string& aCandidate) override;
+                       const std::string& aCandidate,
+                       const std::string& aUfrag) override;
 
   void UpdateNetworkState(bool aOnline) override;
 
   void SendPacket(const std::string& aTransportId,
                   MediaPacket&& aPacket) override;
 
   RefPtr<StatsPromise> GetIceStats(
       const std::string& aTransportId, DOMHighResTimeStamp aNow,
@@ -130,17 +131,18 @@ class MediaTransportHandlerSTS : public 
   using MediaTransportHandler::OnRtcpStateChange;
   using MediaTransportHandler::OnStateChange;
 
   void OnGatheringStateChange(NrIceCtx* aIceCtx,
                               NrIceCtx::GatheringState aState);
   void OnConnectionStateChange(NrIceCtx* aIceCtx,
                                NrIceCtx::ConnectionState aState);
   void OnCandidateFound(NrIceMediaStream* aStream,
-                        const std::string& aCandidate);
+                        const std::string& aCandidate,
+                        const std::string& aUfrag);
   void OnStateChange(TransportLayer* aLayer, TransportLayer::State);
   void OnRtcpStateChange(TransportLayer* aLayer, TransportLayer::State);
   void PacketReceived(TransportLayer* aLayer, MediaPacket& aPacket);
   void EncryptedPacketSending(TransportLayer* aLayer, MediaPacket& aPacket);
   RefPtr<TransportFlow> GetTransportFlow(const std::string& aTransportId,
                                          bool aIsRtcp) const;
   void GetIceStats(const NrIceMediaStream& aStream, DOMHighResTimeStamp aNow,
                    dom::RTCStatsReportInternal* aReport) const;
@@ -614,34 +616,35 @@ void MediaTransportHandlerSTS::StartIceC
   rv = mIceCtx->StartChecks(aIsOfferer);
   if (NS_FAILED(rv)) {
     CSFLogError(LOGTAG, "%s: couldn't start checks", __FUNCTION__);
     return;
   }
 }
 
 void MediaTransportHandlerSTS::AddIceCandidate(const std::string& aTransportId,
-                                               const std::string& aCandidate) {
+                                               const std::string& aCandidate,
+                                               const std::string& aUfrag) {
   if (!mStsThread->IsOnCurrentThread()) {
     mStsThread->Dispatch(
         WrapRunnable(RefPtr<MediaTransportHandlerSTS>(this),
                      &MediaTransportHandlerSTS::AddIceCandidate, aTransportId,
-                     aCandidate),
+                     aCandidate, aUfrag),
         NS_DISPATCH_NORMAL);
     return;
   }
 
   RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aTransportId));
   if (!stream) {
     CSFLogError(LOGTAG, "No ICE stream for candidate with transport id %s: %s",
                 aTransportId.c_str(), aCandidate.c_str());
     return;
   }
 
-  nsresult rv = stream->ParseTrickleCandidate(aCandidate);
+  nsresult rv = stream->ParseTrickleCandidate(aCandidate, aUfrag);
   if (NS_FAILED(rv)) {
     CSFLogError(LOGTAG,
                 "Couldn't process ICE candidate with transport id %s: "
                 "%s",
                 aTransportId.c_str(), aCandidate.c_str());
   }
 }
 
@@ -1146,17 +1149,19 @@ static mozilla::dom::PCImplIceGatheringS
   }
   MOZ_CRASH();
 }
 
 void MediaTransportHandlerSTS::OnGatheringStateChange(
     NrIceCtx* aIceCtx, NrIceCtx::GatheringState aState) {
   if (aState == NrIceCtx::ICE_CTX_GATHER_COMPLETE) {
     for (const auto& stream : mIceCtx->GetStreams()) {
-      OnCandidateFound(stream, "");
+      // TODO: Do we need to set the ufrag here? Do we need to fire more than
+      // once if we have an ICE restart in progress?
+      OnCandidateFound(stream, "", "");
     }
   }
   OnGatheringStateChange(toDomIceGatheringState(aState));
 }
 
 static mozilla::dom::PCImplIceConnectionState toDomIceConnectionState(
     NrIceCtx::ConnectionState aState) {
   switch (aState) {
@@ -1180,19 +1185,21 @@ static mozilla::dom::PCImplIceConnection
 
 void MediaTransportHandlerSTS::OnConnectionStateChange(
     NrIceCtx* aIceCtx, NrIceCtx::ConnectionState aState) {
   OnConnectionStateChange(toDomIceConnectionState(aState));
 }
 
 // The stuff below here will eventually go into the MediaTransportChild class
 void MediaTransportHandlerSTS::OnCandidateFound(NrIceMediaStream* aStream,
-                                                const std::string& aCandidate) {
+                                                const std::string& aCandidate,
+                                                const std::string& aUfrag) {
   CandidateInfo info;
   info.mCandidate = aCandidate;
+  info.mUfrag = aUfrag;
   NrIceCandidate defaultRtpCandidate;
   NrIceCandidate defaultRtcpCandidate;
   nsresult rv = aStream->GetDefaultCandidate(1, &defaultRtpCandidate);
   if (NS_SUCCEEDED(rv)) {
     info.mDefaultHostRtp = defaultRtpCandidate.cand_addr.host;
     info.mDefaultPortRtp = defaultRtpCandidate.cand_addr.port;
   } else {
     CSFLogError(LOGTAG,
--- a/media/webrtc/signaling/src/peerconnection/MediaTransportHandler.h
+++ b/media/webrtc/signaling/src/peerconnection/MediaTransportHandler.h
@@ -100,17 +100,18 @@ class MediaTransportHandler {
 
   virtual void StartIceChecks(bool aIsControlling, bool aIsOfferer,
                               const std::vector<std::string>& aIceOptions) = 0;
 
   virtual void SendPacket(const std::string& aTransportId,
                           MediaPacket&& aPacket) = 0;
 
   virtual void AddIceCandidate(const std::string& aTransportId,
-                               const std::string& aCandidate) = 0;
+                               const std::string& aCandidate,
+                               const std::string& aUFrag) = 0;
 
   virtual void UpdateNetworkState(bool aOnline) = 0;
 
   // dom::RTCStatsReportInternal doesn't have move semantics.
   typedef MozPromise<std::unique_ptr<dom::RTCStatsReportInternal>, nsresult,
                      true>
       StatsPromise;
   virtual RefPtr<StatsPromise> GetIceStats(
--- a/media/webrtc/signaling/src/peerconnection/MediaTransportHandlerIPC.cpp
+++ b/media/webrtc/signaling/src/peerconnection/MediaTransportHandlerIPC.cpp
@@ -250,22 +250,23 @@ void MediaTransportHandlerIPC::SendPacke
         if (mChild) {
           mChild->SendSendPacket(aTransportId, aPacket);
         }
       },
       [](const nsCString& aError) {});
 }
 
 void MediaTransportHandlerIPC::AddIceCandidate(const std::string& aTransportId,
-                                               const std::string& aCandidate) {
+                                               const std::string& aCandidate,
+                                               const std::string& aUfrag) {
   mInitPromise->Then(
       GetMainThreadSerialEventTarget(), __func__,
       [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
         if (mChild) {
-          mChild->SendAddIceCandidate(aTransportId, aCandidate);
+          mChild->SendAddIceCandidate(aTransportId, aCandidate, aUfrag);
         }
       },
       [](const nsCString& aError) {});
 }
 
 void MediaTransportHandlerIPC::UpdateNetworkState(bool aOnline) {
   mInitPromise->Then(
       GetMainThreadSerialEventTarget(), __func__,
--- a/media/webrtc/signaling/src/peerconnection/MediaTransportHandlerIPC.h
+++ b/media/webrtc/signaling/src/peerconnection/MediaTransportHandlerIPC.h
@@ -58,17 +58,18 @@ class MediaTransportHandlerIPC : public 
 
   void StartIceChecks(bool aIsControlling, bool aIsOfferer,
                       const std::vector<std::string>& aIceOptions) override;
 
   void SendPacket(const std::string& aTransportId,
                   MediaPacket&& aPacket) override;
 
   void AddIceCandidate(const std::string& aTransportId,
-                       const std::string& aCandidate) override;
+                       const std::string& aCandidate,
+                       const std::string& aUfrag) override;
 
   void UpdateNetworkState(bool aOnline) override;
 
   RefPtr<StatsPromise> GetIceStats(
       const std::string& aTransportId, DOMHighResTimeStamp aNow,
       std::unique_ptr<dom::RTCStatsReportInternal>&& aReport) override;
 
  private:
--- a/media/webrtc/signaling/src/peerconnection/MediaTransportParent.cpp
+++ b/media/webrtc/signaling/src/peerconnection/MediaTransportParent.cpp
@@ -217,19 +217,19 @@ mozilla::ipc::IPCResult MediaTransportPa
     const string& transportId, const MediaPacket& packet) {
   MOZ_ASSERT(GetMainThreadEventTarget()->IsOnCurrentThread());
   MediaPacket copy(packet);  // Laaaaaaame.
   mImpl->mHandler->SendPacket(transportId, std::move(copy));
   return ipc::IPCResult::Ok();
 }
 
 mozilla::ipc::IPCResult MediaTransportParent::RecvAddIceCandidate(
-    const string& transportId, const string& candidate) {
+    const string& transportId, const string& candidate, const string& ufrag) {
   MOZ_ASSERT(GetMainThreadEventTarget()->IsOnCurrentThread());
-  mImpl->mHandler->AddIceCandidate(transportId, candidate);
+  mImpl->mHandler->AddIceCandidate(transportId, candidate, ufrag);
   return ipc::IPCResult::Ok();
 }
 
 mozilla::ipc::IPCResult MediaTransportParent::RecvUpdateNetworkState(
     const bool& online) {
   MOZ_ASSERT(GetMainThreadEventTarget()->IsOnCurrentThread());
   mImpl->mHandler->UpdateNetworkState(online);
   return ipc::IPCResult::Ok();
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -1590,17 +1590,17 @@ PeerConnectionImpl::GetStats(MediaStream
                                                 nsAutoPtr<RTCStatsQuery>());
              });
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PeerConnectionImpl::AddIceCandidate(
-    const char* aCandidate, const char* aMid,
+    const char* aCandidate, const char* aMid, const char* aUfrag,
     const dom::Nullable<unsigned short>& aLevel) {
   PC_AUTO_ENTER_API_CALL(true);
 
   if (mForceIceTcp &&
       std::string::npos != std::string(aCandidate).find(" UDP ")) {
     CSFLogError(LOGTAG, "Blocking remote UDP candidate: %s", aCandidate);
     return NS_OK;
   }
@@ -1637,17 +1637,17 @@ PeerConnectionImpl::AddIceCandidate(
   nsresult res = mJsepSession->AddRemoteIceCandidate(aCandidate, aMid, level,
                                                      &transportId);
 
   if (NS_SUCCEEDED(res)) {
     // We do not bother PCMedia about this before offer/answer concludes.
     // Once offer/answer concludes, PCMedia will extract these candidates from
     // the remote SDP.
     if (mSignalingState == PCImplSignalingState::SignalingStable) {
-      mMedia->AddIceCandidate(aCandidate, transportId);
+      mMedia->AddIceCandidate(aCandidate, transportId, aUfrag);
       mRawTrickledCandidates.push_back(aCandidate);
     }
     pco->OnAddIceCandidateSuccess(rv);
   } else {
     ++mAddCandidateErrorCount;
     Error error;
     switch (res) {
       case NS_ERROR_UNEXPECTED:
@@ -2502,17 +2502,18 @@ const std::string& PeerConnectionImpl::G
 }
 
 const std::string& PeerConnectionImpl::GetName() {
   PC_AUTO_ENTER_API_CALL_NO_CHECK();
   return mName;
 }
 
 void PeerConnectionImpl::CandidateReady(const std::string& candidate,
-                                        const std::string& transportId) {
+                                        const std::string& transportId,
+                                        const std::string& ufrag) {
   PC_AUTO_ENTER_API_CALL_VOID_RETURN(false);
 
   if (candidate.empty()) {
     mJsepSession->EndOfLocalCandidates(transportId);
     return;
   }
 
   if (mForceIceTcp && std::string::npos != candidate.find(" UDP ")) {
@@ -2546,41 +2547,44 @@ void PeerConnectionImpl::CandidateReady(
                 "is bundled, which means it doesn't make sense for it "
                 "to have its own transport-related attributes.",
                 candidate.c_str(), transportId.c_str());
     return;
   }
 
   CSFLogDebug(LOGTAG, "Passing local candidate to content: %s",
               candidate.c_str());
-  SendLocalIceCandidateToContent(level, mid, candidate);
+  SendLocalIceCandidateToContent(level, mid, candidate, ufrag);
 }
 
 static void SendLocalIceCandidateToContentImpl(const nsWeakPtr& weakPCObserver,
                                                uint16_t level,
                                                const std::string& mid,
-                                               const std::string& candidate) {
+                                               const std::string& candidate,
+                                               const std::string& ufrag) {
   RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(weakPCObserver);
   if (!pco) {
     return;
   }
 
   JSErrorResult rv;
   pco->OnIceCandidate(level, ObString(mid.c_str()), ObString(candidate.c_str()),
-                      rv);
+                      ObString(ufrag.c_str()), rv);
 }
 
 void PeerConnectionImpl::SendLocalIceCandidateToContent(
-    uint16_t level, const std::string& mid, const std::string& candidate) {
+    uint16_t level, const std::string& mid, const std::string& candidate,
+    const std::string& ufrag) {
   // We dispatch this because OnSetLocalDescriptionSuccess does a setTimeout(0)
   // to unwind the stack, but the event handlers don't. We need to ensure that
   // the candidates do not skip ahead of the callback.
-  NS_DispatchToMainThread(WrapRunnableNM(&SendLocalIceCandidateToContentImpl,
-                                         mPCObserver, level, mid, candidate),
-                          NS_DISPATCH_NORMAL);
+  NS_DispatchToMainThread(
+      WrapRunnableNM(&SendLocalIceCandidateToContentImpl, mPCObserver, level,
+                     mid, candidate, ufrag),
+      NS_DISPATCH_NORMAL);
 }
 
 static bool isDone(PCImplIceConnectionState state) {
   return state != PCImplIceConnectionState::Checking &&
          state != PCImplIceConnectionState::New;
 }
 
 static bool isSucceeded(PCImplIceConnectionState state) {
@@ -2694,17 +2698,17 @@ void PeerConnectionImpl::IceGatheringSta
   }
   WrappableJSErrorResult rv;
   mThread->Dispatch(WrapRunnable(pco, &PeerConnectionObserver::OnStateChange,
                                  PCObserverStateType::IceGatheringState, rv,
                                  static_cast<JS::Realm*>(nullptr)),
                     NS_DISPATCH_NORMAL);
 
   if (mIceGatheringState == PCImplIceGatheringState::Complete) {
-    SendLocalIceCandidateToContent(0, "", "");
+    SendLocalIceCandidateToContent(0, "", "", "");
   }
 }
 
 void PeerConnectionImpl::UpdateDefaultCandidate(
     const std::string& defaultAddr, uint16_t defaultPort,
     const std::string& defaultRtcpAddr, uint16_t defaultRtcpPort,
     const std::string& transportId) {
   CSFLogDebug(LOGTAG, "%s", __FUNCTION__);
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -288,23 +288,26 @@ class PeerConnectionImpl final
   }
 
   NS_IMETHODIMP_TO_ERRORRESULT(GetStats, ErrorResult& rv,
                                mozilla::dom::MediaStreamTrack* aSelector) {
     rv = GetStats(aSelector);
   }
 
   NS_IMETHODIMP AddIceCandidate(const char* aCandidate, const char* aMid,
+                                const char* aUfrag,
                                 const dom::Nullable<unsigned short>& aLevel);
 
   void AddIceCandidate(const nsAString& aCandidate, const nsAString& aMid,
+                       const nsAString& aUfrag,
                        const dom::Nullable<unsigned short>& aLevel,
                        ErrorResult& rv) {
     rv = AddIceCandidate(NS_ConvertUTF16toUTF8(aCandidate).get(),
-                         NS_ConvertUTF16toUTF8(aMid).get(), aLevel);
+                         NS_ConvertUTF16toUTF8(aMid).get(),
+                         NS_ConvertUTF16toUTF8(aUfrag).get(), aLevel);
   }
 
   void UpdateNetworkState(bool online);
 
   NS_IMETHODIMP CloseStreams();
 
   void CloseStreams(ErrorResult& rv) { rv = CloseStreams(); }
 
@@ -546,19 +549,20 @@ class PeerConnectionImpl final
       dom::MediaStreamTrack& aRecvTrack);
 
   nsresult GetTimeSinceEpoch(DOMHighResTimeStamp* result);
 
   // Shut down media - called on main thread only
   void ShutdownMedia();
 
   void CandidateReady(const std::string& candidate,
-                      const std::string& transportId);
+                      const std::string& transportId, const std::string& ufrag);
   void SendLocalIceCandidateToContent(uint16_t level, const std::string& mid,
-                                      const std::string& candidate);
+                                      const std::string& candidate,
+                                      const std::string& ufrag);
 
   nsresult GetDatachannelParameters(uint32_t* channels, uint16_t* localport,
                                     uint16_t* remoteport,
                                     uint32_t* maxmessagesize, bool* mmsset,
                                     std::string* transportId,
                                     bool* client) const;
 
   nsresult AddRtpTransceiverToJsepSession(RefPtr<JsepTransceiver>& transceiver);
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -296,17 +296,17 @@ void PeerConnectionMedia::UpdateTranspor
           transport.mTransportId, transport.mLocalUfrag, transport.mLocalPwd,
           components, ufrag, pwd, keyDer, certDer,
           mParent->Identity()->auth_type(),
           transport.mDtls->GetRole() == JsepDtlsTransport::kJsepDtlsClient,
           digests, mParent->PrivacyRequested()),
       NS_DISPATCH_NORMAL);
 
   for (auto& candidate : candidates) {
-    AddIceCandidate("candidate:" + candidate, transport.mTransportId);
+    AddIceCandidate("candidate:" + candidate, transport.mTransportId, ufrag);
   }
 }
 
 nsresult PeerConnectionMedia::UpdateMediaPipelines() {
   // The GMP code is all the way on the other side of webrtc.org, and it is not
   // feasible to plumb error information all the way back. So, we set up a
   // handle to the PC (for the duration of this call) in a global variable.
   // This allows the GMP code to report errors to the PC.
@@ -383,22 +383,23 @@ void PeerConnectionMedia::ConnectSignals
       this, &PeerConnectionMedia::IceConnectionStateChange_s);
   mTransportHandler->SignalCandidate.connect(
       this, &PeerConnectionMedia::OnCandidateFound_s);
   mTransportHandler->SignalAlpnNegotiated.connect(
       this, &PeerConnectionMedia::AlpnNegotiated_s);
 }
 
 void PeerConnectionMedia::AddIceCandidate(const std::string& aCandidate,
-                                          const std::string& aTransportId) {
+                                          const std::string& aTransportId,
+                                          const std::string& aUfrag) {
   MOZ_ASSERT(!aTransportId.empty());
   RUN_ON_THREAD(
       GetSTSThread(),
       WrapRunnable(mTransportHandler, &MediaTransportHandler::AddIceCandidate,
-                   aTransportId, aCandidate),
+                   aTransportId, aCandidate, aUfrag),
       NS_DISPATCH_NORMAL);
 }
 
 void PeerConnectionMedia::UpdateNetworkState(bool online) {
   RUN_ON_THREAD(
       GetSTSThread(),
       WrapRunnable(mTransportHandler,
                    &MediaTransportHandler::UpdateNetworkState, online),
@@ -677,17 +678,18 @@ void PeerConnectionMedia::OnCandidateFou
     const std::string& aTransportId, const CandidateInfo& aCandidateInfo) {
   ASSERT_ON_THREAD(mMainThread);
   if (!aCandidateInfo.mDefaultHostRtp.empty()) {
     SignalUpdateDefaultCandidate(aCandidateInfo.mDefaultHostRtp,
                                  aCandidateInfo.mDefaultPortRtp,
                                  aCandidateInfo.mDefaultHostRtcp,
                                  aCandidateInfo.mDefaultPortRtcp, aTransportId);
   }
-  SignalCandidate(aCandidateInfo.mCandidate, aTransportId);
+  SignalCandidate(aCandidateInfo.mCandidate, aTransportId,
+                  aCandidateInfo.mUfrag);
 }
 
 void PeerConnectionMedia::AlpnNegotiated_s(const std::string& aAlpn) {
   GetMainThread()->Dispatch(
       WrapRunnableNM(&PeerConnectionMedia::AlpnNegotiated_m, mParentHandle,
                      aAlpn),
       NS_DISPATCH_NORMAL);
 }
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
@@ -60,17 +60,18 @@ class PeerConnectionMedia : public sigsl
   nsresult UpdateTransports(const JsepSession& aSession,
                             const bool forceIceTcp);
 
   // Start ICE checks.
   void StartIceChecks(const JsepSession& session);
 
   // Process a trickle ICE candidate.
   void AddIceCandidate(const std::string& candidate,
-                       const std::string& aTransportId);
+                       const std::string& aTransportId,
+                       const std::string& aUFrag);
 
   // Handle notifications of network online/offline events.
   void UpdateNetworkState(bool online);
 
   // Handle complete media pipelines.
   // This updates codec parameters, starts/stops send/receive, and other
   // stuff that doesn't necessarily require negotiation. This can be called at
   // any time, not just when an offer/answer exchange completes.
@@ -129,19 +130,20 @@ class PeerConnectionMedia : public sigsl
   static void AlpnNegotiated_m(const std::string& aParentHandle,
                                const std::string& aAlpn);
 
   // ICE state signals
   sigslot::signal1<mozilla::dom::PCImplIceGatheringState>
       SignalIceGatheringStateChange;
   sigslot::signal1<mozilla::dom::PCImplIceConnectionState>
       SignalIceConnectionStateChange;
-  // This passes a candidate:... attribute and transport id
+  // This passes a candidate:... attribute, transport id, and ufrag
   // end-of-candidates is signaled with the empty string
-  sigslot::signal2<const std::string&, const std::string&> SignalCandidate;
+  sigslot::signal3<const std::string&, const std::string&, const std::string&>
+      SignalCandidate;
   // This passes address, port, transport id of the default candidate.
   sigslot::signal5<const std::string&, uint16_t, const std::string&, uint16_t,
                    const std::string&>
       SignalUpdateDefaultCandidate;
 
   // TODO: Move to PeerConnectionImpl
   RefPtr<WebRtcCallWrapper> mCall;
 
--- a/testing/web-platform/meta/webrtc/RTCIceCandidate-constructor.html.ini
+++ b/testing/web-platform/meta/webrtc/RTCIceCandidate-constructor.html.ini
@@ -22,32 +22,16 @@
   [new RTCIceCandidate({ sdpMLineIndex: 0 })]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1529630
 
   [new RTCIceCandidate({ sdpMid: 'audio', sdpMLineIndex: 0 })]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1529630
 
-  [new RTCIceCandidate({ candidate: '', sdpMid: 'audio' }]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1490658
-
-  [new RTCIceCandidate({ candidate: '', sdpMLineIndex: 0 }]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1490658
-
-  [new RTCIceCandidate({ ... }) with valid candidate string and sdpMid]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1490658
-
-  [new RTCIceCandidate({ ... }) with invalid candidate string and sdpMid]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1490658
-
   [new RTCIceCandidate({ ... }) with invalid sdpMid]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1529630
 
   [new RTCIceCandidate({ ... }) with invalid sdpMLineIndex]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1529630