Backed out 10 changesets (bug 1535442) for mochitest failure at dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver_inproc.html
authorCoroiu Cristina <ccoroiu@mozilla.com>
Wed, 27 Mar 2019 01:19:57 +0200
changeset 525064 cebb5fa563f96382ce4036ddd0f53168d9211006
parent 525063 1467f14d7dd6bc82b4d445dd6505353069d005ff
child 525065 d5b5baea492ae20badf1a8d45189aeaa0ef0cc7d
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1535442
milestone68.0a1
backs outfe0c1f8b519b06f6f2ccb3605086c790b28e5a0f
96249192254a3d086cd45784a52505888e176d7a
8342491a4e91861a5e1f3642c29e512600c6a013
53556fae6a9bd9f651e68c37c8b821fb79c363ef
94781a70cd20a410e6bdfb2cfd99ea3b0174d8bf
d31a37ebf804c4cfb3c23e3c2757d198d6105093
490575f0e834cbb545f4a532e6506a64979c3d32
32f8c041f72dd48b7766d61432120051d503e90c
124cf9c814eb74cdf287b0b4ffafe5c57030a92f
2326906f18742e6a5c35f8f03c9a06dcd4044835
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
Backed out 10 changesets (bug 1535442) for mochitest failure at dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver_inproc.html Backed out changeset fe0c1f8b519b (bug 1535442) Backed out changeset 96249192254a (bug 1535442) Backed out changeset 8342491a4e91 (bug 1535442) Backed out changeset 53556fae6a9b (bug 1535442) Backed out changeset 94781a70cd20 (bug 1535442) Backed out changeset d31a37ebf804 (bug 1535442) Backed out changeset 490575f0e834 (bug 1535442) Backed out changeset 32f8c041f72d (bug 1535442) Backed out changeset 124cf9c814eb (bug 1535442) Backed out changeset 2326906f1874 (bug 1535442)
dom/media/PeerConnection.jsm
dom/media/tests/mochitest/pc.js
dom/media/tests/mochitest/sdpUtils.js
dom/media/tests/mochitest/test_peerConnection_restartIceLocalRollback.html
dom/media/tests/mochitest/test_peerConnection_restartIceLocalRollbackNoSubsequentRestart.html
media/mtransport/nricectx.cpp
media/mtransport/third_party/nICEr/src/ice/ice_component.c
media/mtransport/third_party/nICEr/src/ice/ice_component.h
media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
media/mtransport/third_party/nICEr/src/ice/ice_media_stream.c
media/mtransport/third_party/nICEr/src/ice/ice_media_stream.h
media/webrtc/signaling/gtest/jsep_session_unittest.cpp
media/webrtc/signaling/src/jsep/JsepSession.h
media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
media/webrtc/signaling/src/jsep/JsepSessionImpl.h
media/webrtc/signaling/src/peerconnection/MediaTransportHandler.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
media/webrtc/signaling/src/sdp/SdpHelper.cpp
media/webrtc/signaling/src/sdp/SdpHelper.h
testing/web-platform/meta/webrtc/RTCPeerConnection-addIceCandidate.html.ini
testing/web-platform/tests/webrtc/RTCPeerConnection-addIceCandidate.html
--- a/dom/media/PeerConnection.jsm
+++ b/dom/media/PeerConnection.jsm
@@ -1698,23 +1698,23 @@ class PeerConnectionObserver {
   onAddIceCandidateSuccess() {
     this._dompc._onAddIceCandidateSuccess();
   }
 
   onAddIceCandidateError(code, message) {
     this._dompc._onAddIceCandidateError(this.newError(message, code));
   }
 
-  onIceCandidate(sdpMLineIndex, sdpMid, candidate, usernameFragment) {
+  onIceCandidate(sdpMLineIndex, sdpMid, candidate, ufrag) {
     let win = this._dompc._win;
-    if (candidate || sdpMid || usernameFragment) {
+    if (candidate) {
       if (candidate.includes(" typ relay ")) {
         this._dompc._iceGatheredRelayCandidates = true;
       }
-      candidate = new win.RTCIceCandidate({ candidate, sdpMid, sdpMLineIndex, usernameFragment });
+      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/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -1426,18 +1426,18 @@ PeerConnectionWrapper.prototype = {
         info(this.label + ": received end of trickle ICE event");
         ok(this._pc.iceGatheringState === 'complete',
            "ICE gathering state has reached complete");
         resolveEndOfTrickle(this.label);
         return;
       }
 
       info(this.label + ": iceCandidate = " + JSON.stringify(anEvent.candidate));
+      ok(anEvent.candidate.candidate.length > 0, "ICE candidate contains candidate");
       ok(anEvent.candidate.sdpMid.length > 0, "SDP mid not empty");
-      ok(anEvent.candidate.usernameFragment.length > 0, "usernameFragment not empty");
 
       // only check the m-section for the updated default addr that corresponds
       // with this candidate.
       var mSections = this.localDescription.sdp.split("\r\nm=");
       sdputils.checkSdpCLineNotDefault(
         mSections[anEvent.candidate.sdpMLineIndex+1], this.label
       );
 
--- a/dom/media/tests/mochitest/sdpUtils.js
+++ b/dom/media/tests/mochitest/sdpUtils.js
@@ -66,73 +66,42 @@ verify_unique_extmap_ids: function(sdp) 
       ((result[id][0] === urn) && (result[id][1] === dir)),
         "ID " + id + " is unique ID for " + urn + " and direction " + dir);
     result[id] = [urn, dir];
     return result;
   }, {});
 },
 
 getMSections: function(sdp) {
-  return sdp.split(new RegExp('^m=', 'gm')).slice(1).map(s => "m=" + s);
+  return sdp.split(new RegExp('^m=', 'gm')).slice(1);
 },
 
 getAudioMSections: function(sdp) {
-  return this.getMSections(sdp).filter(section => section.startsWith('m=audio'))
+  return this.getMSections(sdp).filter(section => section.startsWith('audio'))
 },
 
 getVideoMSections: function(sdp) {
-  return this.getMSections(sdp).filter(section => section.startsWith('m=video'))
+  return this.getMSections(sdp).filter(section => section.startsWith('video'))
 },
 
-checkSdpAfterEndOfTrickle: function(description, testOptions, label) {
-  info("EOC-SDP: " + JSON.stringify(description));
-
-  const checkForTransportAttributes = msection => {
-    info("Checking msection: " + msection);
-    ok(msection.includes("a=end-of-candidates"),
-      label + ": SDP contains end-of-candidates");
-    sdputils.checkSdpCLineNotDefault(msection, label);
+checkSdpAfterEndOfTrickle: function(sdp, testOptions, label) {
+  info("EOC-SDP: " + JSON.stringify(sdp));
 
-    if (!msection.startsWith("m=application")) {
-      if (testOptions.rtcpmux) {
-        ok(msection.includes("a=rtcp-mux"), label + ": SDP contains rtcp-mux");
-      } else {
-        ok(msection.includes("a=rtcp:"), label + ": SDP contains rtcp port");
-      }
-    }
-  };
+  ok(sdp.sdp.includes("a=end-of-candidates"), label + ": SDP contains end-of-candidates");
+  sdputils.checkSdpCLineNotDefault(sdp.sdp, label);
 
-  const hasOwnTransport = msection => {
-    const port0Check = new RegExp(/^m=\S+ 0 /).exec(msection);
-    if (port0Check) {
-      return false;
-    }
-    const midMatch = new RegExp(/\r\na=mid:(\S+)/).exec(msection);
-    if (!midMatch) {
-      return true;
-    }
-    const mid = midMatch[1];
-    const bundleGroupMatch =
-      new RegExp("\\r\\na=group:BUNDLE \\S.* " + mid + "\\s+")
-      .exec(description.sdp);
-    return bundleGroupMatch == null;
-  };
-
-  const msectionsWithOwnTransports =
-    this.getMSections(description.sdp).filter(hasOwnTransport);
-
-  ok(msectionsWithOwnTransports.length > 0,
-    "SDP should contain at least one msection with a transport");
-  msectionsWithOwnTransports.forEach(checkForTransportAttributes);
-
+  if (testOptions.rtcpmux) {
+    ok(sdp.sdp.includes("a=rtcp-mux"), label + ": SDP contains rtcp-mux");
+  } else {
+    ok(sdp.sdp.includes("a=rtcp:"), label + ": SDP contains rtcp port");
+  }
   if (testOptions.ssrc) {
-    ok(description.sdp.includes("a=ssrc"), label + ": SDP contains a=ssrc");
+    ok(sdp.sdp.includes("a=ssrc"), label + ": SDP contains a=ssrc");
   } else {
-    ok(!description.sdp.includes("a=ssrc"),
-      label + ": SDP does not contain a=ssrc");
+    ok(!sdp.sdp.includes("a=ssrc"), label + ": SDP does not contain a=ssrc");
   }
 },
 
 // takes sdp in string form (or possibly a fragment, say an m-section), and
 // verifies that the default 0.0.0.0 addr is not present.
 checkSdpCLineNotDefault: function(sdpStr, label) {
   info("CLINE-NO-DEFAULT-ADDR-SDP: " + JSON.stringify(sdpStr));
   ok(!sdpStr.includes("c=IN IP4 0.0.0.0"), label + ": SDP contains non-zero IP c line");
--- a/dom/media/tests/mochitest/test_peerConnection_restartIceLocalRollback.html
+++ b/dom/media/tests/mochitest/test_peerConnection_restartIceLocalRollback.html
@@ -19,17 +19,17 @@
       [
         // causes a full, normal ice restart
         function PC_LOCAL_SET_OFFER_OPTION(test) {
           test.setOfferOptions({ iceRestart: true });
         },
         // causes an ice restart and then rolls it back
         // (does not result in sending an offer)
         function PC_LOCAL_SETUP_ICE_HANDLER(test) {
-          test.pcLocal.setupIceCandidateHandler(test, () => {});
+          test.pcLocal.setupIceCandidateHandler(test);
           if (test.testOptions.steeplechase) {
             test.pcLocal.endOfTrickleIce.then(() => {
               send_message({"type": "end_of_trickle_ice"});
             });
           }
         },
         function PC_LOCAL_CREATE_AND_SET_OFFER(test) {
           return test.createOffer(test.pcLocal).then(offer => {
--- a/dom/media/tests/mochitest/test_peerConnection_restartIceLocalRollbackNoSubsequentRestart.html
+++ b/dom/media/tests/mochitest/test_peerConnection_restartIceLocalRollbackNoSubsequentRestart.html
@@ -19,17 +19,17 @@
       [
         // causes a full, normal ice restart
         function PC_LOCAL_SET_OFFER_OPTION(test) {
           test.setOfferOptions({ iceRestart: true });
         },
         // causes an ice restart and then rolls it back
         // (does not result in sending an offer)
         function PC_LOCAL_SETUP_ICE_HANDLER(test) {
-          test.pcLocal.setupIceCandidateHandler(test, () => {});
+          test.pcLocal.setupIceCandidateHandler(test);
           if (test.testOptions.steeplechase) {
             test.pcLocal.endOfTrickleIce.then(() => {
               send_message({"type": "end_of_trickle_ice"});
             });
           }
         },
         function PC_LOCAL_CREATE_AND_SET_OFFER(test) {
           return test.createOffer(test.pcLocal).then(offer => {
--- a/media/mtransport/nricectx.cpp
+++ b/media/mtransport/nricectx.cpp
@@ -422,21 +422,16 @@ void NrIceCtx::trickle_cb(void *arg, nr_
   NrIceCtx *ctx = static_cast<NrIceCtx *>(arg);
   RefPtr<NrIceMediaStream> s = ctx->FindStream(stream);
 
   if (!s) {
     // This stream has been removed because it is inactive
     return;
   }
 
-  if (!candidate) {
-    s->SignalCandidate(s, "", stream->ufrag);
-    return;
-  }
-
   // Format the candidate.
   char candidate_str[NR_ICE_MAX_ATTRIBUTE_SIZE];
   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 "
--- a/media/mtransport/third_party/nICEr/src/ice/ice_component.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_component.c
@@ -702,30 +702,16 @@ int nr_ice_component_initialize(struct n
 void nr_ice_component_stop_gathering(nr_ice_component *component)
   {
     nr_ice_candidate *c1,*c2;
     TAILQ_FOREACH_SAFE(c1, &component->candidates, entry_comp, c2){
       nr_ice_candidate_stop_gathering(c1);
     }
   }
 
-int nr_ice_component_is_done_gathering(nr_ice_component *comp)
-  {
-    nr_ice_candidate *cand=TAILQ_FIRST(&comp->candidates);
-    while(cand){
-      if(cand->state != NR_ICE_CAND_STATE_INITIALIZED &&
-         cand->state != NR_ICE_CAND_STATE_FAILED){
-        return 0;
-      }
-      cand=TAILQ_NEXT(cand,entry_comp);
-    }
-    return 1;
-  }
-
-
 static int nr_ice_any_peer_paired(nr_ice_candidate* cand) {
   nr_ice_peer_ctx* pctx=STAILQ_FIRST(&cand->ctx->peers);
   while(pctx && pctx->state == NR_ICE_PEER_STATE_UNPAIRED){
     /* Is it worth actually looking through the check lists? Probably not. */
     pctx=STAILQ_NEXT(pctx,entry);
   }
   return pctx != NULL;
 }
--- a/media/mtransport/third_party/nICEr/src/ice/ice_component.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_component.h
@@ -82,17 +82,16 @@ struct nr_ice_component_ {
 };
 
 typedef STAILQ_HEAD(nr_ice_component_head_,nr_ice_component_) nr_ice_component_head;
 
 int nr_ice_component_create(struct nr_ice_media_stream_ *stream, int component_id, nr_ice_component **componentp);
 int nr_ice_component_destroy(nr_ice_component **componentp);
 int nr_ice_component_initialize(struct nr_ice_ctx_ *ctx,nr_ice_component *component);
 void nr_ice_component_stop_gathering(nr_ice_component *component);
-int nr_ice_component_is_done_gathering(nr_ice_component *comp);
 int nr_ice_component_maybe_prune_candidate(nr_ice_ctx *ctx, nr_ice_component *comp, nr_ice_candidate *c1, int *was_pruned);
 int nr_ice_component_pair_candidate(nr_ice_peer_ctx *pctx, nr_ice_component *pcomp, nr_ice_candidate *lcand, int pair_all_remote);
 int nr_ice_component_pair_candidates(nr_ice_peer_ctx *pctx, nr_ice_component *lcomp, nr_ice_component *pcomp);
 int nr_ice_component_service_pre_answer_requests(nr_ice_peer_ctx *pctx, nr_ice_component *pcomp, char *username, int *serviced);
 int nr_ice_component_nominated_pair(nr_ice_component *comp, nr_ice_cand_pair *pair);
 void nr_ice_component_failed_pair(nr_ice_component *comp, nr_ice_cand_pair *pair);
 void nr_ice_component_check_if_failed(nr_ice_component *comp);
 int nr_ice_component_select_pair(nr_ice_peer_ctx *pctx, nr_ice_component *comp);
--- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
@@ -506,26 +506,22 @@ int nr_ice_ctx_destroy(nr_ice_ctx **ctxp
     return(0);
   }
 
 void nr_ice_gather_finished_cb(NR_SOCKET s, int h, void *cb_arg)
   {
     int r;
     nr_ice_candidate *cand=cb_arg;
     nr_ice_ctx *ctx;
-    nr_ice_media_stream *stream;
-    int component_id;
 
 
     assert(cb_arg);
     if (!cb_arg)
       return;
     ctx = cand->ctx;
-    stream = cand->stream;
-    component_id = cand->component_id;
 
     ctx->uninitialized_candidates--;
     if (cand->state == NR_ICE_CAND_STATE_FAILED) {
       r_log(LOG_ICE, LOG_WARNING,
             "ICE(%s)/CAND(%s): failed to initialize, %d remaining", ctx->label,
             cand->label, ctx->uninitialized_candidates);
     } else {
       r_log(LOG_ICE, LOG_DEBUG, "ICE(%s)/CAND(%s): initialized, %d remaining",
@@ -539,36 +535,27 @@ void nr_ice_gather_finished_cb(NR_SOCKET
     if (cand->state == NR_ICE_CAND_STATE_INITIALIZED) {
       int was_pruned = 0;
 
       if (r=nr_ice_component_maybe_prune_candidate(ctx, cand->component,
                                                    cand, &was_pruned)) {
           r_log(LOG_ICE, LOG_NOTICE, "ICE(%s): Problem pruning candidates",ctx->label);
       }
 
-      if (was_pruned) {
-        cand = NULL;
-      }
-
       /* If we are initialized, the candidate wasn't pruned,
          and we have a trickle ICE callback fire the callback */
-      if (ctx->trickle_cb && cand &&
+      if (ctx->trickle_cb && !was_pruned &&
           !nr_ice_ctx_hide_candidate(ctx, cand)) {
         ctx->trickle_cb(ctx->trickle_cb_arg, ctx, cand->stream, cand->component_id, cand);
 
         if (nr_ice_ctx_pair_new_trickle_candidates(ctx, cand)) {
           r_log(LOG_ICE,LOG_ERR, "ICE(%s): All could not pair new trickle candidate",ctx->label);
           /* But continue */
         }
       }
-
-      if (nr_ice_media_stream_is_done_gathering(stream) &&
-          ctx->trickle_cb) {
-        ctx->trickle_cb(ctx->trickle_cb_arg, ctx, stream, component_id, NULL);
-      }
     }
 
     if(ctx->uninitialized_candidates==0){
       r_log(LOG_ICE, LOG_INFO, "ICE(%s): All candidates initialized",
             ctx->label);
       if (ctx->done_cb) {
         ctx->done_cb(0,0,ctx->cb_arg);
       }
--- a/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.c
@@ -651,29 +651,16 @@ void nr_ice_media_stream_set_obsolete(nr
 
     STAILQ_FOREACH_SAFE(c1, &str->components, entry, c2){
       nr_ice_component_stop_gathering(c1);
     }
 
     nr_ice_media_stream_stop_checking(str);
   }
 
-int nr_ice_media_stream_is_done_gathering(nr_ice_media_stream *str)
-  {
-    nr_ice_component *comp;
-    comp=STAILQ_FIRST(&str->components);
-    while(comp){
-      if(!nr_ice_component_is_done_gathering(comp)) {
-        return 0;
-      }
-      comp=STAILQ_NEXT(comp,entry);
-    }
-    return 1;
-  }
-
 void nr_ice_media_stream_refresh_consent_all(nr_ice_media_stream *stream)
   {
     nr_ice_component *comp;
 
     comp=STAILQ_FIRST(&stream->components);
     while(comp){
       if(comp->disconnected) {
         nr_ice_component_refresh_consent_now(comp);
--- a/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.h
@@ -98,17 +98,16 @@ void nr_ice_media_stream_component_nomin
 void nr_ice_media_stream_component_failed(nr_ice_media_stream *stream,nr_ice_component *component);
 void nr_ice_media_stream_refresh_consent_all(nr_ice_media_stream *stream);
 void nr_ice_media_stream_disconnect_all_components(nr_ice_media_stream *stream);
 void nr_ice_media_stream_set_disconnected(nr_ice_media_stream *stream, int disconnected);
 int nr_ice_media_stream_check_if_connected(nr_ice_media_stream *stream);
 int nr_ice_media_stream_set_state(nr_ice_media_stream *str, int state);
 void nr_ice_media_stream_stop_checking(nr_ice_media_stream *str);
 void nr_ice_media_stream_set_obsolete(nr_ice_media_stream *str);
-int nr_ice_media_stream_is_done_gathering(nr_ice_media_stream *str);
 int nr_ice_media_stream_get_best_candidate(nr_ice_media_stream *str, int component, nr_ice_candidate **candp);
 int nr_ice_media_stream_send(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component, UCHAR *data, int len);
 int nr_ice_media_stream_get_active(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component, nr_ice_candidate **local, nr_ice_candidate **remote);
 int nr_ice_media_stream_find_component(nr_ice_media_stream *str, int comp_id, nr_ice_component **compp);
 int nr_ice_media_stream_find_pair(nr_ice_media_stream *str, nr_ice_candidate *local, nr_ice_candidate *remote, nr_ice_cand_pair **pair);
 int nr_ice_media_stream_addrs(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component, nr_transport_addr *local, nr_transport_addr *remote);
 int
 nr_ice_peer_ctx_parse_media_stream_attribute(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, char *attr);
--- a/media/webrtc/signaling/gtest/jsep_session_unittest.cpp
+++ b/media/webrtc/signaling/gtest/jsep_session_unittest.cpp
@@ -969,17 +969,17 @@ class JsepSessionTest : public JsepSessi
         ++port;
         std::ostringstream candidate;
         candidate << "0 " << static_cast<uint16_t>(component)
                   << " UDP 9999 192.168.0.1 " << port << " typ host";
         std::string mid;
         uint16_t level = 0;
         bool skipped;
         session.AddLocalIceCandidate(kAEqualsCandidate + candidate.str(),
-                                     transportId, "", &level, &mid, &skipped);
+                                     transportId, &level, &mid, &skipped);
         if (!skipped) {
           mCandidatesToTrickle.push_back(Tuple<Level, Mid, Candidate>(
               level, mid, kAEqualsCandidate + candidate.str()));
           candidates.push_back(candidate.str());
         }
       }
 
       // Stomp existing candidates
@@ -1005,38 +1005,32 @@ class JsepSessionTest : public JsepSessi
         // cleared during renegotiation.
         session.UpdateDefaultCandidate(
             idAndCandidates.second[RTP].first,
             idAndCandidates.second[RTP].second,
             // Will be empty string if not present, which is how we indicate
             // that there is no default for RTCP
             idAndCandidates.second[RTCP].first,
             idAndCandidates.second[RTCP].second, idAndCandidates.first);
-        std::string mid;
-        uint16_t level = 0;
-        bool skipped;
-        session.AddLocalIceCandidate("", idAndCandidates.first, "", &level,
-                                     &mid, &skipped);
+        session.EndOfLocalCandidates(idAndCandidates.first);
       }
     }
 
     void Trickle(JsepSession& session) {
-      std::string transportId;
       for (const auto& levelMidAndCandidate : mCandidatesToTrickle) {
         Level level;
         Mid mid;
         Candidate candidate;
         Tie(level, mid, candidate) = levelMidAndCandidate;
         std::cerr << "trickling candidate: " << candidate << " level: " << level
                   << " mid: " << mid << std::endl;
+        std::string transportId;
         Maybe<unsigned long> lev = Some(level);
-        session.AddRemoteIceCandidate(candidate, mid, lev, "", &transportId);
+        session.AddRemoteIceCandidate(candidate, mid, lev, &transportId);
       }
-      session.AddRemoteIceCandidate("", "", Maybe<uint16_t>(), "",
-                                    &transportId);
       mCandidatesToTrickle.clear();
     }
 
     void CheckRtpCandidates(bool expectRtpCandidates,
                             const SdpMediaSection& msection,
                             const std::string& transportId,
                             const std::string& context) const {
       auto& attrs = msection.GetAttributeList();
@@ -2592,28 +2586,28 @@ TEST_P(JsepSessionTest, FullCallWithCand
         remoteOffer->GetMediaSection(i).GetAttributeList().HasAttribute(
             SdpAttribute::kBundleOnlyAttribute);
     mOffCandidates->CheckRtpCandidates(
         !bundleOnly, remoteOffer->GetMediaSection(i), id,
         "Remote offer after trickle should have RTP candidates "
         "(unless bundle-only)");
     mOffCandidates->CheckDefaultRtpCandidate(
         false, remoteOffer->GetMediaSection(i), id,
-        "Remote offer after trickle should not have a default RTP candidate.");
+        "Initial remote offer should not have a default RTP candidate.");
     mOffCandidates->CheckRtcpCandidates(
         !bundleOnly && types[i] != SdpMediaSection::kApplication,
         remoteOffer->GetMediaSection(i), id,
         "Remote offer after trickle should have RTCP candidates "
         "(unless m=application or bundle-only)");
     mOffCandidates->CheckDefaultRtcpCandidate(
         false, remoteOffer->GetMediaSection(i), id,
-        "Remote offer after trickle should not have a default RTCP candidate.");
+        "Initial remote offer should not have a default RTCP candidate.");
     CheckEndOfCandidates(
-        true, remoteOffer->GetMediaSection(i),
-        "Remote offer after trickle should have an end-of-candidates.");
+        false, remoteOffer->GetMediaSection(i),
+        "Initial remote offer should not have an end-of-candidates.");
   }
 
   AddTracks(*mSessionAns);
   std::string answer = CreateAnswer();
   SetLocalAnswer(answer);
   // This will gather candidates that mSessionAns knows it doesn't need.
   // They should not be present in the SDP.
   mAnsCandidates->Gather(*mSessionAns);
@@ -2661,18 +2655,18 @@ TEST_P(JsepSessionTest, FullCallWithCand
         false, remoteAnswer->GetMediaSection(i), id,
         "Remote answer after trickle should not have RTCP candidates "
         "(because we're answering with rtcp-mux)");
     mAnsCandidates->CheckDefaultRtcpCandidate(
         false, remoteAnswer->GetMediaSection(i), id,
         "Remote answer after trickle should not have a default RTCP "
         "candidate.");
     CheckEndOfCandidates(
-        true, remoteAnswer->GetMediaSection(i),
-        "Remote answer after trickle should have an end-of-candidates.");
+        false, remoteAnswer->GetMediaSection(i),
+        "Remote answer after trickle should not have an end-of-candidates.");
   }
 }
 
 TEST_P(JsepSessionTest, RenegotiationWithCandidates) {
   AddTracks(*mSessionOff);
   std::string offer = CreateOffer();
   SetLocalOffer(offer);
   mOffCandidates->Gather(*mSessionOff);
@@ -2703,19 +2697,18 @@ TEST_P(JsepSessionTest, RenegotiationWit
     mOffCandidates->CheckRtcpCandidates(
         false, parsedOffer->GetMediaSection(i), id,
         "Local reoffer before gathering should not have RTCP candidates.");
     mOffCandidates->CheckDefaultRtcpCandidate(
         false, parsedOffer->GetMediaSection(i), id,
         "Local reoffer before gathering should not have a default RTCP "
         "candidate.");
     CheckEndOfCandidates(
-        i == 0, parsedOffer->GetMediaSection(i),
-        "Local reoffer before gathering should have an end-of-candidates "
-        "(level 0 only)");
+        false, parsedOffer->GetMediaSection(i),
+        "Local reoffer before gathering should not have an end-of-candidates.");
   }
 
   // mSessionAns should generate a reoffer that is similar
   std::string otherOffer;
   JsepOfferOptions defaultOptions;
   nsresult rv = mSessionAns->CreateOffer(defaultOptions, &otherOffer);
   ASSERT_EQ(NS_OK, rv);
   parsedOffer = Parse(otherOffer);
@@ -2734,19 +2727,19 @@ TEST_P(JsepSessionTest, RenegotiationWit
         false, parsedOffer->GetMediaSection(i), id,
         "Local reoffer before gathering should not have RTCP candidates."
         " (previous answerer)");
     mAnsCandidates->CheckDefaultRtcpCandidate(
         false, parsedOffer->GetMediaSection(i), id,
         "Local reoffer before gathering should not have a default RTCP "
         "candidate. (previous answerer)");
     CheckEndOfCandidates(
-        i == 0, parsedOffer->GetMediaSection(i),
-        "Local reoffer before gathering should have an end-of-candidates "
-        "(level 0 only)");
+        false, parsedOffer->GetMediaSection(i),
+        "Local reoffer before gathering should not have an end-of-candidates. "
+        "(previous answerer)");
   }
 
   // Ok, let's continue with the renegotiation
   SetRemoteOffer(offer);
 
   // PeerConnection will not re-gather for RTP, but it will for RTCP in case
   // the answerer decides to turn off rtcp-mux.
   if (types[0] != SdpMediaSection::kApplication) {
@@ -2819,18 +2812,19 @@ TEST_P(JsepSessionTest, RenegotiationWit
     mOffCandidates->CheckRtcpCandidates(
         !bundleOnly && types[i] != SdpMediaSection::kApplication,
         remoteOffer->GetMediaSection(i), id,
         "Remote reoffer after trickle should have RTCP candidates "
         "(unless m=application or bundle-only)");
     mOffCandidates->CheckDefaultRtcpCandidate(
         false, remoteOffer->GetMediaSection(i), id,
         "Remote reoffer should not have a default RTCP candidate.");
-    CheckEndOfCandidates(true, remoteOffer->GetMediaSection(i),
-                         "Remote reoffer should have an end-of-candidates.");
+    CheckEndOfCandidates(
+        false, remoteOffer->GetMediaSection(i),
+        "Remote reoffer should not have an end-of-candidates.");
   }
 
   answer = CreateAnswer();
   SetLocalAnswer(answer);
   SetRemoteAnswer(answer);
   // No candidates should be gathered at the answerer, but default candidates
   // should be set.
   mAnsCandidates->FinishGathering(*mSessionAns);
@@ -2876,19 +2870,19 @@ TEST_P(JsepSessionTest, RenegotiationWit
     mAnsCandidates->CheckRtcpCandidates(
         false, remoteAnswer->GetMediaSection(i), id,
         "Remote reanswer after trickle should not have RTCP candidates "
         "(because we're reanswering with rtcp-mux)");
     mAnsCandidates->CheckDefaultRtcpCandidate(
         false, remoteAnswer->GetMediaSection(i), id,
         "Remote reanswer after trickle should not have a default RTCP "
         "candidate.");
-    CheckEndOfCandidates(i == 0, remoteAnswer->GetMediaSection(i),
-                         "Remote reanswer after trickle should have an "
-                         "end-of-candidates on level 0 only.");
+    CheckEndOfCandidates(
+        false, remoteAnswer->GetMediaSection(i),
+        "Remote reanswer after trickle should not have an end-of-candidates.");
   }
 }
 
 TEST_P(JsepSessionTest, RenegotiationAnswererSendonly) {
   AddTracks(*mSessionOff);
   AddTracks(*mSessionAns);
   OfferAnswer();
 
@@ -4813,28 +4807,28 @@ TEST_F(JsepSessionTest, CreateOfferAddCa
   std::string offer = CreateOffer();
   SetLocalOffer(offer);
 
   uint16_t level;
   std::string mid;
   bool skipped;
   nsresult rv;
   rv = mSessionOff->AddLocalIceCandidate(strSampleCandidate,
-                                         GetTransportId(*mSessionOff, 0), "",
+                                         GetTransportId(*mSessionOff, 0),
                                          &level, &mid, &skipped);
   ASSERT_EQ(NS_OK, rv);
 }
 
 TEST_F(JsepSessionTest, AddIceCandidateEarly) {
   uint16_t level;
   std::string mid;
   bool skipped;
   nsresult rv;
   rv = mSessionOff->AddLocalIceCandidate(strSampleCandidate,
-                                         GetTransportId(*mSessionOff, 0), "",
+                                         GetTransportId(*mSessionOff, 0),
                                          &level, &mid, &skipped);
 
   // This can't succeed without a local description
   ASSERT_NE(NS_OK, rv);
 }
 
 TEST_F(JsepSessionTest, OfferAnswerDontAddAudioStreamOnAnswerNoOptions) {
   types.push_back(SdpMediaSection::kAudio);
@@ -4910,17 +4904,17 @@ TEST_F(JsepSessionTest, AddCandidateInHa
   AddTracks(*mSessionOff, "audio");
   std::string offer = CreateOffer();
   SetLocalOffer(offer);
 
   nsresult rv;
   std::string mid;
   std::string transportId;
   rv = mSessionOff->AddRemoteIceCandidate(strSampleCandidate, mid,
-                                          Some(nSamplelevel), "", &transportId);
+                                          Some(nSamplelevel), &transportId);
   ASSERT_EQ(NS_ERROR_UNEXPECTED, rv);
 }
 
 TEST_F(JsepSessionTest, SetLocalWithoutCreateOffer) {
   types.push_back(SdpMediaSection::kAudio);
   AddTracks(*mSessionOff, "audio");
   AddTracks(*mSessionAns, "audio");
 
--- a/media/webrtc/signaling/src/jsep/JsepSession.h
+++ b/media/webrtc/signaling/src/jsep/JsepSession.h
@@ -134,27 +134,26 @@ class JsepSession {
       JsepDescriptionPendingOrCurrent type) const = 0;
   virtual nsresult SetLocalDescription(JsepSdpType type,
                                        const std::string& sdp) = 0;
   virtual nsresult SetRemoteDescription(JsepSdpType type,
                                         const std::string& sdp) = 0;
   virtual nsresult AddRemoteIceCandidate(const std::string& candidate,
                                          const std::string& mid,
                                          const Maybe<uint16_t>& level,
-                                         const std::string& ufrag,
                                          std::string* transportId) = 0;
   virtual nsresult AddLocalIceCandidate(const std::string& candidate,
                                         const std::string& transportId,
-                                        const std::string& ufrag,
                                         uint16_t* level, std::string* mid,
                                         bool* skipped) = 0;
   virtual nsresult UpdateDefaultCandidate(
       const std::string& defaultCandidateAddr, uint16_t defaultCandidatePort,
       const std::string& defaultRtcpCandidateAddr,
       uint16_t defaultRtcpCandidatePort, const std::string& transportId) = 0;
+  virtual nsresult EndOfLocalCandidates(const std::string& transportId) = 0;
   virtual nsresult Close() = 0;
 
   // ICE controlling or controlled
   virtual bool IsIceControlling() const = 0;
   virtual bool IsOfferer() const = 0;
   virtual bool IsIceRestarting() const = 0;
 
   virtual const std::string GetLastError() const { return "Error"; }
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
@@ -1049,22 +1049,20 @@ void JsepSessionImpl::EnsureHasOwnTransp
     transport.Close();
   }
 
   transport.mLocalUfrag = msection.GetAttributeList().GetIceUfrag();
   transport.mLocalPwd = msection.GetAttributeList().GetIcePwd();
 
   transceiver->ClearBundleLevel();
 
-  if (!transport.mComponents) {
-    if (mSdpHelper.HasRtcp(msection.GetProtocol())) {
-      transport.mComponents = 2;
-    } else {
-      transport.mComponents = 1;
-    }
+  if (mSdpHelper.HasRtcp(msection.GetProtocol())) {
+    transport.mComponents = 2;
+  } else {
+    transport.mComponents = 1;
   }
 
   if (transport.mTransportId.empty()) {
     // TODO: Once we use different ICE ufrag/pass for each m-section, we can
     // use that here.
     std::ostringstream os;
     os << "transport_" << mTransportIdCounter++;
     transport.mTransportId = os.str();
@@ -2009,89 +2007,73 @@ void JsepSessionImpl::SetState(JsepSigna
   MOZ_MTLOG(ML_NOTICE, "[" << mName << "]: " << GetStateStr(mState) << " -> "
                            << GetStateStr(state));
   mState = state;
 }
 
 nsresult JsepSessionImpl::AddRemoteIceCandidate(const std::string& candidate,
                                                 const std::string& mid,
                                                 const Maybe<uint16_t>& level,
-                                                const std::string& ufrag,
                                                 std::string* transportId) {
   mLastError.clear();
-  if (!mCurrentRemoteDescription && !mPendingRemoteDescription) {
-    JSEP_SET_ERROR("Cannot add ICE candidate when there is no remote SDP");
+
+  mozilla::Sdp* sdp =
+      GetParsedRemoteDescription(kJsepDescriptionPendingOrCurrent);
+
+  if (!sdp) {
+    JSEP_SET_ERROR("Cannot add ICE candidate in state " << GetStateStr(mState));
     return NS_ERROR_UNEXPECTED;
   }
 
-  if (mid.empty() && !level.isSome() && candidate.empty()) {
-    // Set end-of-candidates on SDP
-    if (mCurrentRemoteDescription) {
-      nsresult rv = mSdpHelper.SetIceGatheringComplete(
-          mCurrentRemoteDescription.get(), ufrag);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-
-    if (mPendingRemoteDescription) {
-      // If we had an error when adding the candidate to the current
-      // description, we stomp them here. This is deliberate.
-      nsresult rv = mSdpHelper.SetIceGatheringComplete(
-          mPendingRemoteDescription.get(), ufrag);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-    return NS_OK;
-  }
-
   JsepTransceiver* transceiver = nullptr;
+  bool hasMidOrLevel = true;
   if (!mid.empty()) {
     transceiver = GetTransceiverForMid(mid);
   } else if (level.isSome()) {
     transceiver = GetTransceiverForLevel(level.value());
+  } else {
+    hasMidOrLevel = false;
+  }
+
+  if (candidate.empty() && !hasMidOrLevel) {
+    for (uint16_t i = 0; i < sdp->GetMediaSectionCount(); ++i) {
+      mSdpHelper.SetIceGatheringComplete(sdp, i);
+    }
+    return NS_OK;
   }
 
   if (!transceiver) {
     JSEP_SET_ERROR("Cannot set ICE candidate for level="
                    << level << " mid=" << mid << ": No such transceiver.");
     return NS_ERROR_INVALID_ARG;
   }
 
   if (level.isSome() && transceiver->GetLevel() != level.value()) {
     MOZ_MTLOG(ML_WARNING, "Mismatch between mid and level - \""
                               << mid << "\" is not the mid for level "
                               << level);
   }
 
   *transportId = transceiver->mTransport.mTransportId;
-  nsresult rv = NS_ERROR_UNEXPECTED;
-  if (mCurrentRemoteDescription) {
-    rv =
-        mSdpHelper.AddCandidateToSdp(mCurrentRemoteDescription.get(), candidate,
-                                     transceiver->GetLevel(), ufrag);
-  }
 
-  if (mPendingRemoteDescription) {
-    // If we had an error when adding the candidate to the current description,
-    // we stomp them here. This is deliberate.
-    rv =
-        mSdpHelper.AddCandidateToSdp(mPendingRemoteDescription.get(), candidate,
-                                     transceiver->GetLevel(), ufrag);
-  }
-
-  return rv;
+  return mSdpHelper.AddCandidateToSdp(sdp, candidate, transceiver->GetLevel());
 }
 
 nsresult JsepSessionImpl::AddLocalIceCandidate(const std::string& candidate,
                                                const std::string& transportId,
-                                               const std::string& ufrag,
                                                uint16_t* level,
                                                std::string* mid,
                                                bool* skipped) {
   mLastError.clear();
-  if (!mCurrentLocalDescription && !mPendingLocalDescription) {
-    JSEP_SET_ERROR("Cannot add ICE candidate when there is no local SDP");
+
+  mozilla::Sdp* sdp =
+      GetParsedLocalDescription(kJsepDescriptionPendingOrCurrent);
+
+  if (!sdp) {
+    JSEP_SET_ERROR("Cannot add ICE candidate in state " << GetStateStr(mState));
     return NS_ERROR_UNEXPECTED;
   }
 
   JsepTransceiver* transceiver = GetTransceiverWithTransport(transportId);
   *skipped = !transceiver;
   if (*skipped) {
     // mainly here to make some testing less complicated, but also just in case
     return NS_OK;
@@ -2099,30 +2081,17 @@ nsresult JsepSessionImpl::AddLocalIceCan
 
   MOZ_ASSERT(
       transceiver->IsAssociated(),
       "ICE candidate was gathered before the transceiver was associated! "
       "This should never happen.");
   *level = transceiver->GetLevel();
   *mid = transceiver->GetMid();
 
-  nsresult rv = NS_ERROR_INVALID_ARG;
-  if (mCurrentLocalDescription) {
-    rv = mSdpHelper.AddCandidateToSdp(mCurrentLocalDescription.get(), candidate,
-                                      *level, ufrag);
-  }
-
-  if (mPendingLocalDescription) {
-    // If we had an error when adding the candidate to the current description,
-    // we stomp them here. This is deliberate.
-    rv = mSdpHelper.AddCandidateToSdp(mPendingLocalDescription.get(), candidate,
-                                      *level, ufrag);
-  }
-
-  return rv;
+  return mSdpHelper.AddCandidateToSdp(sdp, candidate, *level);
 }
 
 nsresult JsepSessionImpl::UpdateDefaultCandidate(
     const std::string& defaultCandidateAddr, uint16_t defaultCandidatePort,
     const std::string& defaultRtcpCandidateAddr,
     uint16_t defaultRtcpCandidatePort, const std::string& transportId) {
   mLastError.clear();
 
@@ -2162,16 +2131,36 @@ nsresult JsepSessionImpl::UpdateDefaultC
                                      defaultRtcpCandidatePort,
                                      &sdp->GetMediaSection(level));
     }
   }
 
   return NS_OK;
 }
 
+nsresult JsepSessionImpl::EndOfLocalCandidates(const std::string& transportId) {
+  mLastError.clear();
+
+  mozilla::Sdp* sdp =
+      GetParsedLocalDescription(kJsepDescriptionPendingOrCurrent);
+
+  if (!sdp) {
+    JSEP_SET_ERROR("Cannot mark end of local ICE candidates in state "
+                   << GetStateStr(mState));
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  JsepTransceiver* transceiver = GetTransceiverWithTransport(transportId);
+  if (transceiver) {
+    mSdpHelper.SetIceGatheringComplete(sdp, transceiver->GetLevel());
+  }
+
+  return NS_OK;
+}
+
 nsresult JsepSessionImpl::GetNegotiatedBundledMids(
     SdpHelper::BundledMids* bundledMids) {
   const Sdp* answerSdp = GetAnswer();
 
   if (!answerSdp) {
     return NS_OK;
   }
 
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
@@ -97,31 +97,32 @@ class JsepSessionImpl : public JsepSessi
                                        const std::string& sdp) override;
 
   virtual nsresult SetRemoteDescription(JsepSdpType type,
                                         const std::string& sdp) override;
 
   virtual nsresult AddRemoteIceCandidate(const std::string& candidate,
                                          const std::string& mid,
                                          const Maybe<uint16_t>& level,
-                                         const std::string& ufrag,
                                          std::string* transportId) override;
 
   virtual nsresult AddLocalIceCandidate(const std::string& candidate,
                                         const std::string& transportId,
-                                        const std::string& ufrag,
                                         uint16_t* level, std::string* mid,
                                         bool* skipped) override;
 
   virtual nsresult UpdateDefaultCandidate(
       const std::string& defaultCandidateAddr, uint16_t defaultCandidatePort,
       const std::string& defaultRtcpCandidateAddr,
       uint16_t defaultRtcpCandidatePort,
       const std::string& transportId) override;
 
+  virtual nsresult EndOfLocalCandidates(
+      const std::string& transportId) override;
+
   virtual nsresult Close() override;
 
   virtual const std::string GetLastError() const override;
 
   virtual bool IsIceControlling() const override { return mIceControlling; }
 
   virtual bool IsOfferer() const override { return mIsOfferer; }
 
--- a/media/webrtc/signaling/src/peerconnection/MediaTransportHandler.cpp
+++ b/media/webrtc/signaling/src/peerconnection/MediaTransportHandler.cpp
@@ -1147,16 +1147,23 @@ static mozilla::dom::PCImplIceGatheringS
     case NrIceCtx::ICE_CTX_GATHER_COMPLETE:
       return dom::PCImplIceGatheringState::Complete;
   }
   MOZ_CRASH();
 }
 
 void MediaTransportHandlerSTS::OnGatheringStateChange(
     NrIceCtx* aIceCtx, NrIceCtx::GatheringState aState) {
+  if (aState == NrIceCtx::ICE_CTX_GATHER_COMPLETE) {
+    for (const auto& stream : mIceCtx->GetStreams()) {
+      // 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) {
     case NrIceCtx::ICE_CTX_INIT:
       return dom::PCImplIceConnectionState::New;
@@ -1182,17 +1189,16 @@ void MediaTransportHandlerSTS::OnConnect
 }
 
 // The stuff below here will eventually go into the MediaTransportChild class
 void MediaTransportHandlerSTS::OnCandidateFound(NrIceMediaStream* aStream,
                                                 const std::string& aCandidate,
                                                 const std::string& aUfrag) {
   CandidateInfo info;
   info.mCandidate = aCandidate;
-  MOZ_ASSERT(!aUfrag.empty());
   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 {
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -1614,17 +1614,17 @@ PeerConnectionImpl::AddIceCandidate(
   JSErrorResult rv;
   RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
   if (!pco) {
     return NS_OK;
   }
 
   STAMP_TIMECARD(mTimeCard, "Add Ice Candidate");
 
-  CSFLogDebug(LOGTAG, "AddIceCandidate: %s %s", aCandidate, aUfrag);
+  CSFLogDebug(LOGTAG, "AddIceCandidate: %s", aCandidate);
 
   // When remote candidates are added before our ICE ctx is up and running
   // (the transition to New is async through STS, so this is not impossible),
   // we won't record them as trickle candidates. Is this what we want?
   if (!mIceStartTime.IsNull()) {
     TimeDuration timeDelta = TimeStamp::Now() - mIceStartTime;
     if (mIceConnectionState == PCImplIceConnectionState::Failed) {
       Telemetry::Accumulate(Telemetry::WEBRTC_ICE_LATE_TRICKLE_ARRIVAL_TIME,
@@ -1636,17 +1636,17 @@ PeerConnectionImpl::AddIceCandidate(
   }
 
   std::string transportId;
   Maybe<unsigned short> level;
   if (!aLevel.IsNull()) {
     level = Some(aLevel.Value());
   }
   nsresult res = mJsepSession->AddRemoteIceCandidate(aCandidate, aMid, level,
-                                                     aUfrag, &transportId);
+                                                     &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, aUfrag);
       mRawTrickledCandidates.push_back(aCandidate);
@@ -2496,27 +2496,32 @@ const std::string& PeerConnectionImpl::G
   return mName;
 }
 
 void PeerConnectionImpl::CandidateReady(const std::string& candidate,
                                         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 ")) {
     CSFLogWarn(LOGTAG, "Blocking local UDP candidate: %s", candidate.c_str());
     return;
   }
 
   // One of the very few places we still use level; required by the JSEP API
   uint16_t level = 0;
   std::string mid;
   bool skipped = false;
-  nsresult res = mJsepSession->AddLocalIceCandidate(
-      candidate, transportId, ufrag, &level, &mid, &skipped);
+  nsresult res = mJsepSession->AddLocalIceCandidate(candidate, transportId,
+                                                    &level, &mid, &skipped);
 
   if (NS_FAILED(res)) {
     std::string errorString = mJsepSession->GetLastError();
 
     CSFLogError(LOGTAG,
                 "Failed to incorporate local candidate into SDP:"
                 " res = %u, candidate = %s, transport-id = %s,"
                 " error = %s",
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -647,18 +647,16 @@ void PeerConnectionMedia::IceConnectionS
 
 void PeerConnectionMedia::OnCandidateFound_s(
     const std::string& aTransportId, const CandidateInfo& aCandidateInfo) {
   ASSERT_ON_THREAD(mSTSThread);
   MOZ_RELEASE_ASSERT(mTransportHandler);
 
   CSFLogDebug(LOGTAG, "%s: %s", __FUNCTION__, aTransportId.c_str());
 
-  MOZ_ASSERT(!aCandidateInfo.mUfrag.empty());
-
   // ShutdownMediaTransport_s has not run yet because it unhooks this function
   // from its signal, which means that SelfDestruct_m has not been dispatched
   // yet either, so this PCMedia will still be around when this dispatch reaches
   // main.
   GetMainThread()->Dispatch(
       WrapRunnable(this, &PeerConnectionMedia::OnCandidateFound_m, aTransportId,
                    aCandidateInfo),
       NS_DISPATCH_NORMAL);
--- a/media/webrtc/signaling/src/sdp/SdpHelper.cpp
+++ b/media/webrtc/signaling/src/sdp/SdpHelper.cpp
@@ -49,21 +49,16 @@ nsresult SdpHelper::CopyTransportParams(
         candidateAttrs->mValues.push_back(candidate);
       }
     }
     if (!candidateAttrs->mValues.empty()) {
       newLocalAttrs.SetAttribute(candidateAttrs.release());
     }
   }
 
-  if (oldLocalAttrs.HasAttribute(SdpAttribute::kEndOfCandidatesAttribute)) {
-    newLocalAttrs.SetAttribute(
-        new SdpFlagAttribute(SdpAttribute::kEndOfCandidatesAttribute));
-  }
-
   if (numComponents == 2 &&
       oldLocalAttrs.HasAttribute(SdpAttribute::kRtcpAttribute)) {
     // copy rtcp attribute if we had one that we are using
     newLocalAttrs.SetAttribute(new SdpRtcpAttribute(oldLocalAttrs.GetRtcp()));
   }
 
   return NS_OK;
 }
@@ -271,49 +266,40 @@ nsresult SdpHelper::GetMidFromLevel(cons
     *mid = attrList.GetMid();
   }
 
   return NS_OK;
 }
 
 nsresult SdpHelper::AddCandidateToSdp(Sdp* sdp,
                                       const std::string& candidateUntrimmed,
-                                      uint16_t level,
-                                      const std::string& ufrag) {
+                                      uint16_t level) {
   if (level >= sdp->GetMediaSectionCount()) {
     SDP_SET_ERROR("Index " << level << " out of range");
     return NS_ERROR_INVALID_ARG;
   }
 
-  SdpMediaSection& msection = sdp->GetMediaSection(level);
-  SdpAttributeList& attrList = msection.GetAttributeList();
-
-  if (!ufrag.empty()) {
-    if (!attrList.HasAttribute(SdpAttribute::kIceUfragAttribute) ||
-        attrList.GetIceUfrag() != ufrag) {
-      SDP_SET_ERROR("Unknown ufrag (" << ufrag << ")");
-      return NS_ERROR_INVALID_ARG;
-    }
-  }
-
   if (candidateUntrimmed.empty()) {
-    SetIceGatheringComplete(sdp, level, ufrag);
+    SetIceGatheringComplete(sdp, level);
     return NS_OK;
   }
 
   // Trim off '[a=]candidate:'
   size_t begin = candidateUntrimmed.find(':');
   if (begin == std::string::npos) {
     SDP_SET_ERROR("Invalid candidate, no ':' (" << candidateUntrimmed << ")");
     return NS_ERROR_INVALID_ARG;
   }
   ++begin;
 
   std::string candidate = candidateUntrimmed.substr(begin);
 
+  SdpMediaSection& msection = sdp->GetMediaSection(level);
+
+  SdpAttributeList& attrList = msection.GetAttributeList();
   UniquePtr<SdpMultiStringAttribute> candidates;
   if (!attrList.HasAttribute(SdpAttribute::kCandidateAttribute)) {
     // Create new
     candidates.reset(
         new SdpMultiStringAttribute(SdpAttribute::kCandidateAttribute));
   } else {
     // Copy existing
     candidates.reset(new SdpMultiStringAttribute(
@@ -321,48 +307,24 @@ nsresult SdpHelper::AddCandidateToSdp(Sd
             attrList.GetAttribute(SdpAttribute::kCandidateAttribute))));
   }
   candidates->PushEntry(candidate);
   attrList.SetAttribute(candidates.release());
 
   return NS_OK;
 }
 
-nsresult SdpHelper::SetIceGatheringComplete(Sdp* sdp,
-                                            const std::string& ufrag) {
-  for (uint16_t i = 0; i < sdp->GetMediaSectionCount(); ++i) {
-    nsresult rv = SetIceGatheringComplete(sdp, i, ufrag);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-  return NS_OK;
-}
+void SdpHelper::SetIceGatheringComplete(Sdp* sdp, uint16_t level) {
+  SdpMediaSection& msection = sdp->GetMediaSection(level);
 
-nsresult SdpHelper::SetIceGatheringComplete(Sdp* sdp, uint16_t level,
-                                            const std::string& ufrag) {
-  if (level >= sdp->GetMediaSectionCount()) {
-    SDP_SET_ERROR("Index " << level << " out of range");
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  SdpMediaSection& msection = sdp->GetMediaSection(level);
-  SdpAttributeList& attrList = msection.GetAttributeList();
-
-  if (!ufrag.empty()) {
-    if (!attrList.HasAttribute(SdpAttribute::kIceUfragAttribute) ||
-        attrList.GetIceUfrag() != ufrag) {
-      SDP_SET_ERROR("Unknown ufrag (" << ufrag << ")");
-      return NS_ERROR_INVALID_ARG;
-    }
-  }
-
-  attrList.SetAttribute(
+  SdpAttributeList& attrs = msection.GetAttributeList();
+  attrs.SetAttribute(
       new SdpFlagAttribute(SdpAttribute::kEndOfCandidatesAttribute));
   // Remove trickle-ice option
-  attrList.RemoveAttribute(SdpAttribute::kIceOptionsAttribute);
-  return NS_OK;
+  attrs.RemoveAttribute(SdpAttribute::kIceOptionsAttribute);
 }
 
 void SdpHelper::SetDefaultAddresses(const std::string& defaultCandidateAddr,
                                     uint16_t defaultCandidatePort,
                                     const std::string& defaultRtcpCandidateAddr,
                                     uint16_t defaultRtcpCandidatePort,
                                     SdpMediaSection* msection) {
   SdpAttributeList& attrList = msection->GetAttributeList();
--- a/media/webrtc/signaling/src/sdp/SdpHelper.h
+++ b/media/webrtc/signaling/src/sdp/SdpHelper.h
@@ -55,20 +55,18 @@ class SdpHelper {
   nsresult GetMidFromLevel(const Sdp& sdp, uint16_t level, std::string* mid);
   nsresult GetIdsFromMsid(const Sdp& sdp, const SdpMediaSection& msection,
                           std::vector<std::string>* streamId);
   nsresult GetMsids(const SdpMediaSection& msection,
                     std::vector<SdpMsidAttributeList::Msid>* msids);
   nsresult ParseMsid(const std::string& msidAttribute, std::string* streamId,
                      std::string* trackId);
   nsresult AddCandidateToSdp(Sdp* sdp, const std::string& candidate,
-                             uint16_t level, const std::string& ufrag);
-  nsresult SetIceGatheringComplete(Sdp* sdp, const std::string& ufrag);
-  nsresult SetIceGatheringComplete(Sdp* sdp, uint16_t level,
-                                   const std::string& ufrag);
+                             uint16_t level);
+  void SetIceGatheringComplete(Sdp* sdp, uint16_t level);
   void SetDefaultAddresses(const std::string& defaultCandidateAddr,
                            uint16_t defaultCandidatePort,
                            const std::string& defaultRtcpCandidateAddr,
                            uint16_t defaultRtcpCandidatePort,
                            SdpMediaSection* msection);
   void SetupMsidSemantic(const std::vector<std::string>& msids, Sdp* sdp) const;
   MsectionBundleType GetMsectionBundleType(const Sdp& sdp, uint16_t level,
                                            BundledMids& bundledMids,
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/webrtc/RTCPeerConnection-addIceCandidate.html.ini
@@ -0,0 +1,21 @@
+[RTCPeerConnection-addIceCandidate.html]
+  [Add candidate with invalid usernameFragment should reject with OperationError]
+    expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1535442
+
+  [Add candidate with sdpMid belonging to different usernameFragment should reject with OperationError]
+    expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1535442
+
+  [addIceCandidate({usernameFragment: usernameFragment1}) should work, and add a=end-of-candidates to the first m-section]
+    expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1535442
+
+  [addIceCandidate({usernameFragment: usernameFragment2}) should work, and add a=end-of-candidates to the first m-section]
+    expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1535442
+
+  [addIceCandidate({usernameFragment: "no such ufrag"}) should work, but not add a=end-of-candidates]
+    expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1535442
+
--- a/testing/web-platform/tests/webrtc/RTCPeerConnection-addIceCandidate.html
+++ b/testing/web-platform/tests/webrtc/RTCPeerConnection-addIceCandidate.html
@@ -171,51 +171,48 @@ a=rtcp-rsize
   }, `addIceCandidate(${JSON.stringify(init)}) should work, and add a=end-of-candidates to both m-sections`));
 
   promise_test(async t => {
     const pc = new RTCPeerConnection();
 
     t.add_cleanup(() => pc.close());
 
     await pc.setRemoteDescription(sessionDesc);
-    await pc.addIceCandidate({
-      usernameFragment: usernameFragment1,
-      sdpMid: sdpMid1
-    });
+    await pc.addIceCandidate({usernameFragment: usernameFragment1});
     assert_candidate_line_between(pc.remoteDescription.sdp,
       mediaLine1, endOfCandidateLine, mediaLine2);
     assert_false(is_candidate_line_after(pc.remoteDescription.sdp,
       mediaLine2, endOfCandidateLine));
-  }, 'addIceCandidate({usernameFragment: usernameFragment1, sdpMid: sdpMid1}) should work, and add a=end-of-candidates to the first m-section');
+  }, 'addIceCandidate({usernameFragment: usernameFragment1}) should work, and add a=end-of-candidates to the first m-section');
 
   promise_test(async t => {
     const pc = new RTCPeerConnection();
 
     t.add_cleanup(() => pc.close());
 
     await pc.setRemoteDescription(sessionDesc);
-    await pc.addIceCandidate({
-      usernameFragment: usernameFragment2,
-      sdpMLineIndex: 1
-    });
+    await pc.addIceCandidate({usernameFragment: usernameFragment2});
     assert_false(is_candidate_line_between(pc.remoteDescription.sdp,
       mediaLine1, endOfCandidateLine, mediaLine2));
     assert_true(is_candidate_line_after(pc.remoteDescription.sdp,
       mediaLine2, endOfCandidateLine));
-  }, 'addIceCandidate({usernameFragment: usernameFragment2, sdpMLineIndex: 1}) should work, and add a=end-of-candidates to the first m-section');
+  }, 'addIceCandidate({usernameFragment: usernameFragment2}) should work, and add a=end-of-candidates to the first m-section');
 
   promise_test(async t => {
     const pc = new RTCPeerConnection();
 
     t.add_cleanup(() => pc.close());
 
     await pc.setRemoteDescription(sessionDesc);
-    await promise_rejects(t, 'OperationError',
-      pc.addIceCandidate({usernameFragment: "no such ufrag"}));
-  }, 'addIceCandidate({usernameFragment: "no such ufrag"}) should not work');
+    await pc.addIceCandidate({usernameFragment: "no such ufrag"});
+    assert_false(is_candidate_line_between(pc.remoteDescription.sdp,
+      mediaLine1, endOfCandidateLine, mediaLine2));
+    assert_false(is_candidate_line_after(pc.remoteDescription.sdp,
+      mediaLine2, endOfCandidateLine));
+  }, 'addIceCandidate({usernameFragment: "no such ufrag"}) should work, but not add a=end-of-candidates');
 
   promise_test(t => {
     const pc = new RTCPeerConnection();
 
     t.add_cleanup(() => pc.close());
 
     return pc.setRemoteDescription(sessionDesc)
     .then(() => pc.addIceCandidate({
@@ -314,17 +311,17 @@ a=rtcp-rsize
 
     t.add_cleanup(() => pc.close());
 
     return pc.setRemoteDescription(sessionDesc)
     .then(() => pc.addIceCandidate({
       candidate: candidateStr1,
       sdpMid: sdpMid1,
       sdpMLineIndex: sdpMLineIndex1,
-      usernameFragment: null
+      ufrag: null
     }))
     .then(() => {
       assert_candidate_line_between(pc.remoteDescription.sdp,
         mediaLine1, candidateLine1, mediaLine2);
     });
   }, 'Add candidate for first media stream with null usernameFragment should add candidate to first media stream');
 
   promise_test(t => {
@@ -537,17 +534,17 @@ a=rtcp-rsize
 
     return pc.setRemoteDescription(sessionDesc)
     .then(() =>
       promise_rejects(t, 'OperationError',
         pc.addIceCandidate({
           candidate: candidateStr1,
           sdpMid: sdpMid1,
           sdpMLineIndex: sdpMLineIndex1,
-          usernameFragment: 'invalid'
+          ufrag: 'invalid'
         })));
   }, 'Add candidate with invalid usernameFragment should reject with OperationError');
 
   /*
     4.4.2.  addIceCandidate
       4.6.1.  If candidate could not be successfully added the user agent MUST
              queue a task that runs the following steps:
         2.  Reject p with a DOMException object whose name attribute has
@@ -576,13 +573,13 @@ a=rtcp-rsize
 
     return pc.setRemoteDescription(sessionDesc)
     .then(() =>
       promise_rejects(t, 'OperationError',
         pc.addIceCandidate({
           candidate: candidateStr2,
           sdpMid: sdpMid2,
           sdpMLineIndex: sdpMLineIndex2,
-          usernameFragment: usernameFragment1
+          usernameFragement: usernameFragment1
         })));
   }, 'Add candidate with sdpMid belonging to different usernameFragment should reject with OperationError');
 
 </script>