Bug 1378123 - Make inner window track whether there is an active PeerConnection. draft
authorHenri Sivonen <hsivonen@hsivonen.fi>
Fri, 04 Aug 2017 13:28:15 +0300
changeset 648749 89cf86263ed6ef94fc22d941367043454ca03eb6
parent 645835 72d456d328c7c3c9c0da55a42558ed42bbda7fc1
child 726929 aee694e7aa52f16a55b6ea02ec6d285159925d8f
push id74870
push userbmo:hsivonen@hsivonen.fi
push dateFri, 18 Aug 2017 08:53:38 +0000
bugs1378123
milestone57.0a1
Bug 1378123 - Make inner window track whether there is an active PeerConnection. MozReview-Commit-ID: 98Hwhnxtt1T
dom/base/TimeoutManager.cpp
dom/base/nsDocument.cpp
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/base/nsPIDOMWindow.h
dom/media/PeerConnection.js
dom/media/bridge/IPeerConnection.idl
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
--- a/dom/base/TimeoutManager.cpp
+++ b/dom/base/TimeoutManager.cpp
@@ -15,20 +15,16 @@
 #include "nsITimeoutHandler.h"
 #include "mozilla/dom/TabGroup.h"
 #include "OrderedTimeoutIterator.h"
 #include "TimeoutExecutor.h"
 #include "TimeoutBudgetManager.h"
 #include "mozilla/net/WebSocketEventService.h"
 #include "mozilla/MediaManager.h"
 
-#ifdef MOZ_WEBRTC
-#include "IPeerConnection.h"
-#endif // MOZ_WEBRTC
-
 using namespace mozilla;
 using namespace mozilla::dom;
 
 static LazyLogModule gLog("Timeout");
 
 static int32_t              gRunningTimeoutDepth       = 0;
 
 // The default shortest interval/timeout we permit
@@ -1238,36 +1234,22 @@ TimeoutManager::BudgetThrottlingEnabled(
   }
 
   // Check if we have active GetUserMedia
   if (MediaManager::Exists() &&
       MediaManager::Get()->IsWindowStillActive(mWindow.WindowID())) {
     return false;
   }
 
-  bool active = false;
-#if 0
-  // Check if we have active PeerConnections This doesn't actually
-  // work, since we sometimes call IsActive from Resume, which in turn
-  // is sometimes called from nsGlobalWindow::LeaveModalState. The
-  // problem here is that LeaveModalState can be called with pending
-  // exeptions on the js context, and the following call to
-  // HasActivePeerConnection is a JS call, which will assert on that
-  // exception. Also, calling JS is expensive so we should try to fix
-  // this in some other way.
-  nsCOMPtr<IPeerConnectionManager> pcManager =
-    do_GetService(IPEERCONNECTION_MANAGER_CONTRACTID);
-
-  if (pcManager && NS_SUCCEEDED(pcManager->HasActivePeerConnection(
-                     mWindow.WindowID(), &active)) &&
-      active) {
+  // Check if we have active PeerConnection
+  if (mWindow.AsInner()->HasActivePeerConnections()) {
     return false;
   }
-#endif // MOZ_WEBRTC
 
+  bool active;
   // Check if we have web sockets
   RefPtr<WebSocketEventService> eventService = WebSocketEventService::Get();
   if (eventService &&
       NS_SUCCEEDED(eventService->HasListenerFor(mWindow.WindowID(), &active)) &&
       active) {
     return false;
   }
 
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -267,19 +267,16 @@
 #endif
 #include "nsIPresShellInlines.h"
 
 #include "mozilla/DocLoadingTimelineMarker.h"
 
 #include "nsISpeculativeConnect.h"
 
 #include "mozilla/MediaManager.h"
-#ifdef MOZ_WEBRTC
-#include "IPeerConnection.h"
-#endif // MOZ_WEBRTC
 
 #include "nsIURIClassifier.h"
 #include "mozilla/DocumentStyleRootIterator.h"
 #include "mozilla/ServoRestyleManager.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
@@ -8742,25 +8739,18 @@ nsDocument::CanSavePresentation(nsIReque
   // Check if we have active GetUserMedia use
   if (MediaManager::Exists() && win &&
       MediaManager::Get()->IsWindowStillActive(win->WindowID())) {
     return false;
   }
 
 #ifdef MOZ_WEBRTC
   // Check if we have active PeerConnections
-  nsCOMPtr<IPeerConnectionManager> pcManager =
-    do_GetService(IPEERCONNECTION_MANAGER_CONTRACTID);
-
-  if (pcManager && win) {
-    bool active;
-    pcManager->HasActivePeerConnection(win->WindowID(), &active);
-    if (active) {
-      return false;
-    }
+  if (win && win->HasActivePeerConnections()) {
+    return false;
   }
 #endif // MOZ_WEBRTC
 
   // Don't save presentations for documents containing EME content, so that
   // CDMs reliably shutdown upon user navigation.
   if (ContainsEMEContent()) {
     return false;
   }
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -1014,17 +1014,17 @@ namespace dom {
 extern uint64_t
 NextWindowID();
 } // namespace dom
 } // namespace mozilla
 
 template<class T>
 nsPIDOMWindow<T>::nsPIDOMWindow(nsPIDOMWindowOuter *aOuterWindow)
 : mFrameElement(nullptr), mDocShell(nullptr), mModalStateDepth(0),
-  mMutationBits(0), mIsDocumentLoaded(false),
+  mMutationBits(0), mActivePeerConnections(0), mIsDocumentLoaded(false),
   mIsHandlingResizeEvent(false), mIsInnerWindow(aOuterWindow != nullptr),
   mMayHavePaintEventListener(false), mMayHaveTouchEventListener(false),
   mMayHaveSelectionChangeEventListener(false),
   mMayHaveMouseEnterLeaveEventListener(false),
   mMayHavePointerEnterLeaveEventListener(false),
   mInnerObjectsFreed(false),
   mIsActive(false), mIsBackground(false),
   mMediaSuspend(Preferences::GetBool("media.block-autoplay-until-in-foreground", true) ?
@@ -4373,16 +4373,34 @@ nsPIDOMWindowInner::Thaw()
 }
 
 void
 nsPIDOMWindowInner::SyncStateFromParentWindow()
 {
   nsGlobalWindow::Cast(this)->SyncStateFromParentWindow();
 }
 
+void
+nsPIDOMWindowInner::AddPeerConnection()
+{
+  nsGlobalWindow::Cast(this)->AddPeerConnection();
+}
+
+void
+nsPIDOMWindowInner::RemovePeerConnection()
+{
+  nsGlobalWindow::Cast(this)->RemovePeerConnection();
+}
+
+bool
+nsPIDOMWindowInner::HasActivePeerConnections()
+{
+  return nsGlobalWindow::Cast(this)->HasActivePeerConnections();
+}
+
 bool
 nsPIDOMWindowInner::IsPlayingAudio()
 {
   for (uint32_t i = 0; i < mAudioContexts.Length(); i++) {
     if (mAudioContexts[i]->IsRunning()) {
       return true;
     }
   }
@@ -12558,16 +12576,41 @@ nsGlobalWindow::SyncStateFromParentWindo
 
   // Now apply only the number of Suspend() calls to reach the target
   // suspend count after applying the Freeze() calls.
   for (uint32_t i = 0; i < (parentSuspendDepth - parentFreezeDepth); ++i) {
     Suspend();
   }
 }
 
+void
+nsGlobalWindow::AddPeerConnection()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(IsInnerWindow());
+  mActivePeerConnections++;
+}
+
+void
+nsGlobalWindow::RemovePeerConnection()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(IsInnerWindow());
+  MOZ_ASSERT(mActivePeerConnections);
+  mActivePeerConnections--;
+}
+
+bool
+nsGlobalWindow::HasActivePeerConnections()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(IsInnerWindow());
+  return mActivePeerConnections;
+}
+
 template<typename Method>
 void
 nsGlobalWindow::CallOnChildren(Method aMethod)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(IsInnerWindow());
   MOZ_ASSERT(AsInner()->IsCurrentInnerWindow());
 
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -400,16 +400,19 @@ public:
 
   void Suspend();
   void Resume();
   virtual bool IsSuspended() const override;
   void Freeze();
   void Thaw();
   virtual bool IsFrozen() const override;
   void SyncStateFromParentWindow();
+  void AddPeerConnection();
+  void RemovePeerConnection();
+  bool HasActivePeerConnections();
 
   virtual nsresult FireDelayedDOMEvents() override;
 
   // Outer windows only.
   virtual bool WouldReuseInnerWindow(nsIDocument* aNewDocument) override;
 
   virtual void SetDocShell(nsIDocShell* aDocShell) override;
   virtual void DetachFromDocShell() override;
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -653,16 +653,18 @@ protected:
           ServiceWorkerRegistrationTable;
   ServiceWorkerRegistrationTable mServiceWorkerRegistrationTable;
 
   uint32_t               mModalStateDepth;
 
   // These variables are only used on inner windows.
   uint32_t               mMutationBits;
 
+  uint32_t               mActivePeerConnections;
+
   bool                   mIsDocumentLoaded;
   bool                   mIsHandlingResizeEvent;
   bool                   mIsInnerWindow;
   bool                   mMayHavePaintEventListener;
   bool                   mMayHaveTouchEventListener;
   bool                   mMayHaveSelectionChangeEventListener;
   bool                   mMayHaveMouseEnterLeaveEventListener;
   bool                   mMayHavePointerEnterLeaveEventListener;
@@ -883,16 +885,31 @@ public:
   // calls.
   void Freeze();
   void Thaw();
 
   // Apply the parent window's suspend, freeze, and modal state to the current
   // window.
   void SyncStateFromParentWindow();
 
+  /**
+   * Increment active peer connection count.
+   */
+  void AddPeerConnection();
+
+  /**
+   * Decrement active peer connection count.
+   */
+  void RemovePeerConnection();
+
+  /**
+   * Check whether the active peer connection count is non-zero.
+   */
+  bool HasActivePeerConnections();
+
   bool IsPlayingAudio();
 
   bool IsDocumentLoaded() const;
 
   mozilla::dom::TimeoutManager& TimeoutManager();
 
   bool IsRunningTimeout();
 
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -114,21 +114,16 @@ class GlobalPCList {
     this._list[winID] = this._list[winID].filter(
       function(e, i, a) { return e.get() !== null; });
 
     if (this._list[winID].length === 0) {
       delete this._list[winID];
     }
   }
 
-  hasActivePeerConnection(winID) {
-    this.removeNullRefs(winID);
-    return !!this._list[winID];
-  }
-
   handleGMPCrash(data) {
     let broadcastPluginCrash = function(list, winID, pluginID, pluginName) {
       if (list.hasOwnProperty(winID)) {
         list[winID].forEach(function(pcref) {
           let pc = pcref.get();
           if (pc) {
             pc._pc.pluginCrash(pluginID, pluginName);
           }
@@ -209,18 +204,17 @@ class GlobalPCList {
 
   _registerPeerConnectionLifecycleCallback(winID, cb) {
     this._lifecycleobservers[winID] = cb;
   }
 }
 setupPrototype(GlobalPCList, {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsIMessageListener,
-                                         Ci.nsISupportsWeakReference,
-                                         Ci.IPeerConnectionManager]),
+                                         Ci.nsISupportsWeakReference]),
   classID: PC_MANAGER_CID,
   _xpcom_factory: {
     createInstance(outer, iid) {
       if (outer) {
         throw Cr.NS_ERROR_NO_AGGREGATION;
       }
       return _globalPCList.QueryInterface(iid);
     }
--- a/dom/media/bridge/IPeerConnection.idl
+++ b/dom/media/bridge/IPeerConnection.idl
@@ -1,27 +1,14 @@
 #include "nsIThread.idl"
 #include "nsIDOMWindow.idl"
 #include "nsIPropertyBag2.idl"
 
 interface nsIDOMDataChannel;
 
-/*
- * Manager interface to PeerConnection.js so it is accessible from C++.
- */
-[scriptable, uuid(c2218bd2-2648-4701-8fa6-305d3379e9f8)]
-interface IPeerConnectionManager : nsISupports
-{
-  boolean hasActivePeerConnection(in unsigned long innerWindowID);
-};
-
-%{C++
-#define IPEERCONNECTION_MANAGER_CONTRACTID "@mozilla.org/dom/peerconnectionmanager;1"
-%}
-
 /* Do not confuse with nsIDOMRTCPeerConnection. This interface is purely for
  * communication between the PeerConnection JS DOM binding and the C++
  * implementation in SIPCC.
  *
  * See media/webrtc/signaling/include/PeerConnectionImpl.h
  */
 [scriptable, uuid(d7dfe148-0416-446b-a128-66a7c71ae8d3)]
 interface IPeerConnectionObserver : nsISupports
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -1,8 +1,9 @@
+
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <cstdlib>
 #include <cerrno>
 #include <deque>
 #include <set>
@@ -310,25 +311,28 @@ PeerConnectionImpl::PeerConnectionImpl(c
   , mMedia(nullptr)
   , mUuidGen(MakeUnique<PCUuidGenerator>())
   , mHaveConfiguredCodecs(false)
   , mHaveDataStream(false)
   , mAddCandidateErrorCount(0)
   , mTrickle(true) // TODO(ekr@rtfm.com): Use pref
   , mNegotiationNeeded(false)
   , mPrivateWindow(false)
+  , mActiveOnWindow(false)
 {
   MOZ_ASSERT(NS_IsMainThread());
   auto log = RLogConnector::CreateInstance();
   if (aGlobal) {
     mWindow = do_QueryInterface(aGlobal->GetAsSupports());
     if (IsPrivateBrowsing(mWindow)) {
       mPrivateWindow = true;
       log->EnterPrivateMode();
     }
+    mWindow->AddPeerConnection();
+    mActiveOnWindow = true;
   }
   CSFLogInfo(logTag, "%s: PeerConnectionImpl constructor for %s",
              __FUNCTION__, mHandle.c_str());
   STAMP_TIMECARD(mTimeCard, "Constructor Completed");
   mAllowIceLoopback = Preferences::GetBool(
     "media.peerconnection.ice.loopback", false);
   mAllowIceLinkLocal = Preferences::GetBool(
     "media.peerconnection.ice.link_local", false);
@@ -343,16 +347,24 @@ PeerConnectionImpl::~PeerConnectionImpl(
   if (mTimeCard) {
     STAMP_TIMECARD(mTimeCard, "Destructor Invoked");
     print_timecard(mTimeCard);
     destroy_timecard(mTimeCard);
     mTimeCard = nullptr;
   }
   // This aborts if not on main thread (in Debug builds)
   PC_AUTO_ENTER_API_CALL_NO_CHECK();
+  if (mWindow && mActiveOnWindow) {
+    mWindow->RemovePeerConnection();
+    // No code is supposed to observe the assignment below, but
+    // hopefully it makes looking at this object in a debugger
+    // make more sense.
+    mActiveOnWindow = false;
+  }
+
   if (mPrivateWindow) {
     auto * log = RLogConnector::GetInstance();
     if (log) {
       log->ExitPrivateMode();
     }
     mPrivateWindow = false;
   }
   if (PeerConnectionCtx::isActive()) {
@@ -3138,16 +3150,21 @@ PeerConnectionImpl::SetSignalingState_m(
       if (mMaxSending[i] < sending[i]) {
         mMaxSending[i] = sending[i];
       }
     }
   }
 
   if (mSignalingState == PCImplSignalingState::SignalingClosed) {
     CloseInt();
+    // Uncount this connection as active on the inner window upon close.
+    if (mWindow && mActiveOnWindow) {
+      mWindow->RemovePeerConnection();
+      mActiveOnWindow = false;
+    }
   }
 
   RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
   if (!pco) {
     return;
   }
   JSErrorResult rv;
   pco->OnStateChange(PCObserverStateType::SignalingState, rv);
@@ -3390,16 +3407,22 @@ void PeerConnectionImpl::IceConnectionSt
   if (mIceConnectionState == PCImplIceConnectionState::Connected ||
       mIceConnectionState == PCImplIceConnectionState::Completed ||
       mIceConnectionState == PCImplIceConnectionState::Failed) {
     if (mMedia->IsIceRestarting()) {
       FinalizeIceRestart();
     }
   }
 
+  // Uncount this connection as active on the inner window upon close.
+  if (mWindow && mActiveOnWindow && mIceConnectionState == PCImplIceConnectionState::Closed) {
+    mWindow->RemovePeerConnection();
+    mActiveOnWindow = false;
+  }
+
   // Would be nice if we had a means of converting one of these dom enums
   // to a string that wasn't almost as much text as this switch statement...
   switch (mIceConnectionState) {
     case PCImplIceConnectionState::New:
       STAMP_TIMECARD(mTimeCard, "Ice state: new");
       break;
     case PCImplIceConnectionState::Checking:
       // For telemetry
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -784,16 +784,19 @@ private:
   unsigned int mAddCandidateErrorCount;
 
   bool mTrickle;
 
   bool mNegotiationNeeded;
 
   bool mPrivateWindow;
 
+  // Whether this PeerConnection is being counted as active by mWindow
+  bool mActiveOnWindow;
+
   // storage for Telemetry data
   uint16_t mMaxReceiving[SdpMediaSection::kMediaTypes];
   uint16_t mMaxSending[SdpMediaSection::kMediaTypes];
 
   // DTMF
   struct DTMFState {
     PeerConnectionImpl* mPeerConnectionImpl;
     nsCOMPtr<nsITimer> mSendTimer;