Bug 1318180: turn network offline events into ice disconnected event. r?bwc, r?jib draft
authorNils Ohlmeier [:drno] <drno@ohlmeier.org>
Thu, 17 Nov 2016 23:16:33 -0800
changeset 454950 25682d318a82e4af68233f646f005e7167451fd7
parent 454949 e120594f18fb68bf0cc9fe119100210f8b4a354c
child 540856 7e5476472a5cfb026d8db11e6e55b0ac21d336de
push id40093
push userdrno@ohlmeier.org
push dateFri, 30 Dec 2016 21:48:42 +0000
reviewersbwc, jib
bugs1318180
milestone53.0a1
Bug 1318180: turn network offline events into ice disconnected event. r?bwc, r?jib MozReview-Commit-ID: Kqbicl2goL2
dom/media/PeerConnection.js
media/mtransport/nricectx.cpp
media/mtransport/nricectx.h
media/mtransport/test/ice_unittest.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_media_stream.c
media/mtransport/third_party/nICEr/src/ice/ice_media_stream.h
media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.c
media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.h
media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h
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
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -177,29 +177,21 @@ GlobalPCList.prototype = {
       let winID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
       cleanupWinId(this._list, winID);
 
       if (this._lifecycleobservers.hasOwnProperty(winID)) {
         delete this._lifecycleobservers[winID];
       }
     } else if (topic == "profile-change-net-teardown" ||
                topic == "network:offline-about-to-go-offline") {
-      // Delete all peerconnections on shutdown - mostly synchronously (we
-      // need them to be done deleting transports and streams before we
-      // return)! All socket operations must be queued to STS thread
-      // before we return to here.
-      // Also kill them if "Work Offline" is selected - more can be created
-      // while offline, but attempts to connect them should fail.
-      for (let winId in this._list) {
-        cleanupWinId(this._list, winId);
-      }
+      // As Necko doesn't prevent us from accessing the network we still need to
+      // monitor the network offline/online state here. See bug 1326483
       this._networkdown = true;
     } else if (topic == "network:offline-status-changed") {
       if (data == "offline") {
-        // this._list shold be empty here
         this._networkdown = true;
       } else if (data == "online") {
         this._networkdown = false;
       }
     } else if (topic == "gmp-plugin-crash") {
       if (subject instanceof Ci.nsIWritablePropertyBag2) {
         let pluginID = subject.getPropertyAsUint32("pluginID");
         let pluginName = subject.getPropertyAsAString("pluginName");
@@ -1206,19 +1198,21 @@ RTCPeerConnection.prototype = {
 
   changeIceGatheringState: function(state) {
     this._iceGatheringState = state;
     _globalPCList.notifyLifecycleObservers(this, "icegatheringstatechange");
     this.dispatchEvent(new this._win.Event("icegatheringstatechange"));
   },
 
   changeIceConnectionState: function(state) {
-    this._iceConnectionState = state;
-    _globalPCList.notifyLifecycleObservers(this, "iceconnectionstatechange");
-    this.dispatchEvent(new this._win.Event("iceconnectionstatechange"));
+    if (state != this._iceConnectionState) {
+      this._iceConnectionState = state;
+      _globalPCList.notifyLifecycleObservers(this, "iceconnectionstatechange");
+      this.dispatchEvent(new this._win.Event("iceconnectionstatechange"));
+    }
   },
 
   getStats: function(selector, onSucc, onErr) {
     if (typeof onSucc == "function" &&
         this._warnDeprecatedStatsCallbacksNullable.warn) {
       this._warnDeprecatedStatsCallbacksNullable.warn();
       this._warnDeprecatedStatsCallbacksNullable.warn = null;
     }
@@ -1365,45 +1359,35 @@ PeerConnectionObserver.prototype = {
   },
 
 
   // This method is primarily responsible for updating iceConnectionState.
   // This state is defined in the WebRTC specification as follows:
   //
   // iceConnectionState:
   // -------------------
-  //   new           The ICE Agent is gathering addresses and/or waiting for
-  //                 remote candidates to be supplied.
-  //
-  //   checking      The ICE Agent has received remote candidates on at least
-  //                 one component, and is checking candidate pairs but has not
-  //                 yet found a connection. In addition to checking, it may
-  //                 also still be gathering.
+  //   new           Any of the RTCIceTransports are in the new state and none
+  //                 of them are in the checking, failed or disconnected state.
   //
-  //   connected     The ICE Agent has found a usable connection for all
-  //                 components but is still checking other candidate pairs to
-  //                 see if there is a better connection. It may also still be
-  //                 gathering.
+  //   checking      Any of the RTCIceTransports are in the checking state and
+  //                 none of them are in the failed or disconnected state.
   //
-  //   completed     The ICE Agent has finished gathering and checking and found
-  //                 a connection for all components. Open issue: it is not
-  //                 clear how the non controlling ICE side knows it is in the
+  //   connected     All RTCIceTransports are in the connected, completed or
+  //                 closed state and at least one of them is in the connected
   //                 state.
   //
-  //   failed        The ICE Agent is finished checking all candidate pairs and
-  //                 failed to find a connection for at least one component.
-  //                 Connections may have been found for some components.
+  //   completed     All RTCIceTransports are in the completed or closed state
+  //                 and at least one of them is in the completed state.
+  //
+  //   failed        Any of the RTCIceTransports are in the failed state.
   //
-  //   disconnected  Liveness checks have failed for one or more components.
-  //                 This is more aggressive than failed, and may trigger
-  //                 intermittently (and resolve itself without action) on a
-  //                 flaky network.
+  //   disconnected  Any of the RTCIceTransports are in the disconnected state
+  //                 and none of them are in the failed state.
   //
-  //   closed        The ICE Agent has shut down and is no longer responding to
-  //                 STUN requests.
+  //   closed        All of the RTCIceTransports are in the closed state.
 
   handleIceConnectionStateChange: function(iceConnectionState) {
     let pc = this._dompc;
     if (pc.iceConnectionState === iceConnectionState) {
       return;
     }
     if (pc.iceConnectionState === 'new') {
       var checking_histogram = Services.telemetry.getHistogramById("WEBRTC_ICE_CHECKING_RATE");
--- a/media/mtransport/nricectx.cpp
+++ b/media/mtransport/nricectx.cpp
@@ -966,16 +966,26 @@ nsresult NrIceCtx::Finalize() {
     MOZ_MTLOG(ML_ERROR, "Couldn't finalize "
          << name_ << "'");
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
+void NrIceCtx::UpdateNetworkState(bool online) {
+  MOZ_MTLOG(ML_INFO, "NrIceCtx(" << name_ << "): updating network state to " <<
+            (online ? "online" : "offline"));
+  if (online) {
+    nr_ice_peer_ctx_refresh_consent_all_streams(peer_);
+  } else {
+    nr_ice_peer_ctx_disconnect_all_streams(peer_);
+  }
+}
+
 void NrIceCtx::SetConnectionState(ConnectionState state) {
   if (state == connection_state_)
     return;
 
   MOZ_MTLOG(ML_INFO, "NrIceCtx(" << name_ << "): state " <<
             connection_state_ << "->" << state);
   connection_state_ = state;
 
--- a/media/mtransport/nricectx.h
+++ b/media/mtransport/nricectx.h
@@ -309,16 +309,19 @@ class NrIceCtx {
   nsresult SetProxyServer(const NrIceProxyServer& proxy_server);
 
   // Start ICE gathering
   nsresult StartGathering(bool default_route_only, bool proxy_only);
 
   // Start checking
   nsresult StartChecks();
 
+  // Notify that the network has gone online/offline
+  void UpdateNetworkState(bool online);
+
   // Finalize the ICE negotiation. I.e., there will be no
   // more forking.
   nsresult Finalize();
 
   // Are we trickling?
   bool generating_trickle() const { return trickle_; }
 
   // Signals to indicate events. API users can (and should)
--- a/media/mtransport/test/ice_unittest.cpp
+++ b/media/mtransport/test/ice_unittest.cpp
@@ -1361,16 +1361,36 @@ class IceTestPeer : public sigslot::has_
         WrapRunnable(this,
                         &IceTestPeer::AssertConsentRefresh_s,
                         0,
                         1,
                         status),
         NS_DISPATCH_SYNC);
   }
 
+  void ChangeNetworkState_s(bool online) {
+    ice_ctx_->ctx()->UpdateNetworkState(online);
+  }
+
+  void ChangeNetworkStateToOffline() {
+    test_utils_->sts_target()->Dispatch(
+        WrapRunnable(this,
+                        &IceTestPeer::ChangeNetworkState_s,
+                        false),
+        NS_DISPATCH_SYNC);
+  }
+
+  void ChangeNetworkStateToOnline() {
+    test_utils_->sts_target()->Dispatch(
+        WrapRunnable(this,
+                        &IceTestPeer::ChangeNetworkState_s,
+                        true),
+        NS_DISPATCH_SYNC);
+  }
+
   int trickled() { return trickled_; }
 
   void SetControlling(NrIceCtx::Controlling controlling) {
     nsresult res;
     test_utils_->sts_target()->Dispatch(
         WrapRunnableRet(&res, ice_ctx_->ctx(),
                         &NrIceCtx::SetControlling,
                         controlling),
@@ -3345,16 +3365,59 @@ TEST_F(WebRtcIceConnectTest, TestConsent
            request are ignored. */
   p1_->SetStunResponseDelay(300);
   p2_->SetStunResponseDelay(300);
   PR_Sleep(1000);
   AssertConsentRefresh();
   SendReceive();
 }
 
+TEST_F(WebRtcIceConnectTest, TestNetworkForcedOfflineAndRecovery) {
+  AddStream(1);
+  SetupAndCheckConsent();
+  p1_->ChangeNetworkStateToOffline();
+  ASSERT_TRUE_WAIT(p1_->ice_connected() == 0, kDefaultTimeout);
+  // Next round of consent check should switch it back to online
+  ASSERT_TRUE_WAIT(p1_->ice_connected(), kDefaultTimeout);
+}
+
+TEST_F(WebRtcIceConnectTest, TestNetworkForcedOfflineTwice) {
+  AddStream(1);
+  SetupAndCheckConsent();
+  p2_->ChangeNetworkStateToOffline();
+  ASSERT_TRUE_WAIT(p2_->ice_connected() == 0, kDefaultTimeout);
+  p2_->ChangeNetworkStateToOffline();
+  ASSERT_TRUE_WAIT(p2_->ice_connected() == 0, kDefaultTimeout);
+}
+
+TEST_F(WebRtcIceConnectTest, TestNetworkOnlineDoesntChangeState) {
+  AddStream(1);
+  SetupAndCheckConsent();
+  p2_->ChangeNetworkStateToOnline();
+  ASSERT_TRUE(p2_->ice_connected());
+  PR_Sleep(1500);
+  p2_->ChangeNetworkStateToOnline();
+  ASSERT_TRUE(p2_->ice_connected());
+}
+
+TEST_F(WebRtcIceConnectTest, TestNetworkOnlineTriggersConsent) {
+  // Let's emulate audio + video w/o rtcp-mux
+  AddStream(2);
+  AddStream(2);
+  SetupAndCheckConsent();
+  p1_->ChangeNetworkStateToOffline();
+  p1_->SetBlockStun(true);
+  ASSERT_TRUE_WAIT(p1_->ice_connected() == 0, kDefaultTimeout);
+  PR_Sleep(1500);
+  ASSERT_TRUE(p1_->ice_connected() == 0);
+  p1_->SetBlockStun(false);
+  p1_->ChangeNetworkStateToOnline();
+  ASSERT_TRUE_WAIT(p1_->ice_connected(), 500);
+}
+
 TEST_F(WebRtcIceConnectTest, TestConnectTurn) {
   if (turn_server_.empty())
     return;
 
   AddStream(1);
   SetTurnServer(turn_server_, kDefaultStunServerPort,
                 turn_user_, turn_password_);
   ASSERT_TRUE(Gather());
--- a/media/mtransport/third_party/nICEr/src/ice/ice_component.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_component.c
@@ -1237,39 +1237,50 @@ static void nr_ice_component_consent_tim
     comp->consent_timeout = 0;
 
     r_log(LOG_ICE,LOG_WARNING,"ICE(%s)/STREAM(%s)/COMP(%d): Consent refresh final time out",
           comp->ctx->label, comp->stream->label, comp->component_id);
     nr_ice_component_consent_failed(comp);
   }
 
 
-static void nr_ice_component_consent_request_timed_out(nr_ice_component *comp)
+void nr_ice_component_disconnected(nr_ice_component *comp)
   {
     if (!comp->can_send) {
       return;
     }
 
-    nr_ice_peer_ctx_disconnected(comp->stream->pctx);
+    if (comp->disconnected) {
+      return;
+    }
+
+    r_log(LOG_ICE,LOG_WARNING,"ICE(%s)/STREAM(%s)/COMP(%d): component disconnected",
+          comp->ctx->label, comp->stream->label, comp->component_id);
+    comp->disconnected = 1;
+
+    /* a single disconnected component disconnects the stream */
+    nr_ice_media_stream_set_disconnected(comp->stream, NR_ICE_MEDIA_STREAM_DISCONNECTED);
   }
 
 static void nr_ice_component_consent_refreshed(nr_ice_component *comp)
   {
     uint16_t tval;
 
     if (!comp->can_send) {
       return;
     }
 
     gettimeofday(&comp->consent_last_seen, 0);
     r_log(LOG_ICE,LOG_DEBUG,"ICE(%s)/STREAM(%s)/COMP(%d): consent_last_seen is now %lu",
         comp->ctx->label, comp->stream->label, comp->component_id,
         comp->consent_last_seen.tv_sec);
 
-    nr_ice_peer_ctx_connected(comp->stream->pctx);
+    comp->disconnected = 0;
+
+    nr_ice_media_stream_check_if_connected(comp->stream);
 
     if (comp->consent_timeout)
       NR_async_timer_cancel(comp->consent_timeout);
 
     tval = NR_ICE_CONSENT_TIMEOUT_DEFAULT;
     if (comp->ctx->test_timer_divider)
       tval = tval / comp->ctx->test_timer_divider;
 
@@ -1292,17 +1303,17 @@ static void nr_ice_component_refresh_con
       case NR_STUN_CLIENT_STATE_DONE:
         r_log(LOG_ICE, LOG_INFO, "ICE(%s)/STREAM(%s)/COMP(%d): Consent refreshed",
               comp->ctx->label, comp->stream->label, comp->component_id);
         nr_ice_component_consent_refreshed(comp);
         break;
       case NR_STUN_CLIENT_STATE_TIMED_OUT:
         r_log(LOG_ICE, LOG_INFO, "ICE(%s)/STREAM(%s)/COMP(%d): A single consent refresh request timed out",
               comp->ctx->label, comp->stream->label, comp->component_id);
-        nr_ice_component_consent_request_timed_out(comp);
+        nr_ice_component_disconnected(comp);
         break;
       default:
         break;
     }
   }
 
 int nr_ice_component_refresh_consent(nr_stun_client_ctx *ctx, NR_async_cb finished_cb, void *cb_arg)
   {
@@ -1335,16 +1346,19 @@ void nr_ice_component_consent_calc_conse
     comp->consent_ctx->maximum_transmits_timeout_ms = tval;
   }
 
 static void nr_ice_component_consent_timer_cb(NR_SOCKET s, int how, void *cb_arg)
   {
     nr_ice_component *comp=cb_arg;
     int r;
 
+    if (comp->consent_timer) {
+      NR_async_timer_cancel(comp->consent_timer);
+    }
     comp->consent_timer = 0;
 
     comp->consent_ctx->params.ice_binding_request.username =
       comp->stream->l2r_user;
     comp->consent_ctx->params.ice_binding_request.password =
       comp->stream->l2r_pass;
     comp->consent_ctx->params.ice_binding_request.control =
       comp->stream->pctx->controlling?
@@ -1356,22 +1370,16 @@ static void nr_ice_component_consent_tim
 
     nr_ice_component_consent_calc_consent_timer(comp);
 
     if (r=nr_ice_component_refresh_consent(comp->consent_ctx,
                                            nr_ice_component_refresh_consent_cb,
                                            comp)) {
       r_log(LOG_ICE,LOG_ERR,"ICE(%s)/STREAM(%s)/COMP(%d): Refresh consent failed with %d",
             comp->ctx->label, comp->stream->label, comp->component_id, r);
-      /* In case our attempt to send the refresh binding request reports an
-       * error we don't have to wait for timeouts, but declare this connection
-       * dead right away. */
-      if (r != R_WOULDBLOCK) {
-        nr_ice_component_consent_failed(comp);
-      }
     }
 
     nr_ice_component_consent_schedule_consent_timer(comp);
 
   }
 
 void nr_ice_component_consent_schedule_consent_timer(nr_ice_component *comp)
   {
@@ -1379,16 +1387,21 @@ void nr_ice_component_consent_schedule_c
       return;
     }
 
     NR_ASYNC_TIMER_SET(comp->consent_ctx->maximum_transmits_timeout_ms,
                        nr_ice_component_consent_timer_cb, comp,
                        &comp->consent_timer);
   }
 
+void nr_ice_component_refresh_consent_now(nr_ice_component *comp)
+  {
+    nr_ice_component_consent_timer_cb(0, 0, comp);
+  }
+
 void nr_ice_component_consent_destroy(nr_ice_component *comp)
   {
     if (comp->consent_timer) {
       NR_async_timer_cancel(comp->consent_timer);
       comp->consent_timer = 0;
     }
     if (comp->consent_timeout) {
       NR_async_timer_cancel(comp->consent_timeout);
@@ -1396,38 +1409,42 @@ void nr_ice_component_consent_destroy(nr
     }
     if (comp->consent_handle) {
       nr_ice_socket_deregister(comp->active->local->isock,
                                comp->consent_handle);
       comp->consent_handle = 0;
     }
     if (comp->consent_ctx) {
       nr_stun_client_ctx_destroy(&comp->consent_ctx);
+      comp->consent_ctx = 0;
     }
   }
 
 int nr_ice_component_setup_consent(nr_ice_component *comp)
   {
     int r,_status;
 
     r_log(LOG_ICE,LOG_DEBUG,"ICE(%s)/STREAM(%s)/COMP(%d): Setting up refresh consent",
           comp->ctx->label, comp->stream->label, comp->component_id);
 
+    nr_ice_component_consent_destroy(comp);
+
     if (r=nr_stun_client_ctx_create("consent", comp->active->local->osock,
                                     &comp->active->remote->addr, 0,
                                     &comp->consent_ctx))
       ABORT(r);
     /* Consent request get send only once. */
     comp->consent_ctx->maximum_transmits = 1;
 
     if (r=nr_ice_socket_register_stun_client(comp->active->local->isock,
             comp->consent_ctx, &comp->consent_handle))
       ABORT(r);
 
     comp->can_send = 1;
+    comp->disconnected = 0;
     nr_ice_component_consent_refreshed(comp);
 
     nr_ice_component_consent_calc_consent_timer(comp);
     nr_ice_component_consent_schedule_consent_timer(comp);
 
     _status=0;
   abort:
     return(_status);
--- a/media/mtransport/third_party/nICEr/src/ice/ice_component.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_component.h
@@ -70,16 +70,17 @@ struct nr_ice_component_ {
   struct nr_ice_cand_pair_ *nominated; /* Highest priority nomninated pair */
   struct nr_ice_cand_pair_ *active;
 
   nr_stun_client_ctx *consent_ctx;
   void *consent_timer;
   void *consent_timeout;
   void *consent_handle;
   int can_send;
+  int disconnected;
   struct timeval consent_last_seen;
 
   STAILQ_ENTRY(nr_ice_component_)entry;
 };
 
 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);
@@ -92,13 +93,15 @@ int nr_ice_component_service_pre_answer_
 int nr_ice_component_nominated_pair(nr_ice_component *comp, nr_ice_cand_pair *pair);
 int nr_ice_component_failed_pair(nr_ice_component *comp, nr_ice_cand_pair *pair);
 int 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);
 int nr_ice_component_set_failed(nr_ice_component *comp);
 int nr_ice_component_finalize(nr_ice_component *lcomp, nr_ice_component *rcomp);
 int nr_ice_component_insert_pair(nr_ice_component *pcomp, nr_ice_cand_pair *pair);
 int nr_ice_component_get_default_candidate(nr_ice_component *comp, nr_ice_candidate **candp, int ip_version);
+void nr_ice_component_refresh_consent_now(nr_ice_component *comp);
+void nr_ice_component_disconnected(nr_ice_component *comp);
 
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
 #endif
--- a/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.c
@@ -38,17 +38,17 @@ static char *RCSSTRING __UNUSED__="$Id: 
 #include <assert.h>
 #include <nr_api.h>
 #include <r_assoc.h>
 #include <async_timer.h>
 #include "ice_util.h"
 #include "ice_ctx.h"
 
 static char *nr_ice_media_stream_states[]={"INVALID",
-  "UNPAIRED","FROZEN","ACTIVE","COMPLETED","FAILED"
+  "UNPAIRED","FROZEN","ACTIVE","CONNECTED","FAILED"
 };
 
 int nr_ice_media_stream_set_state(nr_ice_media_stream *str, int state);
 
 int nr_ice_media_stream_create(nr_ice_ctx *ctx,char *label,int components, nr_ice_media_stream **streamp)
   {
     int r,_status;
     nr_ice_media_stream *stream=0;
@@ -69,16 +69,17 @@ int nr_ice_media_stream_create(nr_ice_ct
       if(r=nr_ice_component_create(stream, i+1, &comp))
         ABORT(r);
 
     }
 
     TAILQ_INIT(&stream->check_list);
     TAILQ_INIT(&stream->trigger_check_queue);
 
+    stream->disconnected = 0;
     stream->component_ct=components;
     stream->ice_state = NR_ICE_MEDIA_STREAM_UNPAIRED;
     *streamp=stream;
 
     _status=0;
   abort:
     if(_status){
       nr_ice_media_stream_destroy(&stream);
@@ -334,17 +335,17 @@ static void nr_ice_media_stream_check_ti
         /* Remove the pair from he trigger check queue */
         r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): Removing pair from trigger check queue %s",stream->pctx->label,pair->as_string);
         TAILQ_REMOVE(&stream->trigger_check_queue,pair,triggered_check_queue_entry);
         break;
       }
       pair=TAILQ_NEXT(pair,triggered_check_queue_entry);
     }
 
-    if (stream->ice_state != NR_ICE_MEDIA_STREAM_CHECKS_COMPLETED) {
+    if (stream->ice_state != NR_ICE_MEDIA_STREAM_CHECKS_CONNECTED) {
       if(!pair){
         /* Find the highest priority WAITING check and move it to RUNNING */
         pair=TAILQ_FIRST(&stream->check_list);
         while(pair){
           if(pair->state==NR_ICE_PAIR_STATE_WAITING)
             break;
           pair=TAILQ_NEXT(pair,check_queue_entry);
         }
@@ -387,17 +388,17 @@ int nr_ice_media_stream_start_checks(nr_
     if (stream->ice_state == NR_ICE_MEDIA_STREAM_CHECKS_FAILED) {
       assert(0);
       ABORT(R_INTERNAL);
     }
 
     /* Even if the stream is completed already remote can still create a new
      * triggered check request which needs to fire, but not change our stream
      * state. */
-    if (stream->ice_state != NR_ICE_MEDIA_STREAM_CHECKS_COMPLETED) {
+    if (stream->ice_state != NR_ICE_MEDIA_STREAM_CHECKS_CONNECTED) {
       if(r=nr_ice_media_stream_set_state(stream,NR_ICE_MEDIA_STREAM_CHECKS_ACTIVE)) {
         ABORT(r);
       }
     }
 
     if (!stream->timer) {
       r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/ICE-STREAM(%s): Starting check timer for stream.",pctx->label,stream->label);
       nr_ice_media_stream_check_timer_cb(0,0,stream);
@@ -579,16 +580,90 @@ int nr_ice_media_stream_set_state(nr_ice
     r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): %d active streams",
       str->pctx->label, str->pctx->active_streams);
 
     str->ice_state=state;
 
     return(0);
   }
 
+
+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->state != NR_ICE_COMPONENT_DISABLED) &&
+         (comp->local_component->state != NR_ICE_COMPONENT_DISABLED) &&
+          comp->disconnected) {
+        nr_ice_component_refresh_consent_now(comp);
+      }
+
+      comp=STAILQ_NEXT(comp,entry);
+    }
+  }
+
+void nr_ice_media_stream_disconnect_all_components(nr_ice_media_stream *stream)
+  {
+    nr_ice_component *comp;
+
+    comp=STAILQ_FIRST(&stream->components);
+    while(comp){
+      if((comp->state != NR_ICE_COMPONENT_DISABLED) &&
+         (comp->local_component->state != NR_ICE_COMPONENT_DISABLED)) {
+        comp->disconnected = 1;
+      }
+
+      comp=STAILQ_NEXT(comp,entry);
+    }
+  }
+
+void nr_ice_media_stream_set_disconnected(nr_ice_media_stream *stream, int disconnected)
+  {
+    if (stream->disconnected == disconnected) {
+      return;
+    }
+
+    if (stream->ice_state != NR_ICE_MEDIA_STREAM_CHECKS_CONNECTED) {
+      return;
+    }
+    stream->disconnected = disconnected;
+
+    if (disconnected == NR_ICE_MEDIA_STREAM_DISCONNECTED) {
+      nr_ice_peer_ctx_disconnected(stream->pctx);
+    } else {
+      nr_ice_peer_ctx_check_if_connected(stream->pctx);
+    }
+  }
+
+int nr_ice_media_stream_check_if_connected(nr_ice_media_stream *stream)
+  {
+    nr_ice_component *comp;
+
+    comp=STAILQ_FIRST(&stream->components);
+    while(comp){
+      if((comp->state != NR_ICE_COMPONENT_DISABLED) &&
+         (comp->local_component->state != NR_ICE_COMPONENT_DISABLED) &&
+         comp->disconnected)
+        break;
+
+      comp=STAILQ_NEXT(comp,entry);
+    }
+
+    /* At least one disconnected component */
+    if(comp)
+      goto done;
+
+    nr_ice_media_stream_set_disconnected(stream, NR_ICE_MEDIA_STREAM_CONNECTED);
+
+  done:
+    return(0);
+  }
+
 /* S OK, this component has a nominated. If every component has a nominated,
    the stream is ready */
 int nr_ice_media_stream_component_nominated(nr_ice_media_stream *stream,nr_ice_component *component)
   {
     int r,_status;
     nr_ice_component *comp;
 
     comp=STAILQ_FIRST(&stream->components);
@@ -602,17 +677,17 @@ int nr_ice_media_stream_component_nomina
     }
 
     /* At least one un-nominated component */
     if(comp)
       goto done;
 
     /* All done... */
     r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/ICE-STREAM(%s): all active components have nominated candidate pairs",stream->pctx->label,stream->label);
-    nr_ice_media_stream_set_state(stream,NR_ICE_MEDIA_STREAM_CHECKS_COMPLETED);
+    nr_ice_media_stream_set_state(stream,NR_ICE_MEDIA_STREAM_CHECKS_CONNECTED);
 
     /* Cancel our timer */
     if(stream->timer){
       NR_async_timer_cancel(stream->timer);
       stream->timer=0;
     }
 
     if (stream->pctx->handler) {
@@ -742,18 +817,22 @@ int nr_ice_media_stream_send(nr_ice_peer
     if(!comp->can_send)
       ABORT(R_FAILED);
 
     /* OK, write to that pair, which means:
        1. Use the socket on our local side.
        2. Use the address on the remote side
     */
     if(r=nr_socket_sendto(comp->active->local->osock,data,len,0,
-                          &comp->active->remote->addr))
+                          &comp->active->remote->addr)) {
+      if ((r==R_IO_ERROR) || (r==R_EOD)) {
+        nr_ice_component_disconnected(comp);
+      }
       ABORT(r);
+    }
 
     _status=0;
   abort:
     return(_status);
   }
 
 /* Returns R_REJECTED if the component is unpaired or has been disabled. */
 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)
--- a/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.h
@@ -54,19 +54,24 @@ struct nr_ice_media_stream_ {
   char *l2r_user;  /* The username for outgoing requests */
   Data r2l_pass;   /* The password for incoming requests */
   Data l2r_pass;   /* The password for outcoming requests */
   int ice_state;
 
 #define NR_ICE_MEDIA_STREAM_UNPAIRED           1
 #define NR_ICE_MEDIA_STREAM_CHECKS_FROZEN      2
 #define NR_ICE_MEDIA_STREAM_CHECKS_ACTIVE      3
-#define NR_ICE_MEDIA_STREAM_CHECKS_COMPLETED   4
+#define NR_ICE_MEDIA_STREAM_CHECKS_CONNECTED   4
 #define NR_ICE_MEDIA_STREAM_CHECKS_FAILED      5
 
+  int disconnected;
+
+#define NR_ICE_MEDIA_STREAM_CONNECTED    0
+#define NR_ICE_MEDIA_STREAM_DISCONNECTED 1
+
   nr_ice_cand_pair_head check_list;
   nr_ice_cand_pair_head trigger_check_queue;
   void *timer;  /* Check list periodic timer */
 
 /*  nr_ice_cand_pair_head valid_list; */
 
   STAILQ_ENTRY(nr_ice_media_stream_) entry;
 };
@@ -82,16 +87,20 @@ int nr_ice_media_stream_get_default_cand
 int nr_ice_media_stream_pair_candidates(nr_ice_peer_ctx *pctx,nr_ice_media_stream *lstream,nr_ice_media_stream *pstream);
 int nr_ice_media_stream_start_checks(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream);
 int nr_ice_media_stream_service_pre_answer_requests(nr_ice_peer_ctx *pctx,nr_ice_media_stream *lstream,nr_ice_media_stream *pstream, int *serviced);
 int nr_ice_media_stream_unfreeze_pairs(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream);
 int nr_ice_media_stream_unfreeze_pairs_foundation(nr_ice_media_stream *stream, char *foundation);
 int nr_ice_media_stream_dump_state(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream,FILE *out);
 int nr_ice_media_stream_component_nominated(nr_ice_media_stream *stream,nr_ice_component *component);
 int 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);
 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
--- a/media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.c
@@ -643,22 +643,55 @@ int nr_ice_peer_ctx_dump_state(nr_ice_pe
     fprintf(out,"==========================================\n");
 
     _status=0;
   abort:
     return(_status);
   }
 #endif
 
+void nr_ice_peer_ctx_refresh_consent_all_streams(nr_ice_peer_ctx *pctx)
+  {
+    nr_ice_media_stream *str;
+
+    r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s): refreshing consent on all streams",pctx->label);
+
+    str=STAILQ_FIRST(&pctx->peer_streams);
+    while(str) {
+      nr_ice_media_stream_refresh_consent_all(str);
+      str=STAILQ_NEXT(str,entry);
+    }
+  }
+
 void nr_ice_peer_ctx_disconnected(nr_ice_peer_ctx *pctx)
   {
     if (pctx->reported_connected &&
         pctx->handler &&
         pctx->handler->vtbl->ice_disconnected) {
       pctx->handler->vtbl->ice_disconnected(pctx->handler->obj, pctx);
+
+      pctx->reported_connected = 0;
+    }
+  }
+
+void nr_ice_peer_ctx_disconnect_all_streams(nr_ice_peer_ctx *pctx)
+  {
+    nr_ice_media_stream *str;
+
+    r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s): disconnecting all streams",pctx->label);
+
+    str=STAILQ_FIRST(&pctx->peer_streams);
+    while(str) {
+      nr_ice_media_stream_disconnect_all_components(str);
+
+      /* The first stream to be disconnected will cause the peer ctx to signal
+         the disconnect up. */
+      nr_ice_media_stream_set_disconnected(str, NR_ICE_MEDIA_STREAM_DISCONNECTED);
+
+      str=STAILQ_NEXT(str,entry);
     }
   }
 
 void nr_ice_peer_ctx_connected(nr_ice_peer_ctx *pctx)
   {
     /* Fire the handler callback to say we're done */
     if (pctx->reported_connected &&
         pctx->handler &&
@@ -682,17 +715,17 @@ int nr_ice_peer_ctx_check_if_connected(n
   {
     int _status;
     nr_ice_media_stream *str;
     int failed=0;
     int succeeded=0;
 
     str=STAILQ_FIRST(&pctx->peer_streams);
     while(str){
-      if(str->ice_state==NR_ICE_MEDIA_STREAM_CHECKS_COMPLETED){
+      if(str->ice_state==NR_ICE_MEDIA_STREAM_CHECKS_CONNECTED){
         succeeded++;
       }
       else if(str->ice_state==NR_ICE_MEDIA_STREAM_CHECKS_FAILED){
         failed++;
       }
       else{
         break;
       }
@@ -711,17 +744,16 @@ int nr_ice_peer_ctx_check_if_connected(n
     if (!pctx->reported_connected) {
       pctx->reported_connected = 1;
       assert(!pctx->connected_cb_timer);
       NR_ASYNC_TIMER_SET(0,nr_ice_peer_ctx_fire_connected,pctx,&pctx->connected_cb_timer);
     }
 
   done:
     _status=0;
-//  abort:
     return(_status);
   }
 
 
 /* Given a component in the main ICE ctx, find the relevant component in
    the peer_ctx */
 int nr_ice_peer_ctx_find_component(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component_id, nr_ice_component **compp)
   {
--- a/media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.h
@@ -78,16 +78,18 @@ int nr_ice_peer_ctx_find_pstream(nr_ice_
 int nr_ice_peer_ctx_remove_pstream(nr_ice_peer_ctx *pctx, nr_ice_media_stream **pstreamp);
 int nr_ice_peer_ctx_parse_trickle_candidate(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, char *cand);
 
 int nr_ice_peer_ctx_pair_candidates(nr_ice_peer_ctx *pctx);
 int nr_ice_peer_ctx_parse_global_attributes(nr_ice_peer_ctx *pctx, char **attrs, int attr_ct);
 int nr_ice_peer_ctx_start_checks(nr_ice_peer_ctx *pctx);
 int nr_ice_peer_ctx_start_checks2(nr_ice_peer_ctx *pctx, int allow_non_first);
 void nr_ice_peer_ctx_stream_started_checks(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream);
+void nr_ice_peer_ctx_refresh_consent_all_streams(nr_ice_peer_ctx *pctx);
+void nr_ice_peer_ctx_disconnect_all_streams(nr_ice_peer_ctx *pctx);
 void nr_ice_peer_ctx_disconnected(nr_ice_peer_ctx *pctx);
 void nr_ice_peer_ctx_connected(nr_ice_peer_ctx *pctx);
 int nr_ice_peer_ctx_dump_state(nr_ice_peer_ctx *pctx,FILE *out);
 int nr_ice_peer_ctx_log_state(nr_ice_peer_ctx *pctx);
 int nr_ice_peer_ctx_check_if_connected(nr_ice_peer_ctx *pctx);
 int nr_ice_peer_ctx_find_component(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component_id, nr_ice_component **compp);
 int nr_ice_peer_ctx_deliver_packet_maybe(nr_ice_peer_ctx *pctx, nr_ice_component *comp, nr_transport_addr *source_addr, UCHAR *data, int len);
 int nr_ice_peer_ctx_disable_component(nr_ice_peer_ctx *pctx, nr_ice_media_stream *lstream, int component_id);
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp
@@ -17,93 +17,118 @@
 #include "mozilla/Preferences.h"
 #include <mozilla/Types.h>
 #endif
 
 #include "nsNetCID.h" // NS_SOCKETTRANSPORTSERVICE_CONTRACTID
 #include "nsServiceManagerUtils.h" // do_GetService
 #include "nsIObserverService.h"
 #include "nsIObserver.h"
+#include "nsIIOService.h" // NS_IOSERVICE_*
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 
+#include "nsCRTGlue.h"
+
 #include "gmp-video-decode.h" // GMP_API_VIDEO_DECODER
 #include "gmp-video-encode.h" // GMP_API_VIDEO_ENCODER
 
 static const char* logTag = "PeerConnectionCtx";
 
 namespace mozilla {
 
 using namespace dom;
 
-class PeerConnectionCtxShutdown : public nsIObserver
+class PeerConnectionCtxObserver : public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
 
-  PeerConnectionCtxShutdown() {}
+  PeerConnectionCtxObserver() {}
 
   void Init()
     {
       nsCOMPtr<nsIObserverService> observerService =
         services::GetObserverService();
       if (!observerService)
         return;
 
       nsresult rv = NS_OK;
 
 #ifdef MOZILLA_INTERNAL_API
       rv = observerService->AddObserver(this,
                                         NS_XPCOM_SHUTDOWN_OBSERVER_ID,
                                         false);
       MOZ_ALWAYS_SUCCEEDS(rv);
+      rv = observerService->AddObserver(this,
+                                        NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
+                                        false);
+      MOZ_ALWAYS_SUCCEEDS(rv);
 #endif
       (void) rv;
     }
 
   NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
                      const char16_t* aData) override {
     if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
       CSFLogDebug(logTag, "Shutting down PeerConnectionCtx");
       PeerConnectionCtx::Destroy();
 
       nsCOMPtr<nsIObserverService> observerService =
         services::GetObserverService();
       if (!observerService)
         return NS_ERROR_FAILURE;
 
       nsresult rv = observerService->RemoveObserver(this,
-                                                    NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+                                           NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
+      MOZ_ALWAYS_SUCCEEDS(rv);
+      rv = observerService->RemoveObserver(this,
+                                           NS_XPCOM_SHUTDOWN_OBSERVER_ID);
       MOZ_ALWAYS_SUCCEEDS(rv);
 
       // Make sure we're not deleted while still inside ::Observe()
-      RefPtr<PeerConnectionCtxShutdown> kungFuDeathGrip(this);
-      PeerConnectionCtx::gPeerConnectionCtxShutdown = nullptr;
+      RefPtr<PeerConnectionCtxObserver> kungFuDeathGrip(this);
+      PeerConnectionCtx::gPeerConnectionCtxObserver = nullptr;
+    }
+    if (strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC) == 0) {
+      const nsLiteralString onlineString(u"" NS_IOSERVICE_ONLINE);
+      const nsLiteralString offlineString(u"" NS_IOSERVICE_OFFLINE);
+      if (NS_strcmp(aData, offlineString.get()) == 0) {
+        CSFLogDebug(logTag, "Updating network state to offline");
+        PeerConnectionCtx::UpdateNetworkState(false);
+      } else if(NS_strcmp(aData, onlineString.get()) == 0) {
+        CSFLogDebug(logTag, "Updating network state to online");
+        PeerConnectionCtx::UpdateNetworkState(true);
+      } else {
+        CSFLogDebug(logTag, "Received unsupported network state event");
+        MOZ_CRASH();
+      }
     }
     return NS_OK;
   }
 
 private:
-  virtual ~PeerConnectionCtxShutdown()
+  virtual ~PeerConnectionCtxObserver()
     {
       nsCOMPtr<nsIObserverService> observerService =
         services::GetObserverService();
       if (observerService)
+        observerService->RemoveObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
         observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
     }
 };
 
-NS_IMPL_ISUPPORTS(PeerConnectionCtxShutdown, nsIObserver);
+NS_IMPL_ISUPPORTS(PeerConnectionCtxObserver, nsIObserver);
 }
 
 namespace mozilla {
 
 PeerConnectionCtx* PeerConnectionCtx::gInstance;
 nsIThread* PeerConnectionCtx::gMainThread;
-StaticRefPtr<PeerConnectionCtxShutdown> PeerConnectionCtx::gPeerConnectionCtxShutdown;
+StaticRefPtr<PeerConnectionCtxObserver> PeerConnectionCtx::gPeerConnectionCtxObserver;
 
 const std::map<const std::string, PeerConnectionImpl *>&
 PeerConnectionCtx::mGetPeerConnections()
 {
   return mPeerConnections;
 }
 
 nsresult PeerConnectionCtx::InitializeGlobal(nsIThread *mainThread,
@@ -124,19 +149,19 @@ nsresult PeerConnectionCtx::InitializeGl
 
     res = ctx->Initialize();
     PR_ASSERT(NS_SUCCEEDED(res));
     if (!NS_SUCCEEDED(res))
       return res;
 
     gInstance = ctx;
 
-    if (!PeerConnectionCtx::gPeerConnectionCtxShutdown) {
-      PeerConnectionCtx::gPeerConnectionCtxShutdown = new PeerConnectionCtxShutdown();
-      PeerConnectionCtx::gPeerConnectionCtxShutdown->Init();
+    if (!PeerConnectionCtx::gPeerConnectionCtxObserver) {
+      PeerConnectionCtx::gPeerConnectionCtxObserver = new PeerConnectionCtxObserver();
+      PeerConnectionCtx::gPeerConnectionCtxObserver->Init();
     }
   }
 
   EnableWebRtcLog();
   return NS_OK;
 }
 
 PeerConnectionCtx* PeerConnectionCtx::GetInstance() {
@@ -329,16 +354,27 @@ PeerConnectionCtx::EverySecondTelemetryC
     rv = RUN_ON_THREAD(stsThread,
                        WrapRunnableNM(&EverySecondTelemetryCallback_s, queries),
                        NS_DISPATCH_NORMAL);
     NS_ENSURE_SUCCESS_VOID(rv);
   }
 }
 #endif
 
+void
+PeerConnectionCtx::UpdateNetworkState(bool online) {
+  auto ctx = GetInstance();
+  if (ctx->mPeerConnections.empty()) {
+    return;
+  }
+  for (auto pc : ctx->mPeerConnections) {
+    pc.second->UpdateNetworkState(online);
+  }
+}
+
 nsresult PeerConnectionCtx::Initialize() {
   initGMP();
 
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
   mTelemetryTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
   MOZ_ASSERT(mTelemetryTimer);
   nsresult rv = mTelemetryTimer->SetTarget(gMainThread);
   NS_ENSURE_SUCCESS(rv, rv);
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h
@@ -13,17 +13,17 @@
 
 #include "mozilla/Attributes.h"
 #include "mozilla/StaticPtr.h"
 #include "PeerConnectionImpl.h"
 #include "mozIGeckoMediaPluginService.h"
 #include "nsIRunnable.h"
 
 namespace mozilla {
-class PeerConnectionCtxShutdown;
+class PeerConnectionCtxObserver;
 
 namespace dom {
 class WebrtcGlobalInformation;
 }
 
 // A class to hold some of the singleton objects we need:
 // * The global PeerConnectionImpl table and its associated lock.
 // * Stats report objects for PCs that are gone
@@ -43,16 +43,18 @@ class PeerConnectionCtx {
     return true;
   }
 
   void queueJSEPOperation(nsIRunnable* aJSEPOperation);
   void onGMPReady();
 
   bool gmpHasH264();
 
+  static void UpdateNetworkState(bool online);
+
   // Make these classes friend so that they can access mPeerconnections.
   friend class PeerConnectionImpl;
   friend class PeerConnectionWrapper;
   friend class mozilla::dom::WebrtcGlobalInformation;
 
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
   // WebrtcGlobalInformation uses this; we put it here so we don't need to
   // create another shutdown observer class.
@@ -96,14 +98,14 @@ private:
   // ready to go, since blocking on this init is just begging for deadlock.
   nsCOMPtr<mozIGeckoMediaPluginService> mGMPService;
   bool mGMPReady;
   nsTArray<nsCOMPtr<nsIRunnable>> mQueuedJSEPOperations;
 
   static PeerConnectionCtx *gInstance;
 public:
   static nsIThread *gMainThread;
-  static mozilla::StaticRefPtr<mozilla::PeerConnectionCtxShutdown> gPeerConnectionCtxShutdown;
+  static mozilla::StaticRefPtr<mozilla::PeerConnectionCtxObserver> gPeerConnectionCtxObserver;
 };
 
 } // namespace mozilla
 
 #endif
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -2292,20 +2292,27 @@ PeerConnectionImpl::AddIceCandidate(cons
                         static_cast<unsigned>(res),
                         aCandidate,
                         static_cast<unsigned>(aLevel),
                         errorString.c_str());
 
     pco->OnAddIceCandidateError(error, ObString(errorString.c_str()), rv);
   }
 
-  UpdateSignalingState();
   return NS_OK;
 }
 
+void
+PeerConnectionImpl::UpdateNetworkState(bool online) {
+  if (!mMedia) {
+    return;
+  }
+  mMedia->UpdateNetworkState(online);
+}
+
 NS_IMETHODIMP
 PeerConnectionImpl::CloseStreams() {
   PC_AUTO_ENTER_API_CALL(false);
 
   return NS_OK;
 }
 
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
@@ -3359,18 +3366,16 @@ PeerConnectionImpl::CandidateReady(const
                         candidate.c_str(),
                         static_cast<unsigned>(level));
     return;
   }
 
   CSFLogDebug(logTag, "Passing local candidate to content: %s",
               candidate.c_str());
   SendLocalIceCandidateToContent(level, mid, candidate);
-
-  UpdateSignalingState();
 }
 
 static void
 SendLocalIceCandidateToContentImpl(nsWeakPtr weakPCObserver,
                                    uint16_t level,
                                    const std::string& mid,
                                    const std::string& candidate) {
   RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(weakPCObserver);
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -409,16 +409,18 @@ public:
 
   void AddIceCandidate(const nsAString& aCandidate, const nsAString& aMid,
                        unsigned short aLevel, ErrorResult &rv)
   {
     rv = AddIceCandidate(NS_ConvertUTF16toUTF8(aCandidate).get(),
                          NS_ConvertUTF16toUTF8(aMid).get(), aLevel);
   }
 
+  void UpdateNetworkState(bool online);
+
   NS_IMETHODIMP CloseStreams();
 
   void CloseStreams(ErrorResult &rv)
   {
     rv = CloseStreams();
   }
 
   NS_IMETHODIMP_TO_ERRORRESULT(AddTrack, ErrorResult &rv,
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -853,16 +853,17 @@ PeerConnectionMedia::AddIceCandidate(con
                 WrapRunnable(
                     RefPtr<PeerConnectionMedia>(this),
                     &PeerConnectionMedia::AddIceCandidate_s,
                     std::string(candidate), // Make copies.
                     std::string(mid),
                     aMLine),
                 NS_DISPATCH_NORMAL);
 }
+
 void
 PeerConnectionMedia::AddIceCandidate_s(const std::string& aCandidate,
                                        const std::string& aMid,
                                        uint32_t aMLine) {
   RefPtr<NrIceMediaStream> stream(mIceCtxHdlr->ctx()->GetStream(aMLine));
   if (!stream) {
     CSFLogError(logTag, "No ICE stream for candidate at level %u: %s",
                         static_cast<unsigned>(aMLine), aCandidate.c_str());
@@ -873,16 +874,31 @@ PeerConnectionMedia::AddIceCandidate_s(c
   if (NS_FAILED(rv)) {
     CSFLogError(logTag, "Couldn't process ICE candidate at level %u",
                 static_cast<unsigned>(aMLine));
     return;
   }
 }
 
 void
+PeerConnectionMedia::UpdateNetworkState(bool online) {
+  RUN_ON_THREAD(GetSTSThread(),
+                WrapRunnable(
+                    RefPtr<PeerConnectionMedia>(this),
+                    &PeerConnectionMedia::UpdateNetworkState_s,
+                    online),
+                NS_DISPATCH_NORMAL);
+}
+
+void
+PeerConnectionMedia::UpdateNetworkState_s(bool online) {
+  mIceCtxHdlr->ctx()->UpdateNetworkState(online);
+}
+
+void
 PeerConnectionMedia::FlushIceCtxOperationQueueIfReady()
 {
   ASSERT_ON_THREAD(mMainThread);
 
   if (IsIceCtxReady()) {
     for (auto i = mQueuedIceCtxOperations.begin();
          i != mQueuedIceCtxOperations.end();
          ++i) {
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
@@ -287,16 +287,19 @@ class PeerConnectionMedia : public sigsl
   void FinalizeIceRestart();
   // Abort ICE restart
   void RollbackIceRestart();
 
   // Process a trickle ICE candidate.
   void AddIceCandidate(const std::string& candidate, const std::string& mid,
                        uint32_t aMLine);
 
+  // Handle notifications of network online/offline events.
+  void UpdateNetworkState(bool online);
+
   // Handle complete media pipelines.
   nsresult UpdateMediaPipelines(const JsepSession& session);
 
   // Add a track (main thread only)
   nsresult AddTrack(DOMMediaStream& aMediaStream,
                     const std::string& streamId,
                     dom::MediaStreamTrack& aTrack,
                     const std::string& trackId);
@@ -486,16 +489,17 @@ class PeerConnectionMedia : public sigsl
   bool GetPrefProxyOnly() const;
 
   void ConnectSignals(NrIceCtx *aCtx, NrIceCtx *aOldCtx=nullptr);
 
   // Process a trickle ICE candidate.
   void AddIceCandidate_s(const std::string& aCandidate, const std::string& aMid,
                          uint32_t aMLine);
 
+  void UpdateNetworkState_s(bool online);
 
   // ICE events
   void IceGatheringStateChange_s(NrIceCtx* ctx,
                                NrIceCtx::GatheringState state);
   void IceConnectionStateChange_s(NrIceCtx* ctx,
                                 NrIceCtx::ConnectionState state);
   void IceStreamReady_s(NrIceMediaStream *aStream);
   void OnCandidateFound_s(NrIceMediaStream *aStream,