Backed out 5 changesets (bug 1320994) for browser_downloads_panel_block.js permafails on Win7VM a=backout CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Wed, 26 Apr 2017 15:21:54 -0700
changeset 403156 745f2d85212d7c4bc82240c5a43730d9ecd32125
parent 403155 0ab29e9e984f9e58123a18a938caecf2c8c35d8b
child 403208 0b77ed3f26c5335503bc16e85b8c067382e7bb1e
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbackout
bugs1320994
milestone55.0a1
backs outb744a070cafb37c8af2d7c7877f42127747276d1
5766ba7143b465bd426a2601f2dd1274cd62fcd4
31afc3fcd61f31d452d58fa65d0aaf0a907c006f
78e5f8775727b45489bb0178f702a503c0e6cb4c
6d4e1736fd690e2dcfe963257bb2f42fcd641c10
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out 5 changesets (bug 1320994) for browser_downloads_panel_block.js permafails on Win7VM a=backout CLOSED TREE Backed out changeset b744a070cafb (bug 1320994) Backed out changeset 5766ba7143b4 (bug 1320994) Backed out changeset 31afc3fcd61f (bug 1320994) Backed out changeset 78e5f8775727 (bug 1320994) Backed out changeset 6d4e1736fd69 (bug 1320994) MozReview-Commit-ID: 9tPD6gwXttd
browser/base/content/test/webrtc/browser.ini
browser/base/content/test/webrtc/browser_devices_get_user_media_screen.js
browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access.js
browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access_in_frame.js
browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access_tear_off_tab.js
browser/base/content/test/webrtc/head.js
dom/media/MediaManager.cpp
dom/media/MediaManager.h
--- a/browser/base/content/test/webrtc/browser.ini
+++ b/browser/base/content/test/webrtc/browser.ini
@@ -7,14 +7,15 @@ support-files =
 
 [browser_devices_get_user_media.js]
 skip-if = (os == "linux" && debug) # linux: bug 976544
 [browser_devices_get_user_media_anim.js]
 [browser_devices_get_user_media_in_frame.js]
 [browser_devices_get_user_media_multi_process.js]
 skip-if = e10s && (asan || debug) # bug 1347625
 [browser_devices_get_user_media_screen.js]
+skip-if = (os == "linux") || (os == "win" && !debug) # bug 1320994 for linux opt, bug 1338038 for windows and linux debug
 [browser_devices_get_user_media_tear_off_tab.js]
 [browser_devices_get_user_media_unprompted_access.js]
 [browser_devices_get_user_media_unprompted_access_in_frame.js]
 [browser_devices_get_user_media_unprompted_access_tear_off_tab.js]
-skip-if = (os == "win" && bits == 64) # win8: bug 1334752
+skip-if = (os == "linux") || (os == "win" && bits == 64) # linux: bug 1331616, win8: bug 1334752
 [browser_webrtc_hooks.js]
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media_screen.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_screen.js
@@ -405,30 +405,30 @@ var gTests = [
     yield share(false, true);
     yield check({video: true, audio: true, screen: "Screen"});
 
     info("Stop the screen share, mic+cam should continue");
     yield stopSharing("screen", true);
     yield check({video: true, audio: true});
 
     info("Stop the camera, everything should stop.");
-    yield stopSharing("camera");
+    yield stopSharing("camera", false, true);
 
     info("Now, share only the screen...");
     indicator = promiseIndicatorWindow();
     yield share(false, false, true);
     yield indicator;
     yield check({screen: "Screen"});
 
     info("... and add camera and microphone in a second request.");
     yield share(true, true);
     yield check({video: true, audio: true, screen: "Screen"});
 
     info("Stop the camera, this should stop everything.");
-    yield stopSharing("camera");
+    yield stopSharing("camera", false, true);
   }
 },
 
 {
   desc: "getUserMedia screen: reloading the page removes all gUM UI",
   run: function* checkReloading() {
     let promise = promisePopupNotificationShown("webRTC-shareDevices");
     yield promiseRequestDevice(false, true, null, "screen");
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access.js
@@ -57,17 +57,17 @@ var gTests = [
     });
 
     yield expectObserverCalled("getUserMedia:response:deny");
     SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
     SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
     SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
 
     // After closing all streams, gUM(audio+camera) causes a prompt.
-    yield closeStream();
+    yield closeStream(false, 0, 2);
     promise = promisePopupNotificationShown("webRTC-shareDevices");
     yield promiseRequestDevice(true, true);
     yield promise;
     yield expectObserverCalled("getUserMedia:request");
     checkDeviceSelectors(true, true);
 
     yield promiseMessage(permissionError, () => {
       activateSecondaryAction(kActionDeny);
@@ -164,17 +164,17 @@ var gTests = [
     yield expectObserverCalled("recording-device-events");
 
     Assert.deepEqual((yield getMediaCaptureState()), {video: true},
                      "expected camera to be shared");
 
     yield checkSharingUI({audio: false, video: true});
 
     // close all streams
-    yield closeStream();
+    yield closeStream(false, 0, 2);
   }
 },
 
 {
   desc: "getUserMedia audio",
   run: function* checkAudioVideoWhileLiveTracksExist_audio() {
     let promise = promisePopupNotificationShown("webRTC-shareDevices");
     yield promiseRequestDevice(true, false);
@@ -236,17 +236,17 @@ var gTests = [
     yield expectObserverCalled("recording-device-events");
 
     Assert.deepEqual((yield getMediaCaptureState()), {audio: true},
                      "expected microphone to be shared");
 
     yield checkSharingUI({audio: true, video: false});
 
     // close all streams
-    yield closeStream();
+    yield closeStream(false, 0, 2);
   }
 }
 
 ];
 
 add_task(async function test() {
   await runTests(gTests);
 });
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access_in_frame.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access_in_frame.js
@@ -52,17 +52,17 @@ var gTests = [
     yield promiseRequestDevice(true, true, "frame1");
     yield promise;
     yield expectObserverCalled("getUserMedia:request");
     yield promiseNoPopupNotification("webRTC-shareDevices");
     yield expectObserverCalled("getUserMedia:response:allow");
     yield expectObserverCalled("recording-device-events");
 
     // close the stream
-    yield closeStream(false, "frame1");
+    yield closeStream(false, "frame1", 2);
   }
 },
 
 {
   desc: "getUserMedia audio+camera in frame 1 - part II",
   run: function* checkAudioVideoWhileLiveTracksExist_frame_partII() {
     let promise = promisePopupNotificationShown("webRTC-shareDevices");
     yield promiseRequestDevice(true, true, "frame1");
@@ -192,17 +192,17 @@ var gTests = [
     yield promiseMessage(permissionError, () => {
       activateSecondaryAction(kActionDeny);
     });
 
     yield expectObserverCalled("getUserMedia:response:deny");
     yield expectObserverCalled("recording-window-ended");
 
     // close the stream
-    yield closeStream();
+    yield closeStream(false);
     SitePermissions.remove(null, "screen", gBrowser.selectedBrowser);
     SitePermissions.remove(null, "camera", gBrowser.selectedBrowser);
     SitePermissions.remove(null, "microphone", gBrowser.selectedBrowser);
   }
 }
 
 ];
 
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access_tear_off_tab.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_unprompted_access_tear_off_tab.js
@@ -36,16 +36,17 @@ var gTests = [
     yield promiseRequestDevice(true, true, null, null, win.gBrowser.selectedBrowser);
     yield promiseObserverCalled("getUserMedia:request");
     let promises = [promiseNoPopupNotification("webRTC-shareDevices"),
                     promiseObserverCalled("getUserMedia:response:allow"),
                     promiseObserverCalled("recording-device-events")];
     yield Promise.all(promises);
 
     promises = [promiseObserverCalled("recording-device-events"),
+                promiseObserverCalled("recording-device-events"),
                 promiseObserverCalled("recording-window-ended")];
     yield BrowserTestUtils.closeWindow(win);
     yield Promise.all(promises);
 
     yield checkNotSharing();
   }
 }
 
--- a/browser/base/content/test/webrtc/head.js
+++ b/browser/base/content/test/webrtc/head.js
@@ -210,27 +210,35 @@ function expectObserverCalled(aTopic) {
       is(data.count, 1, "expected notification " + aTopic);
       mm.removeMessageListener("Test:ExpectObserverCalled:Reply", listener);
       resolve();
     });
     mm.sendAsyncMessage("Test:ExpectObserverCalled", aTopic);
   });
 }
 
-function expectNoObserverCalled() {
+function expectNoObserverCalled(aIgnoreDeviceEvents = false) {
   return new Promise(resolve => {
     let mm = _mm();
     mm.addMessageListener("Test:ExpectNoObserverCalled:Reply",
                           function listener({data}) {
       mm.removeMessageListener("Test:ExpectNoObserverCalled:Reply", listener);
       for (let topic in data) {
         if (!data[topic])
           continue;
 
-        is(data[topic], 0, topic + " notification unexpected");
+        // If we are stopping tracks that were created from 2 different
+        // getUserMedia calls, the "recording-device-events" notification is
+        // fired twice on Windows and Mac, and intermittently twice on Linux.
+        if (topic == "recording-device-events" && aIgnoreDeviceEvents) {
+          todo(false, "Got " + data[topic] + " unexpected " + topic +
+               " notifications, see bug 1320994");
+        } else {
+          is(data[topic], 0, topic + " notification unexpected");
+        }
       }
       resolve();
     });
     mm.sendAsyncMessage("Test:ExpectNoObserverCalled");
   });
 }
 
 function ignoreObserversCalled() {
@@ -341,34 +349,35 @@ function getMediaCaptureState() {
     let mm = _mm();
     mm.addMessageListener("Test:MediaCaptureState", ({data}) => {
       resolve(data);
     });
     mm.sendAsyncMessage("Test:GetMediaCaptureState");
   });
 }
 
-function* stopSharing(aType = "camera", aShouldKeepSharing = false) {
+function* stopSharing(aType = "camera", aShouldKeepSharing = false,
+                      aExpectDoubleRecordingEvent = false) {
   let promiseRecordingEvent = promiseObserverCalled("recording-device-events");
   gIdentityHandler._identityBox.click();
   let permissions = document.getElementById("identity-popup-permission-list");
   let cancelButton =
     permissions.querySelector(".identity-popup-permission-icon." + aType + "-icon ~ " +
                               ".identity-popup-permission-remove-button");
   cancelButton.click();
   gIdentityHandler._identityPopup.hidden = true;
   yield promiseRecordingEvent;
   yield expectObserverCalled("getUserMedia:revoke");
 
   // If we are stopping screen sharing and expect to still have another stream,
   // "recording-window-ended" won't be fired.
   if (!aShouldKeepSharing)
     yield expectObserverCalled("recording-window-ended");
 
-  yield expectNoObserverCalled();
+  yield expectNoObserverCalled(aExpectDoubleRecordingEvent);
 
   if (!aShouldKeepSharing)
     yield* checkNotSharing();
 }
 
 function promiseRequestDevice(aRequestAudio, aRequestVideo, aFrameId, aType,
                               aBrowser = gBrowser.selectedBrowser) {
   info("requesting devices");
@@ -377,23 +386,26 @@ function promiseRequestDevice(aRequestAu
                            function*(args) {
     let global = content.wrappedJSObject;
     if (args.aFrameId)
       global = global.document.getElementById(args.aFrameId).contentWindow;
     global.requestDevice(args.aRequestAudio, args.aRequestVideo, args.aType);
   });
 }
 
-function* closeStream(aAlreadyClosed, aFrameId) {
+function* closeStream(aAlreadyClosed, aFrameId, aStreamCount = 1) {
   yield expectNoObserverCalled();
 
   let promises;
   if (!aAlreadyClosed) {
-    promises = [promiseObserverCalled("recording-device-events"),
-                promiseObserverCalled("recording-window-ended")];
+    promises = [];
+    for (let i = 0; i < aStreamCount; i++) {
+      promises.push(promiseObserverCalled("recording-device-events"));
+    }
+    promises.push(promiseObserverCalled("recording-window-ended"));
   }
 
   info("closing the stream");
   yield ContentTask.spawn(gBrowser.selectedBrowser, aFrameId, function*(contentFrameId) {
     let global = content.wrappedJSObject;
     if (contentFrameId)
       global = global.document.getElementById(contentFrameId).contentWindow;
     global.closeStream();
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -152,514 +152,474 @@ 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;
 }
 
-class SourceListener : public MediaStreamListener {
+/**
+ * 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;
 public:
-  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,
+  // 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,
                 AudioDevice* aAudioDevice,
-                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
+                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
   {
     return mStream;
   }
-
-  SourceMediaStream* GetSourceStream();
-
-  AudioDevice* GetAudioDevice() const
+  SourceMediaStream *GetSourceStream()
   {
-    return mAudioDevice;
-  }
-
-  VideoDevice* GetVideoDevice() const
-  {
-    return mVideoDevice;
+    NS_ASSERTION(mStream,"Getting stream from never-activated GUMCMSListener");
+    if (!mStream) {
+      return nullptr;
+    }
+    return mStream->AsSourceStream();
   }
 
-  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;
+  void StopSharing();
+
+  void StopTrack(TrackID aID);
+
+  void NotifyChromeOfTrackStops();
+
+  typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
 
   already_AddRefed<PledgeVoid>
   ApplyConstraintsToTrack(nsPIDOMWindowInner* aWindow,
-                          TrackID aTrackID,
+                          TrackID aID,
                           const dom::MediaTrackConstraints& aConstraints,
                           dom::CallerType aCallerType);
 
-  PrincipalHandle GetPrincipalHandle() const;
+  // 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; }
 
 private:
-  // 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.
+  // 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.
   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 stopped mAudioDevice. MainThread only.
+  // true if we have sent MEDIA_STOP or MEDIA_STOP_TRACK for mAudioDevice.
+  // MainThread only.
   bool mAudioStopped;
 
-  // true if we have stopped mVideoDevice. MainThread only.
+  // 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.
   bool mVideoStopped;
 
-  // 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;
+  // 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;
 
   // 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
 };
 
-/**
- * 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
+// 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
 {
-  friend MediaManager;
 public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GetUserMediaWindowListener)
-
-  // Create in an inactive state
-  GetUserMediaWindowListener(base::Thread *aThread,
+  // 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,
     uint64_t aWindowID,
-    const PrincipalHandle& aPrincipalHandle)
-    : mMediaThread(aThread)
+    already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
+    const dom::MediaTrackConstraints& aConstraints = dom::MediaTrackConstraints())
+    : mType(aType)
+    , mStream(aStream)
+    , mOnTracksAvailableCallback(aOnTracksAvailableCallback)
+    , mAudioDevice(aAudioDevice)
+    , mVideoDevice(aVideoDevice)
+    , mListener(aListener)
+    , mBool(aBool)
     , mWindowID(aWindowID)
-    , mPrincipalHandle(aPrincipalHandle)
-    , mChromeNotificationTaskPosted(false)
+    , mOnFailure(aError)
+    , mConstraints(aConstraints)
   {}
 
-  /**
-   * Registers an inactive gUM source listener for this WindowListener.
-   */
-  void Register(SourceListener* aListener)
+  ~MediaOperationTask()
   {
-    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);
+    // MediaStreams can be released on any thread.
   }
 
-  /**
-   * 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()
+  void
+  ReturnCallbackError(nsresult rv, const char* errorLog);
+
+  NS_IMETHOD
+  Run() override
   {
-    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;
+    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);
           }
         }
-      }
-
-      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);
-      }
+        break;
+
+      default:
+        MOZ_ASSERT(false,"invalid MediaManager operation");
+        break;
     }
 
-    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 true;
+    return NS_OK;
   }
 
-  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:
-  ~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;
-
+  MediaOperation mType;
+  RefPtr<DOMMediaStream> mStream;
+  nsAutoPtr<OnTracksAvailableCallback> mOnTracksAvailableCallback;
+  RefPtr<AudioDevice> mAudioDevice; // threadsafe
+  RefPtr<VideoDevice> mVideoDevice; // threadsafe
+  RefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
+  bool mBool;
   uint64_t mWindowID;
-  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;
+  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
+  dom::MediaTrackConstraints mConstraints;
 };
 
 /**
  * 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);
@@ -694,16 +654,39 @@ 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())
@@ -906,16 +889,35 @@ 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;
@@ -980,44 +982,42 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(FakeT
  */
 class GetUserMediaStreamRunnable : public Runnable
 {
 public:
   GetUserMediaStreamRunnable(
     nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aOnSuccess,
     nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aOnFailure,
     uint64_t aWindowID,
-    GetUserMediaWindowListener* aWindowListener,
-    SourceListener* aSourceListener,
+    GetUserMediaCallbackMediaStreamListener* aListener,
     const ipc::PrincipalInfo& aPrincipalInfo,
     const MediaStreamConstraints& aConstraints,
     AudioDevice* aAudioDevice,
     VideoDevice* aVideoDevice,
     PeerIdentity* aPeerIdentity)
     : mConstraints(aConstraints)
     , mAudioDevice(aAudioDevice)
     , mVideoDevice(aVideoDevice)
     , mWindowID(aWindowID)
-    , mWindowListener(aWindowListener)
-    , mSourceListener(aSourceListener)
+    , mListener(aListener)
     , mPrincipalInfo(aPrincipalInfo)
     , mPeerIdentity(aPeerIdentity)
     , mManager(MediaManager::GetInstance())
   {
     mOnSuccess.swap(aOnSuccess);
     mOnFailure.swap(aOnFailure);
   }
 
   ~GetUserMediaStreamRunnable() {}
 
   class TracksAvailableCallback : public OnTracksAvailableCallback
   {
   public:
     TracksAvailableCallback(MediaManager* aManager,
-                            already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess,
+                            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,19 +1051,18 @@ 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)
-    GetUserMediaWindowListener* listener =
-      mManager->GetWindowListener(mWindowID);
-    if (!listener || !window || !window->GetExtantDoc()) {
+    StreamListeners* listeners = mManager->GetWindowListeners(mWindowID);
+    if (!listeners || !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 =
@@ -1090,34 +1089,33 @@ public:
             mWindowID, domStream->GetInputStream()->AsProcessedStream());
       window->SetAudioCapture(true);
     } else {
       class LocalTrackSource : public MediaStreamTrackSource
       {
       public:
         LocalTrackSource(nsIPrincipal* aPrincipal,
                          const nsString& aLabel,
-                         SourceListener* aListener,
+                         GetUserMediaCallbackMediaStreamListener* 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.
@@ -1143,17 +1141,17 @@ public:
             mListener->StopTrack(mTrackID);
             mListener = nullptr;
           }
         }
 
       protected:
         ~LocalTrackSource() {}
 
-        RefPtr<SourceListener> mListener;
+        RefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
         const MediaSourceEnum mSource;
         const TrackID mTrackID;
         const RefPtr<const PeerIdentity> mPeerIdentity;
       };
 
       nsCOMPtr<nsIPrincipal> principal;
       if (mPeerIdentity) {
         principal = NullPrincipal::CreateWithInheritedAttributes(window->GetExtantDoc()->NodePrincipal());
@@ -1162,142 +1160,85 @@ 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, mSourceListener,
-                               source, kAudioTrack, mPeerIdentity);
+          new LocalTrackSource(principal, audioDeviceName, mListener, 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, mSourceListener,
-                               source, kVideoTrack, mPeerIdentity);
+          new LocalTrackSource(principal, videoDeviceName, mListener, 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 || !stream || sInShutdown) {
+    if (!domStream || 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;
     }
 
-    // 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);
+    // 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);
 
     // Note: includes JS callbacks; must be released on MainThread
-    auto callback = MakeRefPtr<Refcountable<UniquePtr<OnTracksAvailableCallback>>>(
-        new TracksAvailableCallback(mManager, mOnSuccess.forget(), mWindowID, domStream));
+    TracksAvailableCallback* tracksAvailableCallback =
+      new TracksAvailableCallback(mManager, mOnSuccess, mWindowID, domStream);
 
     // Dispatch to the media thread to ask it to start the sources,
     // because that can take a while.
-    // 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);
-
-      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;
-    }));
+    // 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;
 
     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;
@@ -1305,18 +1246,17 @@ public:
 
 private:
   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mOnSuccess;
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
   MediaStreamConstraints mConstraints;
   RefPtr<AudioDevice> mAudioDevice;
   RefPtr<VideoDevice> mVideoDevice;
   uint64_t mWindowID;
-  RefPtr<GetUserMediaWindowListener> mWindowListener;
-  RefPtr<SourceListener> mSourceListener;
+  RefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
   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>
@@ -1439,53 +1379,51 @@ MediaManager::SelectSettings(
  */
 class GetUserMediaTask : public Runnable
 {
 public:
   GetUserMediaTask(
     const MediaStreamConstraints& aConstraints,
     already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aOnSuccess,
     already_AddRefed<nsIDOMGetUserMediaErrorCallback> aOnFailure,
-    uint64_t aWindowID, GetUserMediaWindowListener *aWindowListener,
-    SourceListener *aSourceListener, MediaEnginePrefs &aPrefs,
+    uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener,
+    MediaEnginePrefs &aPrefs,
     const ipc::PrincipalInfo& aPrincipalInfo,
     bool aIsChrome,
     MediaManager::SourceSet* aSourceSet)
     : mConstraints(aConstraints)
     , mOnSuccess(aOnSuccess)
     , mOnFailure(aOnFailure)
     , mWindowID(aWindowID)
-    , mWindowListener(aWindowListener)
-    , mSourceListener(aSourceListener)
+    , mListener(aListener)
     , 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 errorRunnable = MakeRefPtr<ErrorCallbackRunnable<nsIDOMGetUserMediaSuccessCallback>>(
-        Move(mOnSuccess), Move(mOnFailure), *error, mWindowID);
+    auto runnable = MakeRefPtr<ErrorCallbackRunnable<nsIDOMGetUserMediaSuccessCallback>>(
+        mOnSuccess, mOnFailure, *error, mWindowID);
     // These should be empty now
     MOZ_ASSERT(!mOnSuccess);
     MOZ_ASSERT(!mOnFailure);
 
-    NS_DispatchToMainThread(errorRunnable.forget());
+    NS_DispatchToMainThread(runnable.forget());
     // Do after ErrorCallbackRunnable Run()s, as it checks active window list
-    NS_DispatchToMainThread(NewRunnableMethod<RefPtr<SourceListener>>(
-      mWindowListener, &GetUserMediaWindowListener::Remove, mSourceListener));
+    NS_DispatchToMainThread(do_AddRef(new GetUserMediaListenerRemove(mWindowID, mListener)));
   }
 
   NS_IMETHOD
   Run() override
   {
     MOZ_ASSERT(!NS_IsMainThread());
     MOZ_ASSERT(mOnSuccess);
     MOZ_ASSERT(mOnFailure);
@@ -1543,19 +1481,18 @@ public:
     }
     PeerIdentity* peerIdentity = nullptr;
     if (!mConstraints.mPeerIdentity.IsEmpty()) {
       peerIdentity = new PeerIdentity(mConstraints.mPeerIdentity);
     }
 
     NS_DispatchToMainThread(do_AddRef(
         new GetUserMediaStreamRunnable(mOnSuccess, mOnFailure, mWindowID,
-                                       mWindowListener, mSourceListener,
-                                       mPrincipalInfo, mConstraints,
-                                       mAudioDevice, mVideoDevice,
+                                       mListener, mPrincipalInfo,
+                                       mConstraints, mAudioDevice, mVideoDevice,
                                        peerIdentity)));
     MOZ_ASSERT(!mOnSuccess);
     MOZ_ASSERT(!mOnFailure);
     return NS_OK;
   }
 
   nsresult
   Denied(const nsAString& aName,
@@ -1573,17 +1510,18 @@ 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
-      mWindowListener->Remove(mSourceListener);
+      RefPtr<MediaManager> manager(MediaManager::GetInstance());
+      manager->RemoveFromWindowList(mWindowID, mListener);
     } 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);
@@ -1621,18 +1559,17 @@ public:
   }
 
 private:
   MediaStreamConstraints mConstraints;
 
   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mOnSuccess;
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
   uint64_t mWindowID;
-  RefPtr<GetUserMediaWindowListener> mWindowListener;
-  RefPtr<SourceListener> mSourceListener;
+  RefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
   RefPtr<AudioDevice> mAudioDevice;
   RefPtr<VideoDevice> mVideoDevice;
   MediaEnginePrefs mPrefs;
   ipc::PrincipalInfo mPrincipalInfo;
   bool mIsChrome;
 
   bool mDeviceChosen;
 public:
@@ -1953,43 +1890,57 @@ 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 nsString& aMsg,
+                                          const bool& aIsAudio,
+                                          const bool& aIsVideo)
 {
   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();
@@ -2276,30 +2227,25 @@ if (privileged) {
           cs.mMediaSource = ac.mMediaSource;
         }
       }
     }
   } else if (IsOn(c.mAudio)) {
    audioType = MediaSourceEnum::Microphone;
   }
 
-  // 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);
+  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);
 
   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;
@@ -2318,17 +2264,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);
-      windowListener->Remove(sourceListener);
+      RemoveFromWindowList(windowID, listener);
       return NS_OK;
     }
   }
 
   // Get list of all devices, with origin-specific device ids.
 
   MediaEnginePrefs prefs = mPrefs;
 
@@ -2341,34 +2287,33 @@ 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, windowListener,
-           sourceListener, askPermission, prefs, isHTTPS, callID, principalInfo,
+  p->Then([self, onSuccess, onFailure, windowID, c, listener, 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,
-              windowListener, sourceListener, askPermission, prefs, isHTTPS,
-              callID, principalInfo, isChrome, devices
-             ](const char*& badConstraint) mutable {
+              listener, 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;
       }
@@ -2396,25 +2341,21 @@ if (privileged) {
         for (auto& device : **devices) {
           nsresult rv = devicesCopy->AppendElement(device, /*weak =*/ false);
           if (NS_WARN_IF(NS_FAILED(rv))) {
             return;
           }
         }
       }
 
-      // Pass callbacks and listeners along to GetUserMediaTask.
-      RefPtr<GetUserMediaTask> task (new GetUserMediaTask(c,
-                                                          onSuccess.forget(),
+      // Pass callbacks and MediaStreamListener along to GetUserMediaTask.
+      RefPtr<GetUserMediaTask> task (new GetUserMediaTask(c, onSuccess.forget(),
                                                           onFailure.forget(),
-                                                          windowID,
-                                                          windowListener,
-                                                          sourceListener,
-                                                          prefs,
-                                                          principalInfo,
+                                                          windowID, listener,
+                                                          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)) {
@@ -2604,50 +2545,43 @@ 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();
 
-  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);
+  // 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);
 
   bool fake = Preferences::GetBool("media.navigator.streams.fake");
 
   RefPtr<PledgeSourceSet> p = EnumerateDevicesImpl(windowId,
                                                    MediaSourceEnum::Camera,
                                                    MediaSourceEnum::Microphone,
                                                    fake);
-  p->Then([onSuccess, windowListener, sourceListener](SourceSet*& aDevices) mutable {
+  p->Then([onSuccess, windowId, listener](SourceSet*& aDevices) mutable {
     UniquePtr<SourceSet> devices(aDevices); // grab result
-    DebugOnly<bool> rv = windowListener->Remove(sourceListener);
-    MOZ_ASSERT(rv);
+    RefPtr<MediaManager> mgr = MediaManager_GetInstance();
+    mgr->RemoveFromWindowList(windowId, listener);
     nsCOMPtr<nsIWritableVariant> array = MediaManager_ToJSArray(*devices);
     onSuccess->OnSuccess(array);
-  }, [onFailure, windowListener, sourceListener](MediaStreamError*& reason) mutable {
-    DebugOnly<bool> rv = windowListener->Remove(sourceListener);
-    MOZ_ASSERT(rv);
+  }, [onFailure, windowId, listener](MediaStreamError*& reason) mutable {
+    RefPtr<MediaManager> mgr = MediaManager_GetInstance();
+    mgr->RemoveFromWindowList(windowId, listener);
     onFailure->OnError(reason);
   });
   return NS_OK;
 }
 
 /*
  * GetUserMediaDevices - called by the UI-part of getUserMedia from chrome JS.
  */
@@ -2703,31 +2637,34 @@ MediaManager::GetBackend(uint64_t aWindo
 #endif
   }
   return mBackend;
 }
 
 static void
 StopSharingCallback(MediaManager *aThis,
                     uint64_t aWindowID,
-                    GetUserMediaWindowListener *aListener,
+                    StreamListeners *aListeners,
                     void *aData)
 {
   MOZ_ASSERT(NS_IsMainThread());
-
-  // Grab a strong ref since RemoveAll() might destroy the listener mid-way
-  // when clearing the mActiveWindows reference.
-  RefPtr<GetUserMediaWindowListener> listener(aListener);
-  if (!listener) {
-    return;
+  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);
   }
-
-  listener->Stop();
-  listener->RemoveAll();
-  MOZ_ASSERT(!aThis->GetWindowListener(aWindowID));
 }
 
 
 void
 MediaManager::OnNavigation(uint64_t aWindowID)
 {
   MOZ_ASSERT(NS_IsMainThread());
   LOG(("OnNavigation for %" PRIu64, aWindowID));
@@ -2746,17 +2683,16 @@ 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);
@@ -2770,30 +2706,30 @@ MediaManager::RemoveMediaDevicesCallback
       if (window && window->WindowID() == aWindowID) {
         DeviceChangeCallback::RemoveDeviceChangeCallbackLocked(observer);
         return;
       }
     }
   }
 }
 
-void
-MediaManager::AddWindowID(uint64_t aWindowId,
-                          GetUserMediaWindowListener* aListener)
+StreamListeners*
+MediaManager::AddWindowID(uint64_t aWindowId)
 {
   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)
-  if (IsWindowStillActive(aWindowId)) {
-    MOZ_ASSERT(false, "Window already added");
-    return;
+  StreamListeners* listeners = GetActiveWindows()->Get(aWindowId);
+  if (!listeners) {
+    listeners = new StreamListeners;
+    GetActiveWindows()->Put(aWindowId, listeners);
   }
-  GetActiveWindows()->Put(aWindowId, aListener);
+  return listeners;
 }
 
 void
 MediaManager::RemoveWindowID(uint64_t aWindowId)
 {
   mActiveWindows.Remove(aWindowId);
 
   // get outer windowID
@@ -2818,16 +2754,118 @@ 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;
     }
@@ -3118,32 +3156,43 @@ 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();
-    RefPtr<GetUserMediaWindowListener> winListener = iter.UserData();
-    if (!winListener) {
-      continue;
-    }
+    StreamListeners* listeners = iter.UserData();
 
     nsPIDOMWindowInner* window =
       nsGlobalWindow::GetInnerWindowWithId(id)->AsInner();
     MOZ_ASSERT(window);
     // XXXkhuey ...
     if (!window) {
       continue;
     }
-
-    if (winListener->CapturingVideo() || winListener->CapturingAudio() ||
-        winListener->CapturingScreen() || winListener->CapturingWindow() ||
-        winListener->CapturingApplication()) {
+    // 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) {
       array->AppendElement(window, /*weak =*/ false);
     }
   }
 
   array.forget(aArray);
   return NS_OK;
 }
 
@@ -3155,45 +3204,49 @@ struct CaptureWindowStateData {
   bool *mWindowShare;
   bool *mAppShare;
   bool *mBrowserShare;
 };
 
 static void
 CaptureWindowStateCallback(MediaManager *aThis,
                            uint64_t aWindowID,
-                           GetUserMediaWindowListener  *aListener,
+                           StreamListeners *aListeners,
                            void *aData)
 {
   struct CaptureWindowStateData *data = (struct CaptureWindowStateData *) aData;
 
-  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;
+  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;
+      }
+    }
   }
 }
 
+
 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;
@@ -3232,24 +3285,25 @@ MediaManager::SanitizeDeviceIds(int64_t 
 
   media::SanitizeOriginKeys(aSinceWhen, false); // we fire and forget
   return NS_OK;
 }
 
 static void
 StopScreensharingCallback(MediaManager *aThis,
                           uint64_t aWindowID,
-                          GetUserMediaWindowListener *aListener,
+                          StreamListeners *aListeners,
                           void *aData)
 {
-  if (!aListener) {
-    return;
+  if (aListeners) {
+    auto length = aListeners->Length();
+    for (size_t i = 0; i < length; ++i) {
+      aListeners->ElementAt(i)->StopSharing();
+    }
   }
-
-  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.
 
@@ -3264,22 +3318,20 @@ 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();
-      GetUserMediaWindowListener* listener = GetWindowListener(windowID);
-      (*aCallback)(this, windowID, listener, aData);
-      // NB: `listener` might have been destroyed.
-    }
+    uint64_t windowID = aWindow->WindowID();
+    StreamListeners* listeners = GetActiveWindows()->Get(windowID);
+    // pass listeners so it can modify/delete the list
+    (*aCallback)(this, windowID, listeners, aData);
 
     // 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;
@@ -3357,416 +3409,72 @@ 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
-SourceListener::Register(GetUserMediaWindowListener* aListener)
-{
-  LOG(("SourceListener %p registering with window listener %p", this, 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)
+GetUserMediaCallbackMediaStreamListener::Stop()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
-
-  LOG(("SourceListener %p activating audio=%p video=%p", this, aAudioDevice, aVideoDevice));
-
-  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;
   }
 
-  LOG(("SourceListener %p stopping", this));
-
-  // 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();
-  }));
+  // 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;
 }
 
+// Doesn't kill audio
 void
-SourceListener::Remove()
+GetUserMediaCallbackMediaStreamListener::StopSharing()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  if (!mStream || mRemoved) {
-    return;
-  }
-
-  LOG(("SourceListener %p removed on purpose, mFinished = %d", this, (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: {
-      LOG(("SourceListener %p stopping audio track %d", this, aTrackID));
-      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: {
-      LOG(("SourceListener %p stopping video track %d", this, aTrackID));
-      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)) {
-    LOG(("SourceListener %p this was the last track stopped", this));
-    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;
-  }
-
-  LOG(("SourceListener %p StopSharing", this));
-
   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);
-  }
-  if (mAudioDevice &&
-      mAudioDevice->GetMediaSource() == MediaSourceEnum::AudioCapture) {
-    uint64_t windowID = mWindowListener->WindowID();
-    nsCOMPtr<nsPIDOMWindowInner> window = nsGlobalWindow::GetInnerWindowWithId(windowID)->AsInner();
-    MOZ_RELEASE_ASSERT(window);
+  } else if (mAudioDevice &&
+             mAudioDevice->GetMediaSource() == MediaSourceEnum::AudioCapture) {
+    nsCOMPtr<nsPIDOMWindowInner> window = nsGlobalWindow::GetInnerWindowWithId(mWindowID)->AsInner();
+    MOZ_ASSERT(window);
     window->SetAudioCapture(false);
     MediaStreamGraph* graph =
       MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER,
                                     dom::AudioChannel::Normal);
-    graph->UnregisterCaptureStreamForWindow(windowID);
+    graph->UnregisterCaptureStreamForWindow(mWindowID);
     mStream->Destroy();
   }
 }
 
-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;
-  }
-
-  LOG(("SourceListener %p NotifyFinished", this));
-
-  Stop(); // we know it's been activated
-  mWindowListener->Remove(this);
-}
-
-void
-SourceListener::NotifyRemoved()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  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)
+// ApplyConstraints for track
+
+auto
+GetUserMediaCallbackMediaStreamListener::ApplyConstraintsToTrack(
+    nsPIDOMWindowInner* aWindow,
+    TrackID aTrackID,
+    const MediaTrackConstraints& aConstraints,
+    dom::CallerType aCallerType) -> already_AddRefed<PledgeVoid>
 {
   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;
@@ -3844,76 +3552,167 @@ SourceListener::ApplyConstraintsToTrack(
         }
       }
       return NS_OK;
     }));
   }));
   return p.forget();
 }
 
-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();
-  }
-}
+// Stop backend for track
 
 void
-GetUserMediaWindowListener::NotifySourceTrackStopped()
+GetUserMediaCallbackMediaStreamListener::StopTrack(TrackID aTrackID)
 {
   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, &GetUserMediaWindowListener::NotifyChromeOfTrackStops);
+    NewRunnableMethod(this, &GetUserMediaCallbackMediaStreamListener::NotifyChromeOfTrackStops);
   nsContentUtils::RunInStableState(runnable.forget());
   mChromeNotificationTaskPosted = true;
 }
 
 void
-GetUserMediaWindowListener::NotifyChromeOfTrackStops()
+GetUserMediaCallbackMediaStreamListener::NotifyChromeOfTrackStops()
 {
   MOZ_ASSERT(mChromeNotificationTaskPosted);
   mChromeNotificationTaskPosted = false;
 
-  NS_DispatchToMainThread(do_AddRef(new GetUserMediaNotificationEvent(
-    GetUserMediaNotificationEvent::STOPPING, mWindowID)));
+  // 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();
+  }
 }
 
 GetUserMediaNotificationEvent::GetUserMediaNotificationEvent(
+    GetUserMediaCallbackMediaStreamListener* aListener,
     GetUserMediaStatus aStatus,
-    uint64_t aWindowID)
-: mStatus(aStatus), mWindowID(aWindowID) {}
+    bool aIsAudio, bool aIsVideo, uint64_t aWindowID)
+: mListener(aListener) , mStatus(aStatus) , mIsAudio(aIsAudio)
+, mIsVideo(aIsVideo), mWindowID(aWindowID) {}
 
 GetUserMediaNotificationEvent::GetUserMediaNotificationEvent(
     GetUserMediaStatus aStatus,
     already_AddRefed<DOMMediaStream> aStream,
-    already_AddRefed<Refcountable<UniquePtr<OnTracksAvailableCallback>>> aOnTracksAvailableCallback,
-    uint64_t aWindowID,
+    OnTracksAvailableCallback* aOnTracksAvailableCallback,
+    bool aIsAudio, bool aIsVideo, uint64_t aWindowID,
     already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError)
-: mStream(aStream),
-  mOnTracksAvailableCallback(aOnTracksAvailableCallback),
-  mStatus(aStatus),
-  mWindowID(aWindowID),
+: mStream(aStream), mOnTracksAvailableCallback(aOnTracksAvailableCallback),
+  mStatus(aStatus), mIsAudio(aIsAudio), mIsVideo(aIsVideo), mWindowID(aWindowID),
   mOnFailure(aError) {}
 GetUserMediaNotificationEvent::~GetUserMediaNotificationEvent()
 {
 }
 
 NS_IMETHODIMP
 GetUserMediaNotificationEvent::Run()
 {
@@ -3923,22 +3722,23 @@ 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->release());
+    stream->OnTracksAvailable(mOnTracksAvailableCallback.forget());
     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);
+  return MediaManager::NotifyRecordingStatusChange(window->AsInner(), msg, mIsAudio, mIsVideo);
 }
 
 } // namespace mozilla
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -50,20 +50,22 @@ struct MediaTrackConstraints;
 struct MediaTrackConstraintSet;
 enum class CallerType : uint32_t;
 } // namespace dom
 
 namespace ipc {
 class PrincipalInfo;
 }
 
+class MediaManager;
+class GetUserMediaCallbackMediaStreamListener;
 class GetUserMediaTask;
-class GetUserMediaWindowListener;
-class MediaManager;
-class SourceListener;
+
+extern LogModule* GetMediaManagerLog();
+#define MM_LOG(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, msg)
 
 class MediaDevice : public nsIMediaDevice
 {
 public:
   typedef MediaEngineSource Source;
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIMEDIADEVICE
@@ -129,71 +131,76 @@ public:
 };
 
 class GetUserMediaNotificationEvent: public Runnable
 {
   public:
     enum GetUserMediaStatus {
       STARTING,
       STOPPING,
+      STOPPED_TRACK,
     };
-    GetUserMediaNotificationEvent(GetUserMediaStatus aStatus,
-                                  uint64_t aWindowID);
+    GetUserMediaNotificationEvent(GetUserMediaCallbackMediaStreamListener* aListener,
+                                  GetUserMediaStatus aStatus,
+                                  bool aIsAudio, bool aIsVideo, uint64_t aWindowID);
 
     GetUserMediaNotificationEvent(GetUserMediaStatus aStatus,
                                   already_AddRefed<DOMMediaStream> aStream,
-                                  already_AddRefed<media::Refcountable<UniquePtr<OnTracksAvailableCallback>>> aOnTracksAvailableCallback,
-                                  uint64_t aWindowID,
+                                  OnTracksAvailableCallback* aOnTracksAvailableCallback,
+                                  bool aIsAudio, bool aIsVideo, uint64_t aWindowID,
                                   already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError);
     virtual ~GetUserMediaNotificationEvent();
 
     NS_IMETHOD Run() override;
 
   protected:
-    RefPtr<GetUserMediaWindowListener> mListener; // threadsafe
+    RefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
     RefPtr<DOMMediaStream> mStream;
-    RefPtr<media::Refcountable<UniquePtr<OnTracksAvailableCallback>>> mOnTracksAvailableCallback;
+    nsAutoPtr<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,
-    already_AddRefed<media::Refcountable<UniquePtr<OnTracksAvailableCallback>>> aOnTracksAvailableCallback):
+  ReleaseMediaOperationResource(already_AddRefed<DOMMediaStream> aStream,
+    OnTracksAvailableCallback* aOnTracksAvailableCallback):
     mStream(aStream),
     mOnTracksAvailableCallback(aOnTracksAvailableCallback) {}
   NS_IMETHOD Run() override {return NS_OK;}
 private:
   RefPtr<DOMMediaStream> mStream;
-  RefPtr<media::Refcountable<UniquePtr<OnTracksAvailableCallback>>> mOnTracksAvailableCallback;
+  nsAutoPtr<OnTracksAvailableCallback> mOnTracksAvailableCallback;
 };
 
-typedef nsRefPtrHashtable<nsUint64HashKey, GetUserMediaWindowListener> WindowTable;
+typedef nsTArray<RefPtr<GetUserMediaCallbackMediaStreamListener> > StreamListeners;
+typedef nsClassHashtable<nsUint64HashKey, StreamListeners> WindowTable;
 
 // we could add MediaManager if needed
 typedef void (*WindowListenerCallback)(MediaManager *aThis,
                                        uint64_t aWindowID,
-                                       GetUserMediaWindowListener *aListener,
+                                       StreamListeners *aListeners,
                                        void *aData);
 
 class MediaManager final : public nsIMediaManagerService,
                            public nsIObserver
                           ,public DeviceChangeCallback
 {
-  friend SourceListener;
+  friend GetUserMediaCallbackMediaStreamListener;
 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();
@@ -204,41 +211,37 @@ public:
 #endif
 
   static bool Exists()
   {
     return !!sSingleton;
   }
 
   static nsresult NotifyRecordingStatusChange(nsPIDOMWindowInner* aWindow,
-                                              const nsString& aMsg);
+                                              const nsString& aMsg,
+                                              const bool& aIsAudio,
+                                              const bool& aIsVideo);
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIOBSERVER
   NS_DECL_NSIMEDIAMANAGERSERVICE
 
   media::Parent<media::NonE10s>* GetNonE10sParent();
   MediaEngine* GetBackend(uint64_t aWindowId = 0);
-
-  WindowTable *GetActiveWindows() {
+  StreamListeners *GetWindowListeners(uint64_t aWindowId) {
     MOZ_ASSERT(NS_IsMainThread());
-    return &mActiveWindows;
+    return mActiveWindows.Get(aWindowId);
   }
-  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 !!GetWindowListener(aWindowId);
+    return !!GetWindowListeners(aWindowId);
   }
   // Note: also calls aListener->Remove(), even if inactive
   void RemoveFromWindowList(uint64_t aWindowID,
-    GetUserMediaWindowListener *aListener);
+    GetUserMediaCallbackMediaStreamListener *aListener);
 
   nsresult GetUserMedia(
     nsPIDOMWindowInner* aWindow,
     const dom::MediaStreamConstraints& aConstraints,
     nsIDOMGetUserMediaSuccessCallback* onSuccess,
     nsIDOMGetUserMediaErrorCallback* onError,
     dom::CallerType aCallerType);
 
@@ -285,16 +288,22 @@ 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();