Bug 1320994 - Refactor MediaManager's window management. r?jib draft
authorAndreas Pehrson <pehrsons@gmail.com>
Wed, 26 Apr 2017 11:13:40 +0200
changeset 572749 ca7ef5d9e197e7c0cc07d9e76f9e16f336914771
parent 572748 040f30c7fce6cdefdd63d01acca819d06fa6d38f
child 572750 aaee13c926fe82ca010168557624912571292a8f
push id57178
push userbmo:pehrson@telenordigital.com
push dateThu, 04 May 2017 17:44:26 +0000
reviewersjib
bugs1320994
milestone55.0a1
Bug 1320994 - Refactor MediaManager's window management. r?jib This in large does the following: * Changes the model in MediaManager to align with that of chrome code, namely: - One GetUserMediaWindowListener *per window*, containing N SourceListeners for N gUM requests, and - GetUserMediaWindowListener replaces GetUserMediaStreamListener - So if two SourceListeners stop we can still send only one event * Breaks a special event specific to B2G chrome MozReview-Commit-ID: 3wbPFmc9yWj
dom/media/MediaManager.cpp
dom/media/MediaManager.h
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -152,474 +152,514 @@ using dom::Promise;
 using dom::Sequence;
 using media::NewRunnableFrom;
 using media::NewTaskFrom;
 using media::Pledge;
 using media::Refcountable;
 
 static Atomic<bool> sInShutdown;
 
+typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
+
 static bool
 HostIsHttps(nsIURI &docURI)
 {
   bool isHttps;
   nsresult rv = docURI.SchemeIs("https", &isHttps);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
   return isHttps;
 }
 
-/**
- * This class is an implementation of MediaStreamListener. This is used
- * to Start() and Stop() the underlying MediaEngineSource when MediaStreams
- * are assigned and deassigned in content.
- */
-class GetUserMediaCallbackMediaStreamListener : public MediaStreamListener
-{
-  friend MediaManager;
+class SourceListener : public MediaStreamListener {
 public:
-  // Create in an inactive state
-  GetUserMediaCallbackMediaStreamListener(base::Thread *aThread,
-    uint64_t aWindowID,
-    const PrincipalHandle& aPrincipalHandle)
-    : mMediaThread(aThread)
-    , mMainThreadCheck(nullptr)
-    , mWindowID(aWindowID)
-    , mPrincipalHandle(aPrincipalHandle)
-    , mStopped(false)
-    , mFinished(false)
-    , mRemoved(false)
-    , mAudioStopped(false)
-    , mAudioStopPending(false)
-    , mVideoStopped(false)
-    , mVideoStopPending(false)
-    , mChromeNotificationTaskPosted(false)
-  {}
-
-  ~GetUserMediaCallbackMediaStreamListener()
-  {
-    Unused << mMediaThread;
-    // It's OK to release mStream on any thread; they have thread-safe
-    // refcounts.
-  }
-
-  void Activate(already_AddRefed<SourceMediaStream> aStream,
+  SourceListener();
+
+  /**
+   * Registers this source listener as belonging to the given window listener.
+   */
+  void Register(GetUserMediaWindowListener* aListener);
+
+  /**
+   * Marks this listener as active and adds itself as a listener to aStream.
+   */
+  void Activate(SourceMediaStream* aStream,
                 AudioDevice* aAudioDevice,
-                VideoDevice* aVideoDevice)
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    mMainThreadCheck = PR_GetCurrentThread();
-    mStream = aStream;
-    mAudioDevice = aAudioDevice;
-    mVideoDevice = aVideoDevice;
-
-    mStream->AddListener(this);
-  }
-
-  MediaStream *Stream() // Can be used to test if Activate was called
+                VideoDevice* aVideoDevice);
+
+  /**
+   * Stops all live tracks, finishes the associated MediaStream and cleans up.
+   */
+  void Stop();
+
+  /**
+   * Removes this SourceListener from its associated MediaStream and marks it
+   * removed. Also removes the weak reference to the associated window listener.
+   */
+  void Remove();
+
+  /**
+   * Posts a task to stop the device associated with aTrackID and notifies the
+   * associated window listener that a track was stopped.
+   * Should this track be the last live one to be stopped, we'll also clean up.
+   */
+  void StopTrack(TrackID aTrackID);
+
+  /**
+   * Stops all screen/app/window/audioCapture sharing, but not camera or
+   * microphone.
+   */
+  void StopSharing();
+
+  MediaStream* Stream() const
   {
     return mStream;
   }
-  SourceMediaStream *GetSourceStream()
+
+  SourceMediaStream* GetSourceStream();
+
+  AudioDevice* GetAudioDevice() const
   {
-    NS_ASSERTION(mStream,"Getting stream from never-activated GUMCMSListener");
-    if (!mStream) {
-      return nullptr;
-    }
-    return mStream->AsSourceStream();
+    return mAudioDevice;
+  }
+
+  VideoDevice* GetVideoDevice() const
+  {
+    return mVideoDevice;
   }
 
-  void StopSharing();
-
-  void StopTrack(TrackID aID);
-
-  void NotifyChromeOfTrackStops();
-
-  typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
+  void GetSettings(dom::MediaTrackSettings& aOutSettings, TrackID aTrackID);
+
+  void NotifyPull(MediaStreamGraph* aGraph,
+                  StreamTime aDesiredTime) override;
+
+  void NotifyEvent(MediaStreamGraph* aGraph,
+                   MediaStreamGraphEvent aEvent) override;
+
+  void NotifyFinished();
+
+  /**
+   * this can be in response to our own RemoveListener() (via ::Remove()), or
+   * because the DOM GC'd the DOMLocalMediaStream/etc we're attached to.
+   */
+  void NotifyRemoved();
+
+  void NotifyDirectListeners(MediaStreamGraph* aGraph, bool aHasListeners);
+
+  bool Activated() const
+  {
+    return mActivated;
+  }
+
+  bool Stopped() const
+  {
+    return mStopped;
+  }
+
+  bool CapturingVideo() const;
+
+  bool CapturingAudio() const;
+
+  bool CapturingScreen() const;
+
+  bool CapturingWindow() const;
+
+  bool CapturingApplication() const;
+
+  bool CapturingBrowser() const;
 
   already_AddRefed<PledgeVoid>
   ApplyConstraintsToTrack(nsPIDOMWindowInner* aWindow,
-                          TrackID aID,
+                          TrackID aTrackID,
                           const dom::MediaTrackConstraints& aConstraints,
                           dom::CallerType aCallerType);
 
-  // mVideo/AudioDevice are set by Activate(), so we assume they're capturing
-  // if set and represent a real capture device.
-  bool CapturingVideo()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    return mVideoDevice && !mStopped &&
-           !mVideoDevice->GetSource()->IsAvailable() &&
-           mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Camera &&
-           (!mVideoDevice->GetSource()->IsFake() ||
-            Preferences::GetBool("media.navigator.permission.fake"));
-  }
-  bool CapturingAudio()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    return mAudioDevice && !mStopped &&
-           !mAudioDevice->GetSource()->IsAvailable() &&
-           (!mAudioDevice->GetSource()->IsFake() ||
-            Preferences::GetBool("media.navigator.permission.fake"));
-  }
-  bool CapturingScreen()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    return mVideoDevice && !mStopped &&
-           !mVideoDevice->GetSource()->IsAvailable() &&
-           mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Screen;
-  }
-  bool CapturingWindow()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    return mVideoDevice && !mStopped &&
-           !mVideoDevice->GetSource()->IsAvailable() &&
-           mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Window;
-  }
-  bool CapturingApplication()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    return mVideoDevice && !mStopped &&
-           !mVideoDevice->GetSource()->IsAvailable() &&
-           mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Application;
-  }
-  bool CapturingBrowser()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    return mVideoDevice && !mStopped &&
-           mVideoDevice->GetSource()->IsAvailable() &&
-           mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Browser;
-  }
-
-  void GetSettings(dom::MediaTrackSettings& aOutSettings, TrackID aTrackID)
-  {
-    switch (aTrackID) {
-      case kVideoTrack:
-        if (mVideoDevice) {
-          mVideoDevice->GetSource()->GetSettings(aOutSettings);
-        }
-        break;
-
-      case kAudioTrack:
-        if (mAudioDevice) {
-          mAudioDevice->GetSource()->GetSettings(aOutSettings);
-        }
-        break;
-    }
-  }
-
-  // implement in .cpp to avoid circular dependency with MediaOperationTask
-  // Can be invoked from EITHER MainThread or MSG thread
-  void Stop();
-
-  void
-  AudioConfig(bool aEchoOn, uint32_t aEcho,
-              bool aAgcOn, uint32_t aAGC,
-              bool aNoiseOn, uint32_t aNoise,
-              int32_t aPlayoutDelay);
-
-  void
-  Remove()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    // allow calling even if inactive (!mStream) for easier cleanup
-    // Caller holds strong reference to us, so no death grip required
-    if (mStream && !mRemoved) {
-      MM_LOG(("Listener removed on purpose, mFinished = %d", (int) mFinished));
-      mRemoved = true; // RemoveListener is async, avoid races
-      // If it's destroyed, don't call - listener will be removed and we'll be notified!
-      if (!mStream->IsDestroyed()) {
-        mStream->RemoveListener(this);
-      }
-    }
-  }
-
-  // Proxy NotifyPull() to sources
-  void
-  NotifyPull(MediaStreamGraph* aGraph, StreamTime aDesiredTime) override
-  {
-    // Currently audio sources ignore NotifyPull, but they could
-    // watch it especially for fake audio.
-    if (mAudioDevice) {
-      mAudioDevice->GetSource()->NotifyPull(aGraph, mStream, kAudioTrack,
-                                            aDesiredTime, mPrincipalHandle);
-    }
-    if (mVideoDevice) {
-      mVideoDevice->GetSource()->NotifyPull(aGraph, mStream, kVideoTrack,
-                                            aDesiredTime, mPrincipalHandle);
-    }
-  }
-
-  void
-  NotifyEvent(MediaStreamGraph* aGraph,
-              MediaStreamGraphEvent aEvent) override
-  {
-    nsresult rv;
-    nsCOMPtr<nsIThread> thread;
-
-    switch (aEvent) {
-      case MediaStreamGraphEvent::EVENT_FINISHED:
-        rv = NS_GetMainThread(getter_AddRefs(thread));
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          NS_ASSERTION(false, "Mainthread not available; running on current thread");
-          // Ensure this really *was* MainThread (NS_GetCurrentThread won't work)
-          MOZ_RELEASE_ASSERT(mMainThreadCheck == PR_GetCurrentThread());
-          NotifyFinished();
-          return;
-        }
-        thread->Dispatch(NewRunnableMethod(this, &GetUserMediaCallbackMediaStreamListener::NotifyFinished),
-                         NS_DISPATCH_NORMAL);
-        break;
-      case MediaStreamGraphEvent::EVENT_REMOVED:
-        rv = NS_GetMainThread(getter_AddRefs(thread));
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          NS_ASSERTION(false, "Mainthread not available; running on current thread");
-          // Ensure this really *was* MainThread (NS_GetCurrentThread won't work)
-          MOZ_RELEASE_ASSERT(mMainThreadCheck == PR_GetCurrentThread());
-          NotifyRemoved();
-          return;
-        }
-        thread->Dispatch(NewRunnableMethod(this, &GetUserMediaCallbackMediaStreamListener::NotifyRemoved),
-                         NS_DISPATCH_NORMAL);
-        break;
-      case MediaStreamGraphEvent::EVENT_HAS_DIRECT_LISTENERS:
-        NotifyDirectListeners(aGraph, true);
-        break;
-      case MediaStreamGraphEvent::EVENT_HAS_NO_DIRECT_LISTENERS:
-        NotifyDirectListeners(aGraph, false);
-        break;
-      default:
-        break;
-    }
-  }
-
-  void
-  NotifyFinished();
-
-  void
-  NotifyRemoved();
-
-  void
-  NotifyDirectListeners(MediaStreamGraph* aGraph, bool aHasListeners);
-
-  PrincipalHandle GetPrincipalHandle() const { return mPrincipalHandle; }
+  PrincipalHandle GetPrincipalHandle() const;
 
 private:
-  // Set at construction
-  base::Thread* mMediaThread;
-  // never ever indirect off this; just for assertions
-  PRThread* mMainThreadCheck;
-
-  uint64_t mWindowID;
-  const PrincipalHandle mPrincipalHandle;
-
-  // true after this listener has sent MEDIA_STOP. MainThread only.
+  // true after this listener has been Activate()d in a WindowListener.
+  // MainThread only.
+  bool mActivated;
+
+  // true after this listener has had all devices stopped. MainThread only.
   bool mStopped;
 
   // true after the stream this listener is listening to has finished in the
   // MediaStreamGraph. MainThread only.
   bool mFinished;
 
   // true after this listener has been removed from its MediaStream.
   // MainThread only.
   bool mRemoved;
 
-  // true if we have sent MEDIA_STOP or MEDIA_STOP_TRACK for mAudioDevice.
-  // MainThread only.
+  // true if we have stopped mAudioDevice. MainThread only.
   bool mAudioStopped;
 
-  // true if we have scheduled MEDIA_STOP or MEDIA_STOP_TRACK for mAudioDevice.
-  // MainThread only.
-  bool mAudioStopPending;
-
-  // true if we have sent MEDIA_STOP or MEDIA_STOP_TRACK for mVideoDevice.
-  // MainThread only.
+  // true if we have stopped mVideoDevice. MainThread only.
   bool mVideoStopped;
 
-  // true if we have scheduled MEDIA_STOP or MEDIA_STOP_TRACK for mVideoDevice.
-  // MainThread only.
-  bool mVideoStopPending;
-
-  // true if we have scheduled a task to notify chrome in the next stable state.
-  // The task will reset this to false. MainThread only.
-  bool mChromeNotificationTaskPosted;
+  // never ever indirect off this; just for assertions
+  PRThread* mMainThreadCheck;
+
+  // Set in Register() on main thread, then read from any thread.
+  PrincipalHandle mPrincipalHandle;
+
+  // Weak pointer to the window listener that owns us. MainThread only.
+  GetUserMediaWindowListener* mWindowListener;
 
   // Set at Activate on MainThread
 
   // Accessed from MediaStreamGraph thread, MediaManager thread, and MainThread
   // No locking needed as they're only addrefed except on the MediaManager thread
   RefPtr<AudioDevice> mAudioDevice; // threadsafe refcnt
   RefPtr<VideoDevice> mVideoDevice; // threadsafe refcnt
   RefPtr<SourceMediaStream> mStream; // threadsafe refcnt
 };
 
-// Generic class for running long media operations like Start off the main
-// thread, and then (because nsDOMMediaStreams aren't threadsafe),
-// ProxyReleases mStream since it's cycle collected.
-class MediaOperationTask : public Runnable
+/**
+ * This class represents a WindowID and handles all MediaStreamListeners
+ * (here subclassed as SourceListeners) used to feed GetUserMedia source
+ * streams. It proxies feedback from them into messages for browser chrome.
+ * The SourceListeners are used to Start() and Stop() the underlying
+ * MediaEngineSource when MediaStreams are assigned and deassigned in content.
+ */
+class GetUserMediaWindowListener
 {
+  friend MediaManager;
 public:
-  // so we can send Stop without AddRef()ing from the MSG thread
-  MediaOperationTask(MediaOperation aType,
-    GetUserMediaCallbackMediaStreamListener* aListener,
-    DOMMediaStream* aStream,
-    OnTracksAvailableCallback* aOnTracksAvailableCallback,
-    AudioDevice* aAudioDevice,
-    VideoDevice* aVideoDevice,
-    bool aBool,
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GetUserMediaWindowListener)
+
+  // Create in an inactive state
+  GetUserMediaWindowListener(base::Thread *aThread,
     uint64_t aWindowID,
-    already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
-    const dom::MediaTrackConstraints& aConstraints = dom::MediaTrackConstraints())
-    : mType(aType)
-    , mStream(aStream)
-    , mOnTracksAvailableCallback(aOnTracksAvailableCallback)
-    , mAudioDevice(aAudioDevice)
-    , mVideoDevice(aVideoDevice)
-    , mListener(aListener)
-    , mBool(aBool)
+    const PrincipalHandle& aPrincipalHandle)
+    : mMediaThread(aThread)
     , mWindowID(aWindowID)
-    , mOnFailure(aError)
-    , mConstraints(aConstraints)
+    , mPrincipalHandle(aPrincipalHandle)
+    , mChromeNotificationTaskPosted(false)
   {}
 
-  ~MediaOperationTask()
+  /**
+   * Registers an inactive gUM source listener for this WindowListener.
+   */
+  void Register(SourceListener* aListener)
   {
-    // MediaStreams can be released on any thread.
+    MOZ_ASSERT(NS_IsMainThread());
+    if (!aListener || aListener->Activated()) {
+      MOZ_ASSERT(false, "Invalid listener");
+      return;
+    }
+    if (mInactiveListeners.Contains(aListener)) {
+      MOZ_ASSERT(false, "Already registered");
+      return;
+    }
+    if (mActiveListeners.Contains(aListener)) {
+      MOZ_ASSERT(false, "Already activated");
+      return;
+    }
+
+    aListener->Register(this);
+    mInactiveListeners.AppendElement(aListener);
   }
 
-  void
-  ReturnCallbackError(nsresult rv, const char* errorLog);
-
-  NS_IMETHOD
-  Run() override
+  /**
+   * Activates an already registered and inactive gUM source listener for this
+   * WindowListener.
+   */
+  void Activate(SourceListener* aListener,
+                SourceMediaStream* aStream,
+                AudioDevice* aAudioDevice,
+                VideoDevice* aVideoDevice)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (!aListener || aListener->Activated()) {
+      MOZ_ASSERT(false, "Cannot activate already activated source listener");
+      return;
+    }
+
+    if (!mInactiveListeners.RemoveElement(aListener)) {
+      MOZ_ASSERT(false, "Cannot activate non-registered source listener");
+      return;
+    }
+
+    RefPtr<SourceListener> listener = aListener;
+    listener->Activate(aStream, aAudioDevice, aVideoDevice);
+    mActiveListeners.AppendElement(listener.forget());
+  }
+
+  // Can be invoked from EITHER MainThread or MSG thread
+  void Stop()
   {
-    SourceMediaStream *source = mListener->GetSourceStream();
-    // No locking between these is required as all the callbacks for the
-    // same MediaStream will occur on the same thread.
-    if (!source) // means the stream was never Activated()
-      return NS_OK;
-
-    switch (mType) {
-      case MEDIA_START:
-        {
-          NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
-          nsresult rv;
-
-          if (mAudioDevice) {
-            rv = mAudioDevice->GetSource()->Start(source, kAudioTrack,
-                                                  mListener->GetPrincipalHandle());
-            if (NS_FAILED(rv)) {
-              ReturnCallbackError(rv, "Starting audio failed");
-              return NS_OK;
-            }
-          }
-          if (mVideoDevice) {
-            rv = mVideoDevice->GetSource()->Start(source, kVideoTrack,
-                                                  mListener->GetPrincipalHandle());
-            if (NS_FAILED(rv)) {
-              ReturnCallbackError(rv, "Starting video failed");
-              return NS_OK;
-            }
-          }
-          // Start() queued the tracks to be added synchronously to avoid races
-          source->FinishAddTracks();
-
-          source->SetPullEnabled(true);
-          source->AdvanceKnownTracksTime(STREAM_TIME_MAX);
-
-          MM_LOG(("started all sources"));
-          // Forward mOnTracksAvailableCallback to GetUserMediaNotificationEvent,
-          // because mOnTracksAvailableCallback needs to be added to mStream
-          // on the main thread.
-          nsIRunnable *event =
-            new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STARTING,
-                                              mStream.forget(),
-                                              mOnTracksAvailableCallback.forget(),
-                                              mAudioDevice != nullptr,
-                                              mVideoDevice != nullptr,
-                                              mWindowID, mOnFailure.forget());
-          // event must always be released on mainthread due to the JS callbacks
-          // in the TracksAvailableCallback
-          NS_DispatchToMainThread(event);
-        }
-        break;
-
-      case MEDIA_STOP:
-      case MEDIA_STOP_TRACK:
-        {
-          NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
-          if (mAudioDevice) {
-            mAudioDevice->GetSource()->Stop(source, kAudioTrack);
-            mAudioDevice->Deallocate();
-          }
-          if (mVideoDevice) {
-            mVideoDevice->GetSource()->Stop(source, kVideoTrack);
-            mVideoDevice->Deallocate();
-          }
-          if (mType == MEDIA_STOP) {
-            source->EndAllTrackAndFinish();
-          }
-
-          nsIRunnable *event =
-            new GetUserMediaNotificationEvent(mListener,
-                                              mType == MEDIA_STOP ?
-                                              GetUserMediaNotificationEvent::STOPPING :
-                                              GetUserMediaNotificationEvent::STOPPED_TRACK,
-                                              mAudioDevice != nullptr,
-                                              mVideoDevice != nullptr,
-                                              mWindowID);
-          // event must always be released on mainthread due to the JS callbacks
-          // in the TracksAvailableCallback
-          NS_DispatchToMainThread(event);
-        }
-        break;
-
-      case MEDIA_DIRECT_LISTENERS:
-        {
-          NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
-          if (mVideoDevice) {
-            mVideoDevice->GetSource()->SetDirectListeners(mBool);
+    MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
+
+    for (auto& source : mActiveListeners) {
+      source->Stop();
+    }
+
+    // Once all tracks have stopped, that will trigger the chrome notification
+  }
+
+  /**
+   * Removes all SourceListeners from this window listener.
+   * Removes this window listener from the list of active windows, so callers
+   * need to make sure to hold a strong reference.
+   */
+  void RemoveAll()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    // Shallow copy since SourceListener::Remove() will modify the arrays.
+    nsTArray<RefPtr<SourceListener>> listeners(mInactiveListeners.Length()
+                                               + mActiveListeners.Length());
+    listeners.AppendElements(mInactiveListeners);
+    listeners.AppendElements(mActiveListeners);
+    for (auto& l : listeners) {
+      Remove(l);
+    }
+
+    MOZ_ASSERT(mInactiveListeners.Length() == 0);
+    MOZ_ASSERT(mActiveListeners.Length() == 0);
+
+    RefPtr<MediaManager> mgr = MediaManager::GetIfExists();
+    if (!mgr) {
+      MOZ_ASSERT(false, "MediaManager should stay until everything is removed");
+      return;
+    }
+    GetUserMediaWindowListener* windowListener =
+      mgr->GetWindowListener(mWindowID);
+
+    if (!windowListener) {
+      nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+      auto* globalWindow = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
+      if (globalWindow) {
+        RefPtr<GetUserMediaRequest> req =
+          new GetUserMediaRequest(globalWindow->AsInner(),
+                                  NullString(), NullString());
+        obs->NotifyObservers(req, "recording-device-stopped", nullptr);
+      }
+      return;
+    }
+
+    MOZ_ASSERT(windowListener == this,
+               "There should only be one window listener per window ID");
+
+    LOG(("GUMWindowListener %p removing windowID %" PRIu64, this, mWindowID));
+    mgr->RemoveWindowID(mWindowID);
+  }
+
+  bool Remove(SourceListener* aListener)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (!mInactiveListeners.RemoveElement(aListener) &&
+        !mActiveListeners.RemoveElement(aListener)) {
+      return false;
+    }
+
+    MOZ_ASSERT(!mInactiveListeners.Contains(aListener),
+               "A SourceListener should only be once in one of "
+               "mInactiveListeners and mActiveListeners");
+    MOZ_ASSERT(!mActiveListeners.Contains(aListener),
+               "A SourceListener should only be once in one of "
+               "mInactiveListeners and mActiveListeners");
+
+    LOG(("GUMWindowListener %p removing SourceListener %p.", this, aListener));
+    aListener->Remove();
+
+    if (VideoDevice* removedDevice = aListener->GetVideoDevice()) {
+      bool revokeVideoPermission = true;
+      nsString removedRawId;
+      nsString removedSourceType;
+      removedDevice->GetRawId(removedRawId);
+      removedDevice->GetMediaSource(removedSourceType);
+      for (const auto& l : mActiveListeners) {
+        if (VideoDevice* device = l->GetVideoDevice()) {
+          nsString rawId;
+          device->GetRawId(rawId);
+          if (removedRawId.Equals(rawId)) {
+            revokeVideoPermission = false;
+            break;
           }
         }
-        break;
-
-      default:
-        MOZ_ASSERT(false,"invalid MediaManager operation");
-        break;
+      }
+
+      if (revokeVideoPermission) {
+        nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+        auto* globalWindow = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
+        nsPIDOMWindowInner* window = globalWindow ? globalWindow->AsInner()
+                                                  : nullptr;
+        RefPtr<GetUserMediaRequest> req =
+          new GetUserMediaRequest(window, removedRawId, removedSourceType);
+        obs->NotifyObservers(req, "recording-device-stopped", nullptr);
+      }
+    }
+
+    if (AudioDevice* removedDevice = aListener->GetAudioDevice()) {
+      bool revokeAudioPermission = true;
+      nsString removedRawId;
+      nsString removedSourceType;
+      removedDevice->GetRawId(removedRawId);
+      removedDevice->GetMediaSource(removedSourceType);
+      for (const auto& l : mActiveListeners) {
+        if (AudioDevice* device = l->GetAudioDevice()) {
+          nsString rawId;
+          device->GetRawId(rawId);
+          if (removedRawId.Equals(rawId)) {
+            revokeAudioPermission = false;
+            break;
+          }
+        }
+      }
+
+      if (revokeAudioPermission) {
+        nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+        auto* globalWindow = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
+        nsPIDOMWindowInner* window = globalWindow ? globalWindow->AsInner()
+                                                  : nullptr;
+        RefPtr<GetUserMediaRequest> req =
+          new GetUserMediaRequest(window, removedRawId, removedSourceType);
+        obs->NotifyObservers(req, "recording-device-stopped", nullptr);
+      }
+    }
+
+    if (mInactiveListeners.Length() == 0 &&
+        mActiveListeners.Length() == 0) {
+      LOG(("GUMWindowListener %p Removed the last SourceListener. "
+           "Cleaning up.", this));
+      RemoveAll();
     }
 
-    return NS_OK;
+    return true;
+  }
+
+  void StopSharing();
+
+  /**
+   * Called by one of our SourceListeners when one of its tracks has stopped.
+   * Schedules an event for the next stable state to update chrome.
+   */
+  void NotifySourceTrackStopped();
+
+  /**
+   * Called in stable state to send a notification to update chrome.
+   */
+  void NotifyChromeOfTrackStops();
+
+  bool CapturingVideo() const
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    for (auto& l : mActiveListeners) {
+      if (l->CapturingVideo()) {
+        return true;
+      }
+    }
+    return false;
+  }
+  bool CapturingAudio() const
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    for (auto& l : mActiveListeners) {
+      if (l->CapturingAudio()) {
+        return true;
+      }
+    }
+    return false;
   }
+  bool CapturingScreen() const
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    for (auto& l : mActiveListeners) {
+      if (l->CapturingScreen()) {
+        return true;
+      }
+    }
+    return false;
+  }
+  bool CapturingWindow() const
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    for (auto& l : mActiveListeners) {
+      if (l->CapturingWindow()) {
+        return true;
+      }
+    }
+    return false;
+  }
+  bool CapturingApplication() const
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    for (auto& l : mActiveListeners) {
+      if (l->CapturingApplication()) {
+        return true;
+      }
+    }
+    return false;
+  }
+  bool CapturingBrowser() const
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    for (auto& l : mActiveListeners) {
+      if (l->CapturingBrowser()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  uint64_t WindowID() const
+  {
+    return mWindowID;
+  }
+
+  PrincipalHandle GetPrincipalHandle() const { return mPrincipalHandle; }
 
 private:
-  MediaOperation mType;
-  RefPtr<DOMMediaStream> mStream;
-  nsAutoPtr<OnTracksAvailableCallback> mOnTracksAvailableCallback;
-  RefPtr<AudioDevice> mAudioDevice; // threadsafe
-  RefPtr<VideoDevice> mVideoDevice; // threadsafe
-  RefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
-  bool mBool;
+  ~GetUserMediaWindowListener()
+  {
+    for (auto& l : mInactiveListeners) {
+      l->NotifyRemoved();
+    }
+    mInactiveListeners.Clear();
+    for (auto& l : mActiveListeners) {
+      l->NotifyRemoved();
+    }
+    mActiveListeners.Clear();
+    Unused << mMediaThread;
+    // It's OK to release mStream on any thread; they have thread-safe
+    // refcounts.
+  }
+
+  // Set at construction
+  base::Thread* mMediaThread;
+
   uint64_t mWindowID;
-  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
-  dom::MediaTrackConstraints mConstraints;
+  const PrincipalHandle mPrincipalHandle;
+
+  // true if we have scheduled a task to notify chrome in the next stable state.
+  // The task will reset this to false. MainThread only.
+  bool mChromeNotificationTaskPosted;
+
+  nsTArray<RefPtr<SourceListener>> mInactiveListeners;
+  nsTArray<RefPtr<SourceListener>> mActiveListeners;
 };
 
 /**
  * Send an error back to content.
  * Do this only on the main thread. The onSuccess callback is also passed here
  * so it can be released correctly.
  */
 template<class SuccessCallbackType>
 class ErrorCallbackRunnable : public Runnable
 {
 public:
   ErrorCallbackRunnable(
-    nsCOMPtr<SuccessCallbackType>& aOnSuccess,
-    nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aOnFailure,
+    nsCOMPtr<SuccessCallbackType>&& aOnSuccess,
+    nsCOMPtr<nsIDOMGetUserMediaErrorCallback>&& aOnFailure,
     MediaMgrError& aError,
     uint64_t aWindowID)
     : mError(&aError)
     , mWindowID(aWindowID)
     , mManager(MediaManager::GetInstance())
   {
     mOnSuccess.swap(aOnSuccess);
     mOnFailure.swap(aOnFailure);
@@ -654,39 +694,16 @@ private:
 
   nsCOMPtr<SuccessCallbackType> mOnSuccess;
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
   RefPtr<MediaMgrError> mError;
   uint64_t mWindowID;
   RefPtr<MediaManager> mManager; // get ref to this when creating the runnable
 };
 
-// Handle removing GetUserMediaCallbackMediaStreamListener from main thread
-class GetUserMediaListenerRemove: public Runnable
-{
-public:
-  GetUserMediaListenerRemove(uint64_t aWindowID,
-    GetUserMediaCallbackMediaStreamListener *aListener)
-    : mWindowID(aWindowID)
-    , mListener(aListener) {}
-
-  NS_IMETHOD
-  Run() override
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    RefPtr<MediaManager> manager(MediaManager::GetInstance());
-    manager->RemoveFromWindowList(mWindowID, mListener);
-    return NS_OK;
-  }
-
-protected:
-  uint64_t mWindowID;
-  RefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
-};
-
 /**
  * nsIMediaDevice implementation.
  */
 NS_IMPL_ISUPPORTS(MediaDevice, nsIMediaDevice)
 
 MediaDevice::MediaDevice(MediaEngineSource* aSource, bool aIsVideo)
   : mScary(aSource->GetScary())
   , mMediaSource(aSource->GetMediaSource())
@@ -889,35 +906,16 @@ nsresult MediaDevice::Restart(const dom:
   return GetSource()->Restart(mAllocationHandle, aConstraints, aPrefs, mID,
                               aOutBadConstraint);
 }
 
 nsresult MediaDevice::Deallocate() {
   return GetSource()->Deallocate(mAllocationHandle);
 }
 
-void
-MediaOperationTask::ReturnCallbackError(nsresult rv, const char* errorLog)
-{
-  MM_LOG(("%s , rv=%" PRIu32, errorLog, static_cast<uint32_t>(rv)));
-  NS_DispatchToMainThread(do_AddRef(new ReleaseMediaOperationResource(mStream.forget(),
-    mOnTracksAvailableCallback.forget())));
-  nsString log;
-
-  log.AssignASCII(errorLog);
-  nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onSuccess;
-  RefPtr<MediaMgrError> error = new MediaMgrError(
-    NS_LITERAL_STRING("InternalError"), log);
-  NS_DispatchToMainThread(do_AddRef(
-    new ErrorCallbackRunnable<nsIDOMGetUserMediaSuccessCallback>(onSuccess,
-                                                                 mOnFailure,
-                                                                 *error,
-                                                                 mWindowID)));
-}
-
 static bool
 IsOn(const OwningBooleanOrMediaTrackConstraints &aUnion) {
   return !aUnion.IsBoolean() || aUnion.GetAsBoolean();
 }
 
 static const MediaTrackConstraints&
 GetInvariant(const OwningBooleanOrMediaTrackConstraints &aUnion) {
   static const MediaTrackConstraints empty;
@@ -982,42 +980,44 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(FakeT
  */
 class GetUserMediaStreamRunnable : public Runnable
 {
 public:
   GetUserMediaStreamRunnable(
     nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aOnSuccess,
     nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aOnFailure,
     uint64_t aWindowID,
-    GetUserMediaCallbackMediaStreamListener* aListener,
+    GetUserMediaWindowListener* aWindowListener,
+    SourceListener* aSourceListener,
     const ipc::PrincipalInfo& aPrincipalInfo,
     const MediaStreamConstraints& aConstraints,
     AudioDevice* aAudioDevice,
     VideoDevice* aVideoDevice,
     PeerIdentity* aPeerIdentity)
     : mConstraints(aConstraints)
     , mAudioDevice(aAudioDevice)
     , mVideoDevice(aVideoDevice)
     , mWindowID(aWindowID)
-    , mListener(aListener)
+    , mWindowListener(aWindowListener)
+    , mSourceListener(aSourceListener)
     , mPrincipalInfo(aPrincipalInfo)
     , mPeerIdentity(aPeerIdentity)
     , mManager(MediaManager::GetInstance())
   {
     mOnSuccess.swap(aOnSuccess);
     mOnFailure.swap(aOnFailure);
   }
 
   ~GetUserMediaStreamRunnable() {}
 
   class TracksAvailableCallback : public OnTracksAvailableCallback
   {
   public:
     TracksAvailableCallback(MediaManager* aManager,
-                            nsIDOMGetUserMediaSuccessCallback* aSuccess,
+                            already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess,
                             uint64_t aWindowID,
                             DOMMediaStream* aStream)
       : mWindowID(aWindowID), mOnSuccess(aSuccess), mManager(aManager),
         mStream(aStream) {}
     void NotifyTracksAvailable(DOMMediaStream* aStream) override
     {
       // We're in the main thread, so no worries here.
       if (!(mManager->IsWindowStillActive(mWindowID))) {
@@ -1051,18 +1051,19 @@ public:
   Run() override
   {
     MOZ_ASSERT(NS_IsMainThread());
     nsGlobalWindow* globalWindow = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
     nsPIDOMWindowInner* window = globalWindow ? globalWindow->AsInner() : nullptr;
 
     // We're on main-thread, and the windowlist can only
     // be invalidated from the main-thread (see OnNavigation)
-    StreamListeners* listeners = mManager->GetWindowListeners(mWindowID);
-    if (!listeners || !window || !window->GetExtantDoc()) {
+    GetUserMediaWindowListener* listener =
+      mManager->GetWindowListener(mWindowID);
+    if (!listener || !window || !window->GetExtantDoc()) {
       // This window is no longer live.  mListener has already been removed
       return NS_OK;
     }
 
     MediaStreamGraph::GraphDriverType graphDriverType =
       mAudioDevice ? MediaStreamGraph::AUDIO_THREAD_DRIVER
                    : MediaStreamGraph::SYSTEM_THREAD_DRIVER;
     MediaStreamGraph* msg =
@@ -1089,33 +1090,34 @@ public:
             mWindowID, domStream->GetInputStream()->AsProcessedStream());
       window->SetAudioCapture(true);
     } else {
       class LocalTrackSource : public MediaStreamTrackSource
       {
       public:
         LocalTrackSource(nsIPrincipal* aPrincipal,
                          const nsString& aLabel,
-                         GetUserMediaCallbackMediaStreamListener* aListener,
+                         SourceListener* aListener,
                          const MediaSourceEnum aSource,
                          const TrackID aTrackID,
                          const PeerIdentity* aPeerIdentity)
           : MediaStreamTrackSource(aPrincipal, aLabel), mListener(aListener),
             mSource(aSource), mTrackID(aTrackID), mPeerIdentity(aPeerIdentity) {}
 
         MediaSourceEnum GetMediaSource() const override
         {
           return mSource;
         }
 
         const PeerIdentity* GetPeerIdentity() const override
         {
           return mPeerIdentity;
         }
 
+
         already_AddRefed<PledgeVoid>
         ApplyConstraints(nsPIDOMWindowInner* aWindow,
                          const MediaTrackConstraints& aConstraints,
                          dom::CallerType aCallerType) override
         {
           if (sInShutdown || !mListener) {
             // Track has been stopped, or we are in shutdown. In either case
             // there's no observable outcome, so pretend we succeeded.
@@ -1141,17 +1143,17 @@ public:
             mListener->StopTrack(mTrackID);
             mListener = nullptr;
           }
         }
 
       protected:
         ~LocalTrackSource() {}
 
-        RefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
+        RefPtr<SourceListener> mListener;
         const MediaSourceEnum mSource;
         const TrackID mTrackID;
         const RefPtr<const PeerIdentity> mPeerIdentity;
       };
 
       nsCOMPtr<nsIPrincipal> principal;
       if (mPeerIdentity) {
         principal = NullPrincipal::CreateWithInheritedAttributes(window->GetExtantDoc()->NodePrincipal());
@@ -1160,85 +1162,142 @@ public:
       }
 
       // Normal case, connect the source stream to the track union stream to
       // avoid us blocking. Pass a simple TrackSourceGetter for potential
       // fake tracks. Apart from them gUM never adds tracks dynamically.
       domStream =
         DOMLocalMediaStream::CreateSourceStreamAsInput(window, msg,
                                                        new FakeTrackSourceGetter(principal));
+      stream = domStream->GetInputStream()->AsSourceStream();
 
       if (mAudioDevice) {
         nsString audioDeviceName;
         mAudioDevice->GetName(audioDeviceName);
         const MediaSourceEnum source =
           mAudioDevice->GetSource()->GetMediaSource();
         RefPtr<MediaStreamTrackSource> audioSource =
-          new LocalTrackSource(principal, audioDeviceName, mListener, source,
-                               kAudioTrack, mPeerIdentity);
+          new LocalTrackSource(principal, audioDeviceName, mSourceListener,
+                               source, kAudioTrack, mPeerIdentity);
         MOZ_ASSERT(IsOn(mConstraints.mAudio));
         RefPtr<MediaStreamTrack> track =
           domStream->CreateDOMTrack(kAudioTrack, MediaSegment::AUDIO, audioSource,
                                     GetInvariant(mConstraints.mAudio));
         domStream->AddTrackInternal(track);
       }
       if (mVideoDevice) {
         nsString videoDeviceName;
         mVideoDevice->GetName(videoDeviceName);
         const MediaSourceEnum source =
           mVideoDevice->GetSource()->GetMediaSource();
         RefPtr<MediaStreamTrackSource> videoSource =
-          new LocalTrackSource(principal, videoDeviceName, mListener, source,
-                               kVideoTrack, mPeerIdentity);
+          new LocalTrackSource(principal, videoDeviceName, mSourceListener,
+                               source, kVideoTrack, mPeerIdentity);
         MOZ_ASSERT(IsOn(mConstraints.mVideo));
         RefPtr<MediaStreamTrack> track =
           domStream->CreateDOMTrack(kVideoTrack, MediaSegment::VIDEO, videoSource,
                                     GetInvariant(mConstraints.mVideo));
         domStream->AddTrackInternal(track);
       }
-      stream = domStream->GetInputStream()->AsSourceStream();
     }
 
-    if (!domStream || sInShutdown) {
+    if (!domStream || !stream || sInShutdown) {
       nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure = mOnFailure.forget();
       LOG(("Returning error for getUserMedia() - no stream"));
 
       if (auto* window = nsGlobalWindow::GetInnerWindowWithId(mWindowID)) {
         RefPtr<MediaStreamError> error = new MediaStreamError(window->AsInner(),
             NS_LITERAL_STRING("InternalError"),
             sInShutdown ? NS_LITERAL_STRING("In shutdown") :
                           NS_LITERAL_STRING("No stream."));
         onFailure->OnError(error);
       }
       return NS_OK;
     }
 
-    // The listener was added at the beginning in an inactive state.
-    // Activate our listener. We'll call Start() on the source when get a callback
-    // that the MediaStream has started consuming. The listener is freed
-    // when the page is invalidated (on navigation or close).
-    MOZ_ASSERT(stream);
-    mListener->Activate(stream.forget(), mAudioDevice, mVideoDevice);
+    // Activate our source listener. We'll call Start() on the source when we
+    // get a callback that the MediaStream has started consuming. The listener
+    // is freed when the page is invalidated (on navigation or close).
+    mWindowListener->Activate(mSourceListener, stream, mAudioDevice, mVideoDevice);
 
     // Note: includes JS callbacks; must be released on MainThread
-    TracksAvailableCallback* tracksAvailableCallback =
-      new TracksAvailableCallback(mManager, mOnSuccess, mWindowID, domStream);
+    auto callback = MakeRefPtr<Refcountable<UniquePtr<OnTracksAvailableCallback>>>(
+        new TracksAvailableCallback(mManager, mOnSuccess.forget(), mWindowID, domStream));
 
     // Dispatch to the media thread to ask it to start the sources,
     // because that can take a while.
-    // Pass ownership of domStream to the MediaOperationTask
-    // to ensure it's kept alive until the MediaOperationTask runs (at least).
-    RefPtr<Runnable> mediaOperation =
-        new MediaOperationTask(MEDIA_START, mListener, domStream,
-                               tracksAvailableCallback,
-                               mAudioDevice, mVideoDevice,
-                               false, mWindowID, mOnFailure.forget());
-    MediaManager::PostTask(mediaOperation.forget());
-    // We won't need mOnFailure now.
-    mOnFailure = nullptr;
+    // Pass ownership of domStream through the lambda to
+    // GetUserMediaNotificationEvent to ensure it's kept alive until the
+    // GetUserMediaNotificationEvent runs or is discarded.
+    RefPtr<GetUserMediaStreamRunnable> self = this;
+    MediaManager::PostTask(NewTaskFrom([self, domStream, callback]() mutable {
+      MOZ_ASSERT(MediaManager::IsInMediaThread());
+      SourceMediaStream* source = self->mSourceListener->GetSourceStream();
+
+      RefPtr<MediaMgrError> error = nullptr;
+      if (self->mAudioDevice) {
+        nsresult rv =
+          self->mAudioDevice->GetSource()->Start(source, kAudioTrack,
+                                                 self->mSourceListener->GetPrincipalHandle());
+        if (NS_FAILED(rv)) {
+          nsString log;
+          log.AssignASCII("Starting audio failed");
+          error = new MediaMgrError(NS_LITERAL_STRING("InternalError"), log);
+        }
+      }
+
+      if (!error && self->mVideoDevice) {
+        nsresult rv =
+          self->mVideoDevice->GetSource()->Start(source, kVideoTrack,
+                                                 self->mSourceListener->GetPrincipalHandle());
+        if (NS_FAILED(rv)) {
+          nsString log;
+          log.AssignASCII("Starting video failed");
+          error = new MediaMgrError(NS_LITERAL_STRING("InternalError"), log);
+        }
+      }
+
+      if (error) {
+        // The DOM stream and track callback must be released on main thread.
+        NS_DispatchToMainThread(do_AddRef(new ReleaseMediaOperationResource(
+          domStream.forget(), callback.forget())));
+
+        // Dispatch the error callback on main thread.
+        nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onSuccess;
+        NS_DispatchToMainThread(do_AddRef(
+          new ErrorCallbackRunnable<nsIDOMGetUserMediaSuccessCallback>(
+            Move(onSuccess), Move(self->mOnFailure), *error, self->mWindowID)));
+
+        // This should be empty now
+        MOZ_ASSERT(!self->mOnFailure);
+        return NS_OK;
+      }
+
+      // Start() queued the tracks to be added synchronously to avoid races
+      source->FinishAddTracks();
+
+      source->SetPullEnabled(true);
+      source->AdvanceKnownTracksTime(STREAM_TIME_MAX);
+
+      MM_LOG(("started all sources"));
+
+      // Forward onTracksAvailableCallback to GetUserMediaNotificationEvent,
+      // because onTracksAvailableCallback needs to be added to domStream
+      // on the main thread.
+      // The event runnable must always be released on mainthread due to the JS
+      // callbacks in the TracksAvailableCallback.
+      NS_DispatchToMainThread(do_AddRef(
+        new GetUserMediaNotificationEvent(
+          GetUserMediaNotificationEvent::STARTING,
+          domStream.forget(),
+          callback.forget(),
+          self->mWindowID,
+          self->mOnFailure.forget())));
+      return NS_OK;
+    }));
 
     if (!IsPincipalInfoPrivate(mPrincipalInfo)) {
       // Call GetPrincipalKey again, this time w/persist = true, to promote
       // deviceIds to persistent, in case they're not already. Fire'n'forget.
       RefPtr<Pledge<nsCString>> p =
         media::GetPrincipalKey(mPrincipalInfo, true);
     }
     return NS_OK;
@@ -1246,17 +1305,18 @@ public:
 
 private:
   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mOnSuccess;
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
   MediaStreamConstraints mConstraints;
   RefPtr<AudioDevice> mAudioDevice;
   RefPtr<VideoDevice> mVideoDevice;
   uint64_t mWindowID;
-  RefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
+  RefPtr<GetUserMediaWindowListener> mWindowListener;
+  RefPtr<SourceListener> mSourceListener;
   ipc::PrincipalInfo mPrincipalInfo;
   RefPtr<PeerIdentity> mPeerIdentity;
   RefPtr<MediaManager> mManager; // get ref to this when creating the runnable
 };
 
 // Source getter returning full list
 
 template<class DeviceType>
@@ -1379,51 +1439,53 @@ MediaManager::SelectSettings(
  */
 class GetUserMediaTask : public Runnable
 {
 public:
   GetUserMediaTask(
     const MediaStreamConstraints& aConstraints,
     already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aOnSuccess,
     already_AddRefed<nsIDOMGetUserMediaErrorCallback> aOnFailure,
-    uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener,
-    MediaEnginePrefs &aPrefs,
+    uint64_t aWindowID, GetUserMediaWindowListener *aWindowListener,
+    SourceListener *aSourceListener, MediaEnginePrefs &aPrefs,
     const ipc::PrincipalInfo& aPrincipalInfo,
     bool aIsChrome,
     MediaManager::SourceSet* aSourceSet)
     : mConstraints(aConstraints)
     , mOnSuccess(aOnSuccess)
     , mOnFailure(aOnFailure)
     , mWindowID(aWindowID)
-    , mListener(aListener)
+    , mWindowListener(aWindowListener)
+    , mSourceListener(aSourceListener)
     , mPrefs(aPrefs)
     , mPrincipalInfo(aPrincipalInfo)
     , mIsChrome(aIsChrome)
     , mDeviceChosen(false)
     , mSourceSet(aSourceSet)
     , mManager(MediaManager::GetInstance())
   {}
 
   ~GetUserMediaTask() {
   }
 
   void
   Fail(const nsAString& aName,
        const nsAString& aMessage = EmptyString(),
        const nsAString& aConstraint = EmptyString()) {
     RefPtr<MediaMgrError> error = new MediaMgrError(aName, aMessage, aConstraint);
-    auto runnable = MakeRefPtr<ErrorCallbackRunnable<nsIDOMGetUserMediaSuccessCallback>>(
-        mOnSuccess, mOnFailure, *error, mWindowID);
+    auto errorRunnable = MakeRefPtr<ErrorCallbackRunnable<nsIDOMGetUserMediaSuccessCallback>>(
+        Move(mOnSuccess), Move(mOnFailure), *error, mWindowID);
     // These should be empty now
     MOZ_ASSERT(!mOnSuccess);
     MOZ_ASSERT(!mOnFailure);
 
-    NS_DispatchToMainThread(runnable.forget());
+    NS_DispatchToMainThread(errorRunnable.forget());
     // Do after ErrorCallbackRunnable Run()s, as it checks active window list
-    NS_DispatchToMainThread(do_AddRef(new GetUserMediaListenerRemove(mWindowID, mListener)));
+    NS_DispatchToMainThread(NewRunnableMethod<RefPtr<SourceListener>>(
+      mWindowListener, &GetUserMediaWindowListener::Remove, mSourceListener));
   }
 
   NS_IMETHOD
   Run() override
   {
     MOZ_ASSERT(!NS_IsMainThread());
     MOZ_ASSERT(mOnSuccess);
     MOZ_ASSERT(mOnFailure);
@@ -1481,18 +1543,19 @@ public:
     }
     PeerIdentity* peerIdentity = nullptr;
     if (!mConstraints.mPeerIdentity.IsEmpty()) {
       peerIdentity = new PeerIdentity(mConstraints.mPeerIdentity);
     }
 
     NS_DispatchToMainThread(do_AddRef(
         new GetUserMediaStreamRunnable(mOnSuccess, mOnFailure, mWindowID,
-                                       mListener, mPrincipalInfo,
-                                       mConstraints, mAudioDevice, mVideoDevice,
+                                       mWindowListener, mSourceListener,
+                                       mPrincipalInfo, mConstraints,
+                                       mAudioDevice, mVideoDevice,
                                        peerIdentity)));
     MOZ_ASSERT(!mOnSuccess);
     MOZ_ASSERT(!mOnFailure);
     return NS_OK;
   }
 
   nsresult
   Denied(const nsAString& aName,
@@ -1510,18 +1573,17 @@ public:
       nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure = mOnFailure.forget();
 
       if (auto* window = nsGlobalWindow::GetInnerWindowWithId(mWindowID)) {
         RefPtr<MediaStreamError> error = new MediaStreamError(window->AsInner(),
                                                               aName, aMessage);
         onFailure->OnError(error);
       }
       // Should happen *after* error runs for consistency, but may not matter
-      RefPtr<MediaManager> manager(MediaManager::GetInstance());
-      manager->RemoveFromWindowList(mWindowID, mListener);
+      mWindowListener->Remove(mSourceListener);
     } else {
       // This will re-check the window being alive on main-thread
       // and remove the listener on MainThread as well
       Fail(aName, aMessage);
     }
 
     MOZ_ASSERT(!mOnSuccess);
     MOZ_ASSERT(!mOnFailure);
@@ -1559,17 +1621,18 @@ public:
   }
 
 private:
   MediaStreamConstraints mConstraints;
 
   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mOnSuccess;
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
   uint64_t mWindowID;
-  RefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
+  RefPtr<GetUserMediaWindowListener> mWindowListener;
+  RefPtr<SourceListener> mSourceListener;
   RefPtr<AudioDevice> mAudioDevice;
   RefPtr<VideoDevice> mVideoDevice;
   MediaEnginePrefs mPrefs;
   ipc::PrincipalInfo mPrincipalInfo;
   bool mIsChrome;
 
   bool mDeviceChosen;
 public:
@@ -1890,57 +1953,43 @@ MediaManager::PostTask(already_AddRefed<
   }
   NS_ASSERTION(Get(), "MediaManager singleton?");
   NS_ASSERTION(Get()->mMediaThread, "No thread yet");
   Get()->mMediaThread->message_loop()->PostTask(Move(task));
 }
 
 /* static */ nsresult
 MediaManager::NotifyRecordingStatusChange(nsPIDOMWindowInner* aWindow,
-                                          const nsString& aMsg,
-                                          const bool& aIsAudio,
-                                          const bool& aIsVideo)
+                                          const nsString& aMsg)
 {
   NS_ENSURE_ARG(aWindow);
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   if (!obs) {
     NS_WARNING("Could not get the Observer service for GetUserMedia recording notification.");
     return NS_ERROR_FAILURE;
   }
 
   RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
-  props->SetPropertyAsBool(NS_LITERAL_STRING("isAudio"), aIsAudio);
-  props->SetPropertyAsBool(NS_LITERAL_STRING("isVideo"), aIsVideo);
 
   nsCString pageURL;
   nsCOMPtr<nsIURI> docURI = aWindow->GetDocumentURI();
   NS_ENSURE_TRUE(docURI, NS_ERROR_FAILURE);
 
   nsresult rv = docURI->GetSpec(pageURL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   NS_ConvertUTF8toUTF16 requestURL(pageURL);
 
   props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL);
 
   obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
                        "recording-device-events",
                        aMsg.get());
 
-  // Forward recording events to parent process.
-  // The events are gathered in chrome process and used for recording indicator
-  if (!XRE_IsParentProcess()) {
-    Unused <<
-      dom::ContentChild::GetSingleton()->SendRecordingDeviceEvents(aMsg,
-                                                                   requestURL,
-                                                                   aIsAudio,
-                                                                   aIsVideo);
-  }
-
   return NS_OK;
 }
 
 int MediaManager::AddDeviceChangeCallback(DeviceChangeCallback* aCallback)
 {
   bool fakeDeviceChangeEventOn = mPrefs.mFakeDeviceChangeEventOn;
   MediaManager::PostTask(NewTaskFrom([fakeDeviceChangeEventOn]() {
     RefPtr<MediaManager> manager = MediaManager_GetInstance();
@@ -2227,25 +2276,30 @@ if (privileged) {
           cs.mMediaSource = ac.mMediaSource;
         }
       }
     }
   } else if (IsOn(c.mAudio)) {
    audioType = MediaSourceEnum::Microphone;
   }
 
-  StreamListeners* listeners = AddWindowID(windowID);
-
-  // Create a disabled listener to act as a placeholder
-  RefPtr<GetUserMediaCallbackMediaStreamListener> listener =
-    new GetUserMediaCallbackMediaStreamListener(mMediaThread, windowID,
-                                                MakePrincipalHandle(principal));
-
-  // No need for locking because we always do this in the main thread.
-  listeners->AppendElement(listener);
+  // Create a window listener if it doesn't already exist.
+  RefPtr<GetUserMediaWindowListener> windowListener =
+    GetWindowListener(windowID);
+  if (windowListener) {
+    PrincipalHandle existingPrincipalHandle = windowListener->GetPrincipalHandle();
+    MOZ_ASSERT(PrincipalHandleMatches(existingPrincipalHandle, principal));
+  } else {
+    windowListener = new GetUserMediaWindowListener(mMediaThread, windowID,
+                                                    MakePrincipalHandle(principal));
+    AddWindowID(windowID, windowListener);
+  }
+
+  RefPtr<SourceListener> sourceListener = new SourceListener();
+  windowListener->Register(sourceListener);
 
   if (!privileged) {
     // Check if this site has had persistent permissions denied.
     nsCOMPtr<nsIPermissionManager> permManager =
       do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     uint32_t audioPerm = nsIPermissionManager::UNKNOWN_ACTION;
@@ -2264,17 +2318,17 @@ if (privileged) {
     }
 
     if ((!IsOn(c.mAudio) && !IsOn(c.mVideo)) ||
         (IsOn(c.mAudio) && audioPerm == nsIPermissionManager::DENY_ACTION) ||
         (IsOn(c.mVideo) && videoPerm == nsIPermissionManager::DENY_ACTION)) {
       RefPtr<MediaStreamError> error =
           new MediaStreamError(aWindow, NS_LITERAL_STRING("NotAllowedError"));
       onFailure->OnError(error);
-      RemoveFromWindowList(windowID, listener);
+      windowListener->Remove(sourceListener);
       return NS_OK;
     }
   }
 
   // Get list of all devices, with origin-specific device ids.
 
   MediaEnginePrefs prefs = mPrefs;
 
@@ -2287,33 +2341,34 @@ if (privileged) {
 
   bool askPermission =
       (!privileged || Preferences::GetBool("media.navigator.permission.force")) &&
       (!fake || Preferences::GetBool("media.navigator.permission.fake"));
 
   RefPtr<PledgeSourceSet> p = EnumerateDevicesImpl(windowID, videoType,
                                                    audioType, fake);
   RefPtr<MediaManager> self = this;
-  p->Then([self, onSuccess, onFailure, windowID, c, listener, askPermission,
-           prefs, isHTTPS, callID, principalInfo,
+  p->Then([self, onSuccess, onFailure, windowID, c, windowListener,
+           sourceListener, askPermission, prefs, isHTTPS, callID, principalInfo,
            isChrome](SourceSet*& aDevices) mutable {
     // grab result
     auto devices = MakeRefPtr<Refcountable<UniquePtr<SourceSet>>>(aDevices);
 
     // Ensure that our windowID is still good.
     if (!nsGlobalWindow::GetInnerWindowWithId(windowID)) {
       return;
     }
 
     // Apply any constraints. This modifies the passed-in list.
     RefPtr<PledgeChar> p2 = self->SelectSettings(c, isChrome, devices);
 
     p2->Then([self, onSuccess, onFailure, windowID, c,
-              listener, askPermission, prefs, isHTTPS, callID, principalInfo,
-              isChrome, devices](const char*& badConstraint) mutable {
+              windowListener, sourceListener, askPermission, prefs, isHTTPS,
+              callID, principalInfo, isChrome, devices
+             ](const char*& badConstraint) mutable {
 
       // Ensure that the captured 'this' pointer and our windowID are still good.
       auto* globalWindow = nsGlobalWindow::GetInnerWindowWithId(windowID);
       RefPtr<nsPIDOMWindowInner> window = globalWindow ? globalWindow->AsInner()
                                                        : nullptr;
       if (!MediaManager::Exists() || !window) {
         return;
       }
@@ -2341,21 +2396,25 @@ if (privileged) {
         for (auto& device : **devices) {
           nsresult rv = devicesCopy->AppendElement(device, /*weak =*/ false);
           if (NS_WARN_IF(NS_FAILED(rv))) {
             return;
           }
         }
       }
 
-      // Pass callbacks and MediaStreamListener along to GetUserMediaTask.
-      RefPtr<GetUserMediaTask> task (new GetUserMediaTask(c, onSuccess.forget(),
+      // Pass callbacks and listeners along to GetUserMediaTask.
+      RefPtr<GetUserMediaTask> task (new GetUserMediaTask(c,
+                                                          onSuccess.forget(),
                                                           onFailure.forget(),
-                                                          windowID, listener,
-                                                          prefs, principalInfo,
+                                                          windowID,
+                                                          windowListener,
+                                                          sourceListener,
+                                                          prefs,
+                                                          principalInfo,
                                                           isChrome,
                                                           devices->release()));
       // Store the task w/callbacks.
       self->mActiveCallbacks.Put(callID, task.forget());
 
       // Add a WindowID cross-reference so OnNavigation can tear things down
       nsTArray<nsString>* array;
       if (!self->mCallIds.Get(windowID, &array)) {
@@ -2545,43 +2604,50 @@ MediaManager::EnumerateDevices(nsPIDOMWi
                                nsIDOMGetUserMediaErrorCallback* aOnFailure)
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_TRUE(!sInShutdown, NS_ERROR_FAILURE);
   nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess(aOnSuccess);
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure(aOnFailure);
   uint64_t windowId = aWindow->WindowID();
 
-  StreamListeners* listeners = AddWindowID(windowId);
-
   nsIPrincipal* principal = aWindow->GetExtantDoc()->NodePrincipal();
 
-  // Create a disabled listener to act as a placeholder
-  RefPtr<GetUserMediaCallbackMediaStreamListener> listener =
-    new GetUserMediaCallbackMediaStreamListener(mMediaThread, windowId,
-                                                MakePrincipalHandle(principal));
-
-  // No need for locking because we always do this in the main thread.
-  listeners->AppendElement(listener);
+  RefPtr<GetUserMediaWindowListener> windowListener =
+    GetWindowListener(windowId);
+  if (windowListener) {
+    PrincipalHandle existingPrincipalHandle =
+      windowListener->GetPrincipalHandle();
+    MOZ_ASSERT(PrincipalHandleMatches(existingPrincipalHandle, principal));
+  } else {
+    windowListener = new GetUserMediaWindowListener(mMediaThread, windowId,
+                                                    MakePrincipalHandle(principal));
+    AddWindowID(windowId, windowListener);
+  }
+
+  // Create an inactive SourceListener to act as a placeholder, so the
+  // window listener doesn't clean itself up until we're done.
+  RefPtr<SourceListener> sourceListener = new SourceListener();
+  windowListener->Register(sourceListener);
 
   bool fake = Preferences::GetBool("media.navigator.streams.fake");
 
   RefPtr<PledgeSourceSet> p = EnumerateDevicesImpl(windowId,
                                                    MediaSourceEnum::Camera,
                                                    MediaSourceEnum::Microphone,
                                                    fake);
-  p->Then([onSuccess, windowId, listener](SourceSet*& aDevices) mutable {
+  p->Then([onSuccess, windowListener, sourceListener](SourceSet*& aDevices) mutable {
     UniquePtr<SourceSet> devices(aDevices); // grab result
-    RefPtr<MediaManager> mgr = MediaManager_GetInstance();
-    mgr->RemoveFromWindowList(windowId, listener);
+    DebugOnly<bool> rv = windowListener->Remove(sourceListener);
+    MOZ_ASSERT(rv);
     nsCOMPtr<nsIWritableVariant> array = MediaManager_ToJSArray(*devices);
     onSuccess->OnSuccess(array);
-  }, [onFailure, windowId, listener](MediaStreamError*& reason) mutable {
-    RefPtr<MediaManager> mgr = MediaManager_GetInstance();
-    mgr->RemoveFromWindowList(windowId, listener);
+  }, [onFailure, windowListener, sourceListener](MediaStreamError*& reason) mutable {
+    DebugOnly<bool> rv = windowListener->Remove(sourceListener);
+    MOZ_ASSERT(rv);
     onFailure->OnError(reason);
   });
   return NS_OK;
 }
 
 /*
  * GetUserMediaDevices - called by the UI-part of getUserMedia from chrome JS.
  */
@@ -2637,34 +2703,31 @@ MediaManager::GetBackend(uint64_t aWindo
 #endif
   }
   return mBackend;
 }
 
 static void
 StopSharingCallback(MediaManager *aThis,
                     uint64_t aWindowID,
-                    StreamListeners *aListeners,
+                    GetUserMediaWindowListener *aListener,
                     void *aData)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  if (aListeners) {
-    auto length = aListeners->Length();
-    for (size_t i = 0; i < length; ++i) {
-      GetUserMediaCallbackMediaStreamListener *listener = aListeners->ElementAt(i);
-
-      if (listener->Stream()) { // aka HasBeenActivate()ed
-        listener->Stop();
-      }
-      listener->Remove();
-      listener->StopSharing();
-    }
-    aListeners->Clear();
-    aThis->RemoveWindowID(aWindowID);
+
+  // Grab a strong ref since RemoveAll() might destroy the listener mid-way
+  // when clearing the mActiveWindows reference.
+  RefPtr<GetUserMediaWindowListener> listener(aListener);
+  if (!listener) {
+    return;
   }
+
+  listener->Stop();
+  listener->RemoveAll();
+  MOZ_ASSERT(!aThis->GetWindowListener(aWindowID));
 }
 
 
 void
 MediaManager::OnNavigation(uint64_t aWindowID)
 {
   MOZ_ASSERT(NS_IsMainThread());
   LOG(("OnNavigation for %" PRIu64, aWindowID));
@@ -2683,16 +2746,17 @@ MediaManager::OnNavigation(uint64_t aWin
   // This is safe since we're on main-thread, and the windowlist can only
   // be added to from the main-thread
   auto* window = nsGlobalWindow::GetInnerWindowWithId(aWindowID);
   if (window) {
     IterateWindowListeners(window->AsInner(), StopSharingCallback, nullptr);
   } else {
     RemoveWindowID(aWindowID);
   }
+  MOZ_ASSERT(!GetWindowListener(aWindowID));
 
   RemoveMediaDevicesCallback(aWindowID);
 }
 
 void
 MediaManager::RemoveMediaDevicesCallback(uint64_t aWindowID)
 {
   MutexAutoLock lock(mCallbackMutex);
@@ -2706,30 +2770,30 @@ MediaManager::RemoveMediaDevicesCallback
       if (window && window->WindowID() == aWindowID) {
         DeviceChangeCallback::RemoveDeviceChangeCallbackLocked(observer);
         return;
       }
     }
   }
 }
 
-StreamListeners*
-MediaManager::AddWindowID(uint64_t aWindowId)
+void
+MediaManager::AddWindowID(uint64_t aWindowId,
+                          GetUserMediaWindowListener* aListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
   // Store the WindowID in a hash table and mark as active. The entry is removed
   // when this window is closed or navigated away from.
   // This is safe since we're on main-thread, and the windowlist can only
   // be invalidated from the main-thread (see OnNavigation)
-  StreamListeners* listeners = GetActiveWindows()->Get(aWindowId);
-  if (!listeners) {
-    listeners = new StreamListeners;
-    GetActiveWindows()->Put(aWindowId, listeners);
+  if (IsWindowStillActive(aWindowId)) {
+    MOZ_ASSERT(false, "Window already added");
+    return;
   }
-  return listeners;
+  GetActiveWindows()->Put(aWindowId, aListener);
 }
 
 void
 MediaManager::RemoveWindowID(uint64_t aWindowId)
 {
   mActiveWindows.Remove(aWindowId);
 
   // get outer windowID
@@ -2754,118 +2818,16 @@ MediaManager::RemoveWindowID(uint64_t aW
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   obs->NotifyObservers(nullptr, "recording-window-ended", data.get());
   LOG(("Sent recording-window-ended for window %" PRIu64 " (outer %" PRIu64 ")",
        aWindowId, outerID));
 }
 
 void
-MediaManager::RemoveFromWindowList(uint64_t aWindowID,
-  GetUserMediaCallbackMediaStreamListener *aListener)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsString videoRawId;
-  nsString audioRawId;
-  nsString videoSourceType;
-  nsString audioSourceType;
-  bool hasVideoDevice = aListener->mVideoDevice;
-  bool hasAudioDevice = aListener->mAudioDevice;
-
-  if (hasVideoDevice) {
-    aListener->mVideoDevice->GetRawId(videoRawId);
-    aListener->mVideoDevice->GetMediaSource(videoSourceType);
-  }
-  if (hasAudioDevice) {
-    aListener->mAudioDevice->GetRawId(audioRawId);
-    aListener->mAudioDevice->GetMediaSource(audioSourceType);
-  }
-
-  // This is defined as safe on an inactive GUMCMSListener
-  aListener->Remove(); // really queues the remove
-
-  StreamListeners* listeners = GetWindowListeners(aWindowID);
-  if (!listeners) {
-    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-    auto* globalWindow = nsGlobalWindow::GetInnerWindowWithId(aWindowID);
-    RefPtr<nsPIDOMWindowInner> window = globalWindow ? globalWindow->AsInner()
-                                                     : nullptr;
-    if (window != nullptr) {
-      RefPtr<GetUserMediaRequest> req =
-        new GetUserMediaRequest(window, NullString(), NullString());
-      obs->NotifyObservers(req, "recording-device-stopped", nullptr);
-    }
-    return;
-  }
-  listeners->RemoveElement(aListener);
-
-  uint32_t length = listeners->Length();
-
-  if (hasVideoDevice) {
-    bool revokeVideoPermission = true;
-
-    for (uint32_t i = 0; i < length; ++i) {
-      RefPtr<GetUserMediaCallbackMediaStreamListener> listener =
-        listeners->ElementAt(i);
-      if (hasVideoDevice && listener->mVideoDevice) {
-        nsString rawId;
-        listener->mVideoDevice->GetRawId(rawId);
-        if (videoRawId.Equals(rawId)) {
-          revokeVideoPermission = false;
-          break;
-        }
-      }
-    }
-
-    if (revokeVideoPermission) {
-      nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-      auto* globalWindow = nsGlobalWindow::GetInnerWindowWithId(aWindowID);
-      RefPtr<nsPIDOMWindowInner> window = globalWindow ? globalWindow->AsInner()
-                                                       : nullptr;
-      RefPtr<GetUserMediaRequest> req =
-        new GetUserMediaRequest(window, videoRawId, videoSourceType);
-      obs->NotifyObservers(req, "recording-device-stopped", nullptr);
-    }
-  }
-
-  if (hasAudioDevice) {
-    bool revokeAudioPermission = true;
-
-    for (uint32_t i = 0; i < length; ++i) {
-      RefPtr<GetUserMediaCallbackMediaStreamListener> listener =
-        listeners->ElementAt(i);
-      if (hasAudioDevice && listener->mAudioDevice) {
-        nsString rawId;
-        listener->mAudioDevice->GetRawId(rawId);
-        if (audioRawId.Equals(rawId)) {
-          revokeAudioPermission = false;
-          break;
-        }
-      }
-    }
-
-    if (revokeAudioPermission) {
-      nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-      auto* globalWindow = nsGlobalWindow::GetInnerWindowWithId(aWindowID);
-      RefPtr<nsPIDOMWindowInner> window = globalWindow ? globalWindow->AsInner()
-                                                       : nullptr;
-      RefPtr<GetUserMediaRequest> req =
-        new GetUserMediaRequest(window, audioRawId, audioSourceType);
-      obs->NotifyObservers(req, "recording-device-stopped", nullptr);
-    }
-  }
-
-  if (length == 0) {
-    RemoveWindowID(aWindowID);
-    // listeners has been deleted here
-  }
-}
-
-void
 MediaManager::GetPref(nsIPrefBranch *aBranch, const char *aPref,
                       const char *aData, int32_t *aVal)
 {
   int32_t temp;
   if (aData == nullptr || strcmp(aPref,aData) == 0) {
     if (NS_SUCCEEDED(aBranch->GetIntPref(aPref, &temp))) {
       *aVal = temp;
     }
@@ -3156,43 +3118,32 @@ nsresult
 MediaManager::GetActiveMediaCaptureWindows(nsIArray** aArray)
 {
   MOZ_ASSERT(aArray);
 
   nsCOMPtr<nsIMutableArray> array = nsArray::Create();
 
   for (auto iter = mActiveWindows.Iter(); !iter.Done(); iter.Next()) {
     const uint64_t& id = iter.Key();
-    StreamListeners* listeners = iter.UserData();
+    RefPtr<GetUserMediaWindowListener> winListener = iter.UserData();
+    if (!winListener) {
+      continue;
+    }
 
     nsPIDOMWindowInner* window =
       nsGlobalWindow::GetInnerWindowWithId(id)->AsInner();
     MOZ_ASSERT(window);
     // XXXkhuey ...
     if (!window) {
       continue;
     }
-    // mActiveWindows contains both windows that have requested device
-    // access and windows that are currently capturing media. We want
-    // to return only the latter. See bug 975177.
-    bool capturing = false;
-    if (listeners) {
-      uint32_t length = listeners->Length();
-      for (uint32_t i = 0; i < length; ++i) {
-        RefPtr<GetUserMediaCallbackMediaStreamListener> listener =
-          listeners->ElementAt(i);
-        if (listener->CapturingVideo() || listener->CapturingAudio() ||
-            listener->CapturingScreen() || listener->CapturingWindow() ||
-            listener->CapturingApplication()) {
-          capturing = true;
-          break;
-        }
-      }
-    }
-    if (capturing) {
+
+    if (winListener->CapturingVideo() || winListener->CapturingAudio() ||
+        winListener->CapturingScreen() || winListener->CapturingWindow() ||
+        winListener->CapturingApplication()) {
       array->AppendElement(window, /*weak =*/ false);
     }
   }
 
   array.forget(aArray);
   return NS_OK;
 }
 
@@ -3204,49 +3155,45 @@ struct CaptureWindowStateData {
   bool *mWindowShare;
   bool *mAppShare;
   bool *mBrowserShare;
 };
 
 static void
 CaptureWindowStateCallback(MediaManager *aThis,
                            uint64_t aWindowID,
-                           StreamListeners *aListeners,
+                           GetUserMediaWindowListener  *aListener,
                            void *aData)
 {
   struct CaptureWindowStateData *data = (struct CaptureWindowStateData *) aData;
 
-  if (aListeners) {
-    auto length = aListeners->Length();
-    for (size_t i = 0; i < length; ++i) {
-      GetUserMediaCallbackMediaStreamListener *listener = aListeners->ElementAt(i);
-
-      if (listener->CapturingVideo()) {
-        *data->mVideo = true;
-      }
-      if (listener->CapturingAudio()) {
-        *data->mAudio = true;
-      }
-      if (listener->CapturingScreen()) {
-        *data->mScreenShare = true;
-      }
-      if (listener->CapturingWindow()) {
-        *data->mWindowShare = true;
-      }
-      if (listener->CapturingApplication()) {
-        *data->mAppShare = true;
-      }
-      if (listener->CapturingBrowser()) {
-        *data->mBrowserShare = true;
-      }
-    }
+  if (!aListener) {
+    return;
+  }
+
+  if (aListener->CapturingVideo()) {
+    *data->mVideo = true;
+  }
+  if (aListener->CapturingAudio()) {
+    *data->mAudio = true;
+  }
+  if (aListener->CapturingScreen()) {
+    *data->mScreenShare = true;
+  }
+  if (aListener->CapturingWindow()) {
+    *data->mWindowShare = true;
+  }
+  if (aListener->CapturingApplication()) {
+    *data->mAppShare = true;
+  }
+  if (aListener->CapturingBrowser()) {
+    *data->mBrowserShare = true;
   }
 }
 
-
 NS_IMETHODIMP
 MediaManager::MediaCaptureWindowState(nsIDOMWindow* aWindow, bool* aVideo,
                                       bool* aAudio, bool *aScreenShare,
                                       bool* aWindowShare, bool *aAppShare,
                                       bool *aBrowserShare)
 {
   MOZ_ASSERT(NS_IsMainThread());
   struct CaptureWindowStateData data;
@@ -3285,25 +3232,24 @@ MediaManager::SanitizeDeviceIds(int64_t 
 
   media::SanitizeOriginKeys(aSinceWhen, false); // we fire and forget
   return NS_OK;
 }
 
 static void
 StopScreensharingCallback(MediaManager *aThis,
                           uint64_t aWindowID,
-                          StreamListeners *aListeners,
+                          GetUserMediaWindowListener *aListener,
                           void *aData)
 {
-  if (aListeners) {
-    auto length = aListeners->Length();
-    for (size_t i = 0; i < length; ++i) {
-      aListeners->ElementAt(i)->StopSharing();
-    }
+  if (!aListener) {
+    return;
   }
+
+  aListener->StopSharing();
 }
 
 void
 MediaManager::StopScreensharing(uint64_t aWindowID)
 {
   // We need to stop window/screensharing for all streams in all innerwindows that
   // correspond to that outerwindow.
 
@@ -3318,20 +3264,22 @@ MediaManager::StopScreensharing(uint64_t
 void
 MediaManager::IterateWindowListeners(nsPIDOMWindowInner* aWindow,
                                      WindowListenerCallback aCallback,
                                      void *aData)
 {
   // Iterate the docshell tree to find all the child windows, and for each
   // invoke the callback
   if (aWindow) {
-    uint64_t windowID = aWindow->WindowID();
-    StreamListeners* listeners = GetActiveWindows()->Get(windowID);
-    // pass listeners so it can modify/delete the list
-    (*aCallback)(this, windowID, listeners, aData);
+    {
+      uint64_t windowID = aWindow->WindowID();
+      GetUserMediaWindowListener* listener = GetWindowListener(windowID);
+      (*aCallback)(this, windowID, listener, aData);
+      // NB: `listener` might have been destroyed.
+    }
 
     // iterate any children of *this* window (iframes, etc)
     nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
     if (docShell) {
       int32_t i, count;
       docShell->GetChildCount(&count);
       for (i = 0; i < count; ++i) {
         nsCOMPtr<nsIDocShellTreeItem> item;
@@ -3409,72 +3357,403 @@ MediaManager::IsActivelyCapturingOrHasAP
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return false;
     }
   }
   return audio == nsIPermissionManager::ALLOW_ACTION ||
          video == nsIPermissionManager::ALLOW_ACTION;
 }
 
+SourceListener::SourceListener()
+  : mActivated(false)
+  , mStopped(false)
+  , mFinished(false)
+  , mRemoved(false)
+  , mAudioStopped(false)
+  , mVideoStopped(false)
+  , mMainThreadCheck(nullptr)
+  , mPrincipalHandle(PRINCIPAL_HANDLE_NONE)
+  , mWindowListener(nullptr)
+{}
+
 void
-GetUserMediaCallbackMediaStreamListener::Stop()
+SourceListener::Register(GetUserMediaWindowListener* aListener)
+{
+  if (mWindowListener) {
+    MOZ_ASSERT(false, "Already registered");
+    return;
+  }
+  if (mActivated) {
+    MOZ_ASSERT(false, "Already activated");
+    return;
+  }
+  if (!aListener) {
+    MOZ_ASSERT(false, "No listener");
+    return;
+  }
+  mPrincipalHandle = aListener->GetPrincipalHandle();
+  mWindowListener = aListener;
+}
+
+void
+SourceListener::Activate(SourceMediaStream* aStream,
+                         AudioDevice* aAudioDevice,
+                         VideoDevice* aVideoDevice)
 {
   MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
+
+  if (mActivated) {
+    MOZ_ASSERT(false, "Already activated");
+    return;
+  }
+
+  mActivated = true;
+  mMainThreadCheck = PR_GetCurrentThread();
+  mStream = aStream;
+  mAudioDevice = aAudioDevice;
+  mVideoDevice = aVideoDevice;
+  mStream->AddListener(this);
+}
+
+void
+SourceListener::Stop()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
+
   if (mStopped) {
     return;
   }
 
-  // We can't take a chance on blocking here, so proxy this to another
-  // thread.
-  // Pass a ref to us (which is threadsafe) so it can query us for the
-  // source stream info.
-  RefPtr<MediaOperationTask> mediaOperation =
-    new MediaOperationTask(MEDIA_STOP,
-                           this, nullptr, nullptr,
-                           !mAudioStopped ? mAudioDevice.get() : nullptr,
-                           !mVideoStopped ? mVideoDevice.get() : nullptr,
-                           false, mWindowID, nullptr);
-  MediaManager::PostTask(mediaOperation.forget());
-  mStopped = mAudioStopped = mVideoStopped = true;
+  // StopSharing() has some special logic, at least for audio capture.
+  // It must be called when all tracks have stopped, before setting mStopped.
+  StopSharing();
+
+  mStopped = true;
+
+  if (mAudioDevice && !mAudioStopped) {
+    StopTrack(kAudioTrack);
+  }
+  if (mVideoDevice && !mVideoStopped) {
+    StopTrack(kVideoTrack);
+  }
+  RefPtr<SourceMediaStream> source = GetSourceStream();
+  MediaManager::PostTask(NewTaskFrom([source]() {
+    MOZ_ASSERT(MediaManager::IsInMediaThread());
+    source->EndAllTrackAndFinish();
+  }));
 }
 
-// Doesn't kill audio
 void
-GetUserMediaCallbackMediaStreamListener::StopSharing()
+SourceListener::Remove()
 {
   MOZ_ASSERT(NS_IsMainThread());
+  if (!mStream || mRemoved) {
+    return;
+  }
+
+  MM_LOG(("SourceListener removed on purpose, mFinished = %d", (int) mFinished));
+  mRemoved = true; // RemoveListener is async, avoid races
+  mWindowListener = nullptr;
+
+  // If it's destroyed, don't call - listener will be removed and we'll be notified!
+  if (!mStream->IsDestroyed()) {
+    mStream->RemoveListener(this);
+  }
+}
+
+void
+SourceListener::StopTrack(TrackID aTrackID)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
+
+  RefPtr<MediaDevice> device;
+  RefPtr<SourceMediaStream> source;
+
+  switch (aTrackID) {
+    case kAudioTrack: {
+      if (!mAudioDevice) {
+        NS_ASSERTION(false, "Can't stop audio. No device.");
+        return;
+      }
+      if (mAudioStopped) {
+        // Audio already stopped
+        return;
+      }
+      device = mAudioDevice;
+      source = GetSourceStream();
+      mAudioStopped = true;
+      break;
+    }
+    case kVideoTrack: {
+      if (!mVideoDevice) {
+        NS_ASSERTION(false, "Can't stop video. No device.");
+        return;
+      }
+      if (mVideoStopped) {
+        // Video already stopped
+        return;
+      }
+      device = mVideoDevice;
+      source = GetSourceStream();
+      mVideoStopped = true;
+      break;
+    }
+    default: {
+      MOZ_ASSERT(false, "Unknown track id");
+      return;
+    }
+  }
+
+  MediaManager::PostTask(NewTaskFrom([device, source, aTrackID]() {
+    device->GetSource()->Stop(source, aTrackID);
+    device->Deallocate();
+  }));
+
+  if ((!mAudioDevice || mAudioStopped) &&
+      (!mVideoDevice || mVideoStopped)) {
+    Stop();
+  }
+
+  if (!mWindowListener) {
+    MOZ_ASSERT(false, "Should still have window listener");
+    return;
+  }
+  mWindowListener->NotifySourceTrackStopped();
+}
+
+void
+SourceListener::StopSharing()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_RELEASE_ASSERT(mWindowListener);
+
+  if (mStopped) {
+    return;
+  }
+
   if (mVideoDevice &&
       (mVideoDevice->GetMediaSource() == MediaSourceEnum::Screen ||
        mVideoDevice->GetMediaSource() == MediaSourceEnum::Application ||
        mVideoDevice->GetMediaSource() == MediaSourceEnum::Window)) {
     // We want to stop the whole stream if there's no audio;
     // just the video track if we have both.
     // StopTrack figures this out for us.
     StopTrack(kVideoTrack);
-  } else if (mAudioDevice &&
-             mAudioDevice->GetMediaSource() == MediaSourceEnum::AudioCapture) {
-    nsCOMPtr<nsPIDOMWindowInner> window = nsGlobalWindow::GetInnerWindowWithId(mWindowID)->AsInner();
-    MOZ_ASSERT(window);
+  }
+  if (mAudioDevice &&
+      mAudioDevice->GetMediaSource() == MediaSourceEnum::AudioCapture) {
+    uint64_t windowID = mWindowListener->WindowID();
+    nsCOMPtr<nsPIDOMWindowInner> window = nsGlobalWindow::GetInnerWindowWithId(windowID)->AsInner();
+    MOZ_RELEASE_ASSERT(window);
     window->SetAudioCapture(false);
     MediaStreamGraph* graph =
       MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER,
                                     dom::AudioChannel::Normal);
-    graph->UnregisterCaptureStreamForWindow(mWindowID);
+    graph->UnregisterCaptureStreamForWindow(windowID);
     mStream->Destroy();
   }
 }
 
-// ApplyConstraints for track
-
-auto
-GetUserMediaCallbackMediaStreamListener::ApplyConstraintsToTrack(
-    nsPIDOMWindowInner* aWindow,
-    TrackID aTrackID,
-    const MediaTrackConstraints& aConstraints,
-    dom::CallerType aCallerType) -> already_AddRefed<PledgeVoid>
+SourceMediaStream*
+SourceListener::GetSourceStream()
+{
+  NS_ASSERTION(mStream,"Getting stream from never-activated SourceListener");
+  if (!mStream) {
+    return nullptr;
+  }
+  return mStream->AsSourceStream();
+}
+
+void
+SourceListener::GetSettings(dom::MediaTrackSettings& aOutSettings, TrackID aTrackID)
+{
+  switch (aTrackID) {
+    case kVideoTrack: {
+      if (mVideoDevice) {
+        mVideoDevice->GetSource()->GetSettings(aOutSettings);
+      }
+      break;
+    }
+    case kAudioTrack: {
+      if (mAudioDevice) {
+        mAudioDevice->GetSource()->GetSettings(aOutSettings);
+      }
+      break;
+    }
+    default: {
+      MOZ_ASSERT(false, "Unknown track id");
+    }
+  }
+}
+
+// Proxy NotifyPull() to sources
+void
+SourceListener::NotifyPull(MediaStreamGraph* aGraph,
+                           StreamTime aDesiredTime)
+{
+  // Currently audio sources ignore NotifyPull, but they could
+  // watch it especially for fake audio.
+  if (mAudioDevice) {
+    mAudioDevice->GetSource()->NotifyPull(aGraph, mStream, kAudioTrack,
+                                          aDesiredTime, mPrincipalHandle);
+  }
+  if (mVideoDevice) {
+    mVideoDevice->GetSource()->NotifyPull(aGraph, mStream, kVideoTrack,
+                                          aDesiredTime, mPrincipalHandle);
+  }
+}
+
+void
+SourceListener::NotifyEvent(MediaStreamGraph* aGraph,
+                            MediaStreamGraphEvent aEvent)
+{
+  nsresult rv;
+  nsCOMPtr<nsIThread> thread;
+
+  switch (aEvent) {
+    case MediaStreamGraphEvent::EVENT_FINISHED:
+      rv = NS_GetMainThread(getter_AddRefs(thread));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        NS_ASSERTION(false, "Mainthread not available; running on current thread");
+        // Ensure this really *was* MainThread (NS_GetCurrentThread won't work)
+        MOZ_RELEASE_ASSERT(mMainThreadCheck == PR_GetCurrentThread());
+        NotifyFinished();
+        return;
+      }
+      thread->Dispatch(NewRunnableMethod(this, &SourceListener::NotifyFinished),
+                       NS_DISPATCH_NORMAL);
+      break;
+    case MediaStreamGraphEvent::EVENT_REMOVED:
+      rv = NS_GetMainThread(getter_AddRefs(thread));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        NS_ASSERTION(false, "Mainthread not available; running on current thread");
+        // Ensure this really *was* MainThread (NS_GetCurrentThread won't work)
+        MOZ_RELEASE_ASSERT(mMainThreadCheck == PR_GetCurrentThread());
+        NotifyRemoved();
+        return;
+      }
+      thread->Dispatch(NewRunnableMethod(this, &SourceListener::NotifyRemoved),
+                       NS_DISPATCH_NORMAL);
+      break;
+    case MediaStreamGraphEvent::EVENT_HAS_DIRECT_LISTENERS:
+      NotifyDirectListeners(aGraph, true);
+      break;
+    case MediaStreamGraphEvent::EVENT_HAS_NO_DIRECT_LISTENERS:
+      NotifyDirectListeners(aGraph, false);
+      break;
+    default:
+      break;
+  }
+}
+
+void
+SourceListener::NotifyFinished()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mFinished = true;
+  if (!mWindowListener) {
+    // Removed explicitly before finished.
+    return;
+  }
+
+  Stop(); // we know it's been activated
+  mWindowListener->Remove(this);
+}
+
+void
+SourceListener::NotifyRemoved()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MM_LOG(("SourceListener removed, mFinished = %d", (int) mFinished));
+  mRemoved = true;
+
+  if (!mFinished) {
+    NotifyFinished();
+  }
+
+  mWindowListener = nullptr;
+}
+
+void
+SourceListener::NotifyDirectListeners(MediaStreamGraph* aGraph,
+                                      bool aHasListeners)
+{
+  if (!mVideoDevice) {
+    return;
+  }
+
+  auto& videoDevice = mVideoDevice;
+  MediaManager::PostTask(NewTaskFrom([videoDevice, aHasListeners]() {
+    videoDevice->GetSource()->SetDirectListeners(aHasListeners);
+    return NS_OK;
+  }));
+}
+
+bool
+SourceListener::CapturingVideo() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mActivated && mVideoDevice && !mVideoStopped &&
+         !mVideoDevice->GetSource()->IsAvailable() &&
+         mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Camera &&
+         (!mVideoDevice->GetSource()->IsFake() ||
+          Preferences::GetBool("media.navigator.permission.fake"));
+}
+
+bool
+SourceListener::CapturingAudio() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mActivated && mAudioDevice && !mAudioStopped &&
+         !mAudioDevice->GetSource()->IsAvailable() &&
+         (!mAudioDevice->GetSource()->IsFake() ||
+          Preferences::GetBool("media.navigator.permission.fake"));
+}
+
+bool
+SourceListener::CapturingScreen() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mActivated && mVideoDevice && !mVideoStopped &&
+         !mVideoDevice->GetSource()->IsAvailable() &&
+         mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Screen;
+}
+
+bool
+SourceListener::CapturingWindow() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mActivated && mVideoDevice && !mVideoStopped &&
+         !mVideoDevice->GetSource()->IsAvailable() &&
+         mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Window;
+}
+
+bool
+SourceListener::CapturingApplication() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mActivated && mVideoDevice && !mVideoStopped &&
+         !mVideoDevice->GetSource()->IsAvailable() &&
+         mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Application;
+}
+
+bool
+SourceListener::CapturingBrowser() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mActivated && mVideoDevice && !mVideoStopped &&
+         !mVideoDevice->GetSource()->IsAvailable() &&
+         mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Browser;
+}
+
+already_AddRefed<PledgeVoid>
+SourceListener::ApplyConstraintsToTrack(nsPIDOMWindowInner* aWindow,
+                                        TrackID aTrackID,
+                                        const dom::MediaTrackConstraints& aConstraints,
+                                        dom::CallerType aCallerType)
 {
   MOZ_ASSERT(NS_IsMainThread());
   RefPtr<PledgeVoid> p = new PledgeVoid();
 
   // XXX to support multiple tracks of a type in a stream, this should key off
   // the TrackID and not just the type
   RefPtr<AudioDevice> audioDevice =
     aTrackID == kAudioTrack ? mAudioDevice.get() : nullptr;
@@ -3552,167 +3831,76 @@ GetUserMediaCallbackMediaStreamListener:
         }
       }
       return NS_OK;
     }));
   }));
   return p.forget();
 }
 
-// Stop backend for track
+PrincipalHandle
+SourceListener::GetPrincipalHandle() const
+{
+  return mPrincipalHandle;
+}
+
+// Doesn't kill audio
+void
+GetUserMediaWindowListener::StopSharing()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
+
+  for (auto& source : mActiveListeners) {
+    source->StopSharing();
+  }
+}
 
 void
-GetUserMediaCallbackMediaStreamListener::StopTrack(TrackID aTrackID)
+GetUserMediaWindowListener::NotifySourceTrackStopped()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aTrackID == kAudioTrack || aTrackID == kVideoTrack);
-
-  // XXX to support multiple tracks of a type in a stream, this should key off
-  // the TrackID and not just hard coded values.
-
-  bool stopAudio = aTrackID == kAudioTrack;
-  bool stopVideo = aTrackID == kVideoTrack;
-
-  if (mStopped ||
-      (stopAudio && (mAudioStopped || !mAudioDevice)) ||
-      (stopVideo && (mVideoStopped || !mVideoDevice)))
-  {
-    LOG(("Can't stop gUM track %d (%s), exists=%d, stopped=%d",
-         aTrackID,
-         stopAudio ? "audio" : "video",
-         stopAudio ? !!mAudioDevice : !!mVideoDevice,
-         stopAudio ? mAudioStopped : mVideoStopped));
-    return;
-  }
-
-  if ((stopAudio || mAudioStopped || !mAudioDevice) &&
-      (stopVideo || mVideoStopped || !mVideoDevice)) {
-    Stop();
-    return;
-  }
 
   // We wait until stable state before notifying chrome so chrome only does one
   // update if more tracks are stopped in this event loop.
 
-  mAudioStopPending |= stopAudio;
-  mVideoStopPending |= stopVideo;
-
   if (mChromeNotificationTaskPosted) {
     return;
   }
 
   nsCOMPtr<nsIRunnable> runnable =
-    NewRunnableMethod(this, &GetUserMediaCallbackMediaStreamListener::NotifyChromeOfTrackStops);
+    NewRunnableMethod(this, &GetUserMediaWindowListener::NotifyChromeOfTrackStops);
   nsContentUtils::RunInStableState(runnable.forget());
   mChromeNotificationTaskPosted = true;
 }
 
 void
-GetUserMediaCallbackMediaStreamListener::NotifyChromeOfTrackStops()
+GetUserMediaWindowListener::NotifyChromeOfTrackStops()
 {
   MOZ_ASSERT(mChromeNotificationTaskPosted);
   mChromeNotificationTaskPosted = false;
 
-  // We make sure these are always reset.
-  bool stopAudio = mAudioStopPending;
-  bool stopVideo = mVideoStopPending;
-  mAudioStopPending = false;
-  mVideoStopPending = false;
-
-  if (mStopped) {
-    // The entire capture was stopped while we were waiting for stable state.
-    return;
-  }
-
-  MOZ_ASSERT(stopAudio || stopVideo);
-  MOZ_ASSERT(!stopAudio || !mAudioStopped,
-             "If there's a pending stop for audio, audio must not have been stopped");
-  MOZ_ASSERT(!stopAudio || mAudioDevice,
-             "If there's a pending stop for audio, there must be an audio device");
-  MOZ_ASSERT(!stopVideo || !mVideoStopped,
-             "If there's a pending stop for video, video must not have been stopped");
-  MOZ_ASSERT(!stopVideo || mVideoDevice,
-             "If there's a pending stop for video, there must be a video device");
-
-  if ((stopAudio || mAudioStopped || !mAudioDevice) &&
-      (stopVideo || mVideoStopped || !mVideoDevice)) {
-    // All tracks stopped.
-    Stop();
-    return;
-  }
-
-  mAudioStopped |= stopAudio;
-  mVideoStopped |= stopVideo;
-
-  RefPtr<MediaOperationTask> mediaOperation =
-    new MediaOperationTask(MEDIA_STOP_TRACK,
-                           this, nullptr, nullptr,
-                           stopAudio ? mAudioDevice.get() : nullptr,
-                           stopVideo ? mVideoDevice.get() : nullptr,
-                           false , mWindowID, nullptr);
-  MediaManager::PostTask(mediaOperation.forget());
-}
-
-void
-GetUserMediaCallbackMediaStreamListener::NotifyFinished()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  mFinished = true;
-  Stop(); // we know it's been activated
-
-  RefPtr<MediaManager> manager(MediaManager::GetIfExists());
-  if (manager) {
-    manager->RemoveFromWindowList(mWindowID, this);
-  } else {
-    NS_WARNING("Late NotifyFinished after MediaManager shutdown");
-  }
-}
-
-// Called from the MediaStreamGraph thread
-void
-GetUserMediaCallbackMediaStreamListener::NotifyDirectListeners(MediaStreamGraph* aGraph,
-                                                               bool aHasListeners)
-{
-  RefPtr<MediaOperationTask> mediaOperation =
-    new MediaOperationTask(MEDIA_DIRECT_LISTENERS,
-                           this, nullptr, nullptr,
-                           mAudioDevice, mVideoDevice,
-                           aHasListeners, mWindowID, nullptr);
-  MediaManager::PostTask(mediaOperation.forget());
-}
-
-// this can be in response to our own RemoveListener() (via ::Remove()), or
-// because the DOM GC'd the DOMLocalMediaStream/etc we're attached to.
-void
-GetUserMediaCallbackMediaStreamListener::NotifyRemoved()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MM_LOG(("Listener removed by DOM Destroy(), mFinished = %d", (int) mFinished));
-  mRemoved = true;
-
-  if (!mFinished) {
-    NotifyFinished();
-  }
+  NS_DispatchToMainThread(do_AddRef(new GetUserMediaNotificationEvent(
+    GetUserMediaNotificationEvent::STOPPING, mWindowID)));
 }
 
 GetUserMediaNotificationEvent::GetUserMediaNotificationEvent(
-    GetUserMediaCallbackMediaStreamListener* aListener,
     GetUserMediaStatus aStatus,
-    bool aIsAudio, bool aIsVideo, uint64_t aWindowID)
-: mListener(aListener) , mStatus(aStatus) , mIsAudio(aIsAudio)
-, mIsVideo(aIsVideo), mWindowID(aWindowID) {}
+    uint64_t aWindowID)
+: mStatus(aStatus), mWindowID(aWindowID) {}
 
 GetUserMediaNotificationEvent::GetUserMediaNotificationEvent(
     GetUserMediaStatus aStatus,
     already_AddRefed<DOMMediaStream> aStream,
-    OnTracksAvailableCallback* aOnTracksAvailableCallback,
-    bool aIsAudio, bool aIsVideo, uint64_t aWindowID,
+    already_AddRefed<Refcountable<UniquePtr<OnTracksAvailableCallback>>> aOnTracksAvailableCallback,
+    uint64_t aWindowID,
     already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError)
-: mStream(aStream), mOnTracksAvailableCallback(aOnTracksAvailableCallback),
-  mStatus(aStatus), mIsAudio(aIsAudio), mIsVideo(aIsVideo), mWindowID(aWindowID),
+: mStream(aStream),
+  mOnTracksAvailableCallback(aOnTracksAvailableCallback),
+  mStatus(aStatus),
+  mWindowID(aWindowID),
   mOnFailure(aError) {}
 GetUserMediaNotificationEvent::~GetUserMediaNotificationEvent()
 {
 }
 
 NS_IMETHODIMP
 GetUserMediaNotificationEvent::Run()
 {
@@ -3722,23 +3910,22 @@ GetUserMediaNotificationEvent::Run()
   // Otherwise this object might be destroyed off the main thread,
   // releasing DOMMediaStream off the main thread, which is not allowed.
   RefPtr<DOMMediaStream> stream = mStream.forget();
 
   nsString msg;
   switch (mStatus) {
   case STARTING:
     msg = NS_LITERAL_STRING("starting");
-    stream->OnTracksAvailable(mOnTracksAvailableCallback.forget());
+    stream->OnTracksAvailable(mOnTracksAvailableCallback->release());
     break;
   case STOPPING:
-  case STOPPED_TRACK:
     msg = NS_LITERAL_STRING("shutdown");
     break;
   }
 
   RefPtr<nsGlobalWindow> window = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
   NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
 
-  return MediaManager::NotifyRecordingStatusChange(window->AsInner(), msg, mIsAudio, mIsVideo);
+  return MediaManager::NotifyRecordingStatusChange(window->AsInner(), msg);
 }
 
 } // namespace mozilla
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -50,19 +50,20 @@ struct MediaTrackConstraints;
 struct MediaTrackConstraintSet;
 enum class CallerType : uint32_t;
 } // namespace dom
 
 namespace ipc {
 class PrincipalInfo;
 }
 
+class GetUserMediaTask;
+class GetUserMediaWindowListener;
 class MediaManager;
-class GetUserMediaCallbackMediaStreamListener;
-class GetUserMediaTask;
+class SourceListener;
 
 extern LogModule* GetMediaManagerLog();
 #define MM_LOG(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, msg)
 
 class MediaDevice : public nsIMediaDevice
 {
 public:
   typedef MediaEngineSource Source;
@@ -131,76 +132,71 @@ public:
 };
 
 class GetUserMediaNotificationEvent: public Runnable
 {
   public:
     enum GetUserMediaStatus {
       STARTING,
       STOPPING,
-      STOPPED_TRACK,
     };
-    GetUserMediaNotificationEvent(GetUserMediaCallbackMediaStreamListener* aListener,
-                                  GetUserMediaStatus aStatus,
-                                  bool aIsAudio, bool aIsVideo, uint64_t aWindowID);
+    GetUserMediaNotificationEvent(GetUserMediaStatus aStatus,
+                                  uint64_t aWindowID);
 
     GetUserMediaNotificationEvent(GetUserMediaStatus aStatus,
                                   already_AddRefed<DOMMediaStream> aStream,
-                                  OnTracksAvailableCallback* aOnTracksAvailableCallback,
-                                  bool aIsAudio, bool aIsVideo, uint64_t aWindowID,
+                                  already_AddRefed<media::Refcountable<UniquePtr<OnTracksAvailableCallback>>> aOnTracksAvailableCallback,
+                                  uint64_t aWindowID,
                                   already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError);
     virtual ~GetUserMediaNotificationEvent();
 
     NS_IMETHOD Run() override;
 
   protected:
-    RefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
+    RefPtr<GetUserMediaWindowListener> mListener; // threadsafe
     RefPtr<DOMMediaStream> mStream;
-    nsAutoPtr<OnTracksAvailableCallback> mOnTracksAvailableCallback;
+    RefPtr<media::Refcountable<UniquePtr<OnTracksAvailableCallback>>> mOnTracksAvailableCallback;
     GetUserMediaStatus mStatus;
-    bool mIsAudio;
-    bool mIsVideo;
     uint64_t mWindowID;
     RefPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
 };
 
 typedef enum {
-  MEDIA_START,
   MEDIA_STOP,
   MEDIA_STOP_TRACK,
   MEDIA_DIRECT_LISTENERS,
 } MediaOperation;
 
 class ReleaseMediaOperationResource : public Runnable
 {
 public:
-  ReleaseMediaOperationResource(already_AddRefed<DOMMediaStream> aStream,
-    OnTracksAvailableCallback* aOnTracksAvailableCallback):
+  ReleaseMediaOperationResource(
+    already_AddRefed<DOMMediaStream> aStream,
+    already_AddRefed<media::Refcountable<UniquePtr<OnTracksAvailableCallback>>> aOnTracksAvailableCallback):
     mStream(aStream),
     mOnTracksAvailableCallback(aOnTracksAvailableCallback) {}
   NS_IMETHOD Run() override {return NS_OK;}
 private:
   RefPtr<DOMMediaStream> mStream;
-  nsAutoPtr<OnTracksAvailableCallback> mOnTracksAvailableCallback;
+  RefPtr<media::Refcountable<UniquePtr<OnTracksAvailableCallback>>> mOnTracksAvailableCallback;
 };
 
-typedef nsTArray<RefPtr<GetUserMediaCallbackMediaStreamListener> > StreamListeners;
-typedef nsClassHashtable<nsUint64HashKey, StreamListeners> WindowTable;
+typedef nsRefPtrHashtable<nsUint64HashKey, GetUserMediaWindowListener> WindowTable;
 
 // we could add MediaManager if needed
 typedef void (*WindowListenerCallback)(MediaManager *aThis,
                                        uint64_t aWindowID,
-                                       StreamListeners *aListeners,
+                                       GetUserMediaWindowListener *aListener,
                                        void *aData);
 
 class MediaManager final : public nsIMediaManagerService,
                            public nsIObserver
                           ,public DeviceChangeCallback
 {
-  friend GetUserMediaCallbackMediaStreamListener;
+  friend SourceListener;
 public:
   static already_AddRefed<MediaManager> GetInstance();
 
   // NOTE: never Dispatch(....,NS_DISPATCH_SYNC) to the MediaManager
   // thread from the MainThread, as we NS_DISPATCH_SYNC to MainThread
   // from MediaManager thread.
   static MediaManager* Get();
   static MediaManager* GetIfExists();
@@ -211,37 +207,41 @@ public:
 #endif
 
   static bool Exists()
   {
     return !!sSingleton;
   }
 
   static nsresult NotifyRecordingStatusChange(nsPIDOMWindowInner* aWindow,
-                                              const nsString& aMsg,
-                                              const bool& aIsAudio,
-                                              const bool& aIsVideo);
+                                              const nsString& aMsg);
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIOBSERVER
   NS_DECL_NSIMEDIAMANAGERSERVICE
 
   media::Parent<media::NonE10s>* GetNonE10sParent();
   MediaEngine* GetBackend(uint64_t aWindowId = 0);
-  StreamListeners *GetWindowListeners(uint64_t aWindowId) {
+
+  WindowTable *GetActiveWindows() {
     MOZ_ASSERT(NS_IsMainThread());
-    return mActiveWindows.Get(aWindowId);
+    return &mActiveWindows;
   }
+  GetUserMediaWindowListener *GetWindowListener(uint64_t aWindowId) {
+    MOZ_ASSERT(NS_IsMainThread());
+    return mActiveWindows.GetWeak(aWindowId);
+  }
+  void AddWindowID(uint64_t aWindowId, GetUserMediaWindowListener *aListener);
   void RemoveWindowID(uint64_t aWindowId);
   bool IsWindowStillActive(uint64_t aWindowId) {
-    return !!GetWindowListeners(aWindowId);
+    return !!GetWindowListener(aWindowId);
   }
   // Note: also calls aListener->Remove(), even if inactive
   void RemoveFromWindowList(uint64_t aWindowID,
-    GetUserMediaCallbackMediaStreamListener *aListener);
+    GetUserMediaWindowListener *aListener);
 
   nsresult GetUserMedia(
     nsPIDOMWindowInner* aWindow,
     const dom::MediaStreamConstraints& aConstraints,
     nsIDOMGetUserMediaSuccessCallback* onSuccess,
     nsIDOMGetUserMediaErrorCallback* onError,
     dom::CallerType aCallerType);
 
@@ -288,22 +288,16 @@ private:
                        dom::MediaSourceEnum aAudioSrcType,
                        bool aFake = false);
   already_AddRefed<PledgeChar>
   SelectSettings(
       dom::MediaStreamConstraints& aConstraints,
       bool aIsChrome,
       RefPtr<media::Refcountable<UniquePtr<SourceSet>>>& aSources);
 
-  StreamListeners* AddWindowID(uint64_t aWindowId);
-  WindowTable *GetActiveWindows() {
-    MOZ_ASSERT(NS_IsMainThread());
-    return &mActiveWindows;
-  }
-
   void GetPref(nsIPrefBranch *aBranch, const char *aPref,
                const char *aData, int32_t *aVal);
   void GetPrefBool(nsIPrefBranch *aBranch, const char *aPref,
                    const char *aData, bool *aVal);
   void GetPrefs(nsIPrefBranch *aBranch, const char *aData);
 
   // Make private because we want only one instance of this class
   MediaManager();