Bug 1314833 - Part 2.2: Use AbstractThread::CreateDirectTaskDrainer() to Drain Direct Tasks Dispatched to MediaStreamGraph. f=rjesup,r=padenot,jwwang
authorBevis Tseng <btseng@mozilla.com>
Wed, 07 Dec 2016 22:00:12 -1000
changeset 375618 02e5708a75fcdd9e8e4b065995f00b29896fb4b5
parent 375617 0a5d3ec02dcd355cf83eb4b717e8ef2b687f7d0b
child 375619 d3cfaafedb4339897fcede34a67125a4d16a7b98
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspadenot, jwwang
bugs1314833
milestone53.0a1
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
Bug 1314833 - Part 2.2: Use AbstractThread::CreateDirectTaskDrainer() to Drain Direct Tasks Dispatched to MediaStreamGraph. f=rjesup,r=padenot,jwwang MozReview-Commit-ID: 1KgE3uKu4CG
dom/html/HTMLMediaElement.cpp
dom/media/AudioCaptureStream.cpp
dom/media/AudioCaptureStream.h
dom/media/DOMMediaStream.cpp
dom/media/DOMMediaStream.h
dom/media/MediaManager.cpp
dom/media/MediaRecorder.cpp
dom/media/MediaRecorder.h
dom/media/MediaStreamGraph.cpp
dom/media/MediaStreamGraph.h
dom/media/MediaStreamTrack.cpp
dom/media/TrackUnionStream.cpp
dom/media/TrackUnionStream.h
dom/media/mediasink/DecodedStream.cpp
dom/media/webaudio/AudioDestinationNode.cpp
dom/media/webaudio/AudioNode.cpp
dom/media/webaudio/AudioNode.h
dom/media/webaudio/AudioNodeEngine.cpp
dom/media/webaudio/AudioNodeEngine.h
dom/media/webaudio/AudioNodeExternalInputStream.cpp
dom/media/webaudio/AudioNodeExternalInputStream.h
dom/media/webaudio/AudioNodeStream.cpp
dom/media/webaudio/AudioNodeStream.h
dom/media/webaudio/BiquadFilterNode.cpp
dom/media/webaudio/ConvolverNode.cpp
dom/media/webaudio/DelayNode.cpp
dom/media/webaudio/IIRFilterNode.cpp
dom/media/webaudio/MediaStreamAudioSourceNode.cpp
dom/media/webaudio/PannerNode.cpp
dom/media/webspeech/synth/nsSpeechTask.cpp
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -4668,24 +4668,25 @@ nsresult HTMLMediaElement::FinishDecoder
 
   return rv;
 }
 
 class HTMLMediaElement::StreamListener : public MediaStreamListener,
                                          public WatchTarget
 {
 public:
-  explicit StreamListener(HTMLMediaElement* aElement, const char* aName) :
+  StreamListener(HTMLMediaElement* aElement, const char* aName) :
     WatchTarget(aName),
     mElement(aElement),
     mHaveCurrentData(false),
     mBlocked(false),
     mFinished(false),
     mMutex(aName),
-    mPendingNotifyOutput(false)
+    mPendingNotifyOutput(false),
+    mAbstractMainThread(aElement->AbstractMainThread())
   {}
   void Forget()
   {
     mElement = nullptr;
     NotifyWatchers();
   }
 
   // Main thread
@@ -4737,47 +4738,49 @@ public:
   virtual void NotifyBlockingChanged(MediaStreamGraph* aGraph, Blocking aBlocked) override
   {
     nsCOMPtr<nsIRunnable> event;
     if (aBlocked == BLOCKED) {
       event = NewRunnableMethod(this, &StreamListener::DoNotifyBlocked);
     } else {
       event = NewRunnableMethod(this, &StreamListener::DoNotifyUnblocked);
     }
-    aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
+    aGraph->DispatchToMainThreadAfterStreamStateUpdate(mAbstractMainThread,
+                                                       event.forget());
   }
   virtual void NotifyHasCurrentData(MediaStreamGraph* aGraph) override
   {
     MutexAutoLock lock(mMutex);
-    nsCOMPtr<nsIRunnable> event =
-      NewRunnableMethod(this, &StreamListener::DoNotifyHaveCurrentData);
-    aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
+    aGraph->DispatchToMainThreadAfterStreamStateUpdate(
+      mAbstractMainThread,
+      NewRunnableMethod(this, &StreamListener::DoNotifyHaveCurrentData));
   }
   virtual void NotifyOutput(MediaStreamGraph* aGraph,
                             GraphTime aCurrentTime) override
   {
     MutexAutoLock lock(mMutex);
     if (mPendingNotifyOutput)
       return;
     mPendingNotifyOutput = true;
-    nsCOMPtr<nsIRunnable> event =
-      NewRunnableMethod(this, &StreamListener::DoNotifyOutput);
-    aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
+    aGraph->DispatchToMainThreadAfterStreamStateUpdate(
+      mAbstractMainThread,
+      NewRunnableMethod(this, &StreamListener::DoNotifyOutput));
   }
 
 private:
   // These fields may only be accessed on the main thread
   HTMLMediaElement* mElement;
   bool mHaveCurrentData;
   bool mBlocked;
   bool mFinished;
 
   // mMutex protects the fields below; they can be accessed on any thread
   Mutex mMutex;
   bool mPendingNotifyOutput;
+  const RefPtr<AbstractThread> mAbstractMainThread;
 };
 
 class HTMLMediaElement::MediaStreamTracksAvailableCallback:
   public OnTracksAvailableCallback
 {
 public:
   explicit MediaStreamTracksAvailableCallback(HTMLMediaElement* aElement):
       OnTracksAvailableCallback(), mElement(aElement)
--- a/dom/media/AudioCaptureStream.cpp
+++ b/dom/media/AudioCaptureStream.cpp
@@ -25,18 +25,18 @@ using namespace mozilla::dom;
 using namespace mozilla::gfx;
 
 namespace mozilla
 {
 
 // We are mixing to mono until PeerConnection can accept stereo
 static const uint32_t MONO = 1;
 
-AudioCaptureStream::AudioCaptureStream(TrackID aTrackId)
-  : ProcessedMediaStream(), mTrackId(aTrackId), mStarted(false), mTrackCreated(false)
+AudioCaptureStream::AudioCaptureStream(TrackID aTrackId, AbstractThread* aMainThread)
+  : ProcessedMediaStream(aMainThread), mTrackId(aTrackId), mStarted(false), mTrackCreated(false)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_COUNT_CTOR(AudioCaptureStream);
   mMixer.AddCallback(this);
 }
 
 AudioCaptureStream::~AudioCaptureStream()
 {
--- a/dom/media/AudioCaptureStream.h
+++ b/dom/media/AudioCaptureStream.h
@@ -9,26 +9,27 @@
 #include "MediaStreamGraph.h"
 #include "AudioMixer.h"
 #include "StreamTracks.h"
 #include <algorithm>
 
 namespace mozilla
 {
 
+class AbstractThread;
 class DOMMediaStream;
 
 /**
  * See MediaStreamGraph::CreateAudioCaptureStream.
  */
 class AudioCaptureStream : public ProcessedMediaStream,
                            public MixerCallbackReceiver
 {
 public:
-  explicit AudioCaptureStream(TrackID aTrackId);
+  AudioCaptureStream(TrackID aTrackId, AbstractThread* aMainThread);
   virtual ~AudioCaptureStream();
 
   void Start();
 
   void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override;
 
 protected:
   void MixerCallback(AudioDataValue* aMixedBuffer, AudioSampleFormat aFormat,
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -4,16 +4,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "DOMMediaStream.h"
 #include "nsContentUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIScriptError.h"
 #include "nsIUUIDGenerator.h"
 #include "nsPIDOMWindow.h"
+#include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/MediaStreamBinding.h"
 #include "mozilla/dom/MediaStreamTrackEvent.h"
 #include "mozilla/dom/LocalMediaStreamBinding.h"
 #include "mozilla/dom/AudioNode.h"
 #include "AudioChannelAgent.h"
 #include "mozilla/dom/AudioTrack.h"
 #include "mozilla/dom/AudioTrackList.h"
 #include "mozilla/dom/VideoTrack.h"
@@ -133,16 +134,17 @@ NS_IMPL_CYCLE_COLLECTION_0(MediaStreamTr
  * Listener registered on the Owned stream to detect added and ended owned
  * tracks for keeping the list of MediaStreamTracks in sync with the tracks
  * added and ended directly at the source.
  */
 class DOMMediaStream::OwnedStreamListener : public MediaStreamListener {
 public:
   explicit OwnedStreamListener(DOMMediaStream* aStream)
     : mStream(aStream)
+    , mAbstractMainThread(aStream->mAbstractMainThread)
   {}
 
   void Forget() { mStream = nullptr; }
 
   void DoNotifyTrackCreated(TrackID aTrackID, MediaSegment::Type aType,
                             MediaStream* aInputStream, TrackID aInputTrackID)
   {
     MOZ_ASSERT(NS_IsMainThread());
@@ -207,44 +209,47 @@ public:
 
   void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
                                 StreamTime aTrackOffset, TrackEventCommand aTrackEvents,
                                 const MediaSegment& aQueuedMedia,
                                 MediaStream* aInputStream,
                                 TrackID aInputTrackID) override
   {
     if (aTrackEvents & TrackEventCommand::TRACK_EVENT_CREATED) {
-      nsCOMPtr<nsIRunnable> runnable =
+      aGraph->DispatchToMainThreadAfterStreamStateUpdate(
+        mAbstractMainThread,
         NewRunnableMethod<TrackID, MediaSegment::Type, RefPtr<MediaStream>, TrackID>(
           this, &OwnedStreamListener::DoNotifyTrackCreated,
-          aID, aQueuedMedia.GetType(), aInputStream, aInputTrackID);
-      aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
+          aID, aQueuedMedia.GetType(), aInputStream, aInputTrackID));
     } else if (aTrackEvents & TrackEventCommand::TRACK_EVENT_ENDED) {
-      nsCOMPtr<nsIRunnable> runnable =
+      aGraph->DispatchToMainThreadAfterStreamStateUpdate(
+        mAbstractMainThread,
         NewRunnableMethod<RefPtr<MediaStream>, TrackID, TrackID>(
           this, &OwnedStreamListener::DoNotifyTrackEnded,
-          aInputStream, aInputTrackID, aID);
-      aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
+          aInputStream, aInputTrackID, aID));
     }
   }
 
 private:
   // These fields may only be accessed on the main thread
   DOMMediaStream* mStream;
+
+  const RefPtr<AbstractThread> mAbstractMainThread;
 };
 
 /**
  * Listener registered on the Playback stream to detect when tracks end and when
  * all new tracks this iteration have been created - for when several tracks are
  * queued by the source and committed all at once.
  */
 class DOMMediaStream::PlaybackStreamListener : public MediaStreamListener {
 public:
   explicit PlaybackStreamListener(DOMMediaStream* aStream)
     : mStream(aStream)
+    , mAbstractMainThread(aStream->mAbstractMainThread)
   {}
 
   void Forget()
   {
     MOZ_ASSERT(NS_IsMainThread());
     mStream = nullptr;
   }
 
@@ -274,34 +279,37 @@ public:
     NS_DispatchToMainThread(NewRunnableMethod(
       mStream, &DOMMediaStream::NotifyFinished));
   }
 
   // The methods below are called on the MediaStreamGraph thread.
 
   void NotifyFinishedTrackCreation(MediaStreamGraph* aGraph) override
   {
-    nsCOMPtr<nsIRunnable> runnable =
-      NewRunnableMethod(this, &PlaybackStreamListener::DoNotifyFinishedTrackCreation);
-    aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
+    aGraph->DispatchToMainThreadAfterStreamStateUpdate(
+      mAbstractMainThread,
+      NewRunnableMethod(this, &PlaybackStreamListener::DoNotifyFinishedTrackCreation));
   }
 
 
   void NotifyEvent(MediaStreamGraph* aGraph,
                    MediaStreamGraphEvent event) override
   {
     if (event == MediaStreamGraphEvent::EVENT_FINISHED) {
       aGraph->DispatchToMainThreadAfterStreamStateUpdate(
+        mAbstractMainThread,
         NewRunnableMethod(this, &PlaybackStreamListener::DoNotifyFinished));
     }
   }
 
 private:
   // These fields may only be accessed on the main thread
   DOMMediaStream* mStream;
+
+  const RefPtr<AbstractThread> mAbstractMainThread;
 };
 
 class DOMMediaStream::PlaybackTrackListener : public MediaStreamTrackConsumer
 {
 public:
   explicit PlaybackTrackListener(DOMMediaStream* aStream) :
     mStream(aStream) {}
 
@@ -393,17 +401,18 @@ NS_INTERFACE_MAP_END_INHERITING(DOMMedia
 
 DOMMediaStream::DOMMediaStream(nsPIDOMWindowInner* aWindow,
                                MediaStreamTrackSourceGetter* aTrackSourceGetter)
   : mLogicalStreamStartTime(0), mWindow(aWindow),
     mInputStream(nullptr), mOwnedStream(nullptr), mPlaybackStream(nullptr),
     mTracksPendingRemoval(0), mTrackSourceGetter(aTrackSourceGetter),
     mPlaybackTrackListener(MakeAndAddRef<PlaybackTrackListener>(this)),
     mTracksCreated(false), mNotifiedOfMediaStreamGraphShutdown(false),
-    mActive(false), mSetInactiveOnFinish(false)
+    mActive(false), mSetInactiveOnFinish(false),
+    mAbstractMainThread(aWindow->GetDocGroup()->AbstractMainThreadFor(TaskCategory::Other))
 {
   nsresult rv;
   nsCOMPtr<nsIUUIDGenerator> uuidgen =
     do_GetService("@mozilla.org/uuid-generator;1", &rv);
 
   if (NS_SUCCEEDED(rv) && uuidgen) {
     nsID uuid;
     memset(&uuid, 0, sizeof(uuid));
@@ -837,39 +846,39 @@ void
 DOMMediaStream::SetInactiveOnFinish()
 {
   mSetInactiveOnFinish = true;
 }
 
 void
 DOMMediaStream::InitSourceStream(MediaStreamGraph* aGraph)
 {
-  InitInputStreamCommon(aGraph->CreateSourceStream(), aGraph);
+  InitInputStreamCommon(aGraph->CreateSourceStream(mAbstractMainThread), aGraph);
   InitOwnedStreamCommon(aGraph);
   InitPlaybackStreamCommon(aGraph);
 }
 
 void
 DOMMediaStream::InitTrackUnionStream(MediaStreamGraph* aGraph)
 {
-  InitInputStreamCommon(aGraph->CreateTrackUnionStream(), aGraph);
+  InitInputStreamCommon(aGraph->CreateTrackUnionStream(mAbstractMainThread), aGraph);
   InitOwnedStreamCommon(aGraph);
   InitPlaybackStreamCommon(aGraph);
 }
 
 void
 DOMMediaStream::InitAudioCaptureStream(nsIPrincipal* aPrincipal, MediaStreamGraph* aGraph)
 {
   const TrackID AUDIO_TRACK = 1;
 
   RefPtr<BasicTrackSource> audioCaptureSource =
     new BasicTrackSource(aPrincipal, MediaSourceEnum::AudioCapture);
 
   AudioCaptureStream* audioCaptureStream =
-    static_cast<AudioCaptureStream*>(aGraph->CreateAudioCaptureStream(AUDIO_TRACK));
+    static_cast<AudioCaptureStream*>(aGraph->CreateAudioCaptureStream(AUDIO_TRACK, mAbstractMainThread));
   InitInputStreamCommon(audioCaptureStream, aGraph);
   InitOwnedStreamCommon(aGraph);
   InitPlaybackStreamCommon(aGraph);
   RefPtr<MediaStreamTrack> track =
     CreateDOMTrack(AUDIO_TRACK, MediaSegment::AUDIO, audioCaptureSource);
   AddTrackInternal(track);
 
   audioCaptureStream->Start();
@@ -885,32 +894,32 @@ DOMMediaStream::InitInputStreamCommon(Me
   mInputStream->RegisterUser();
 }
 
 void
 DOMMediaStream::InitOwnedStreamCommon(MediaStreamGraph* aGraph)
 {
   MOZ_ASSERT(!mPlaybackStream, "Owned stream must be initialized before playback stream");
 
-  mOwnedStream = aGraph->CreateTrackUnionStream();
+  mOwnedStream = aGraph->CreateTrackUnionStream(mAbstractMainThread);
   mOwnedStream->SetAutofinish(true);
   mOwnedStream->RegisterUser();
   if (mInputStream) {
     mOwnedPort = mOwnedStream->AllocateInputPort(mInputStream);
   }
 
   // Setup track listeners
   mOwnedListener = new OwnedStreamListener(this);
   mOwnedStream->AddListener(mOwnedListener);
 }
 
 void
 DOMMediaStream::InitPlaybackStreamCommon(MediaStreamGraph* aGraph)
 {
-  mPlaybackStream = aGraph->CreateTrackUnionStream();
+  mPlaybackStream = aGraph->CreateTrackUnionStream(mAbstractMainThread);
   mPlaybackStream->SetAutofinish(true);
   mPlaybackStream->RegisterUser();
   if (mOwnedStream) {
     mPlaybackPort = mPlaybackStream->AllocateInputPort(mOwnedStream);
   }
 
   mPlaybackListener = new PlaybackStreamListener(this);
   mPlaybackStream->AddListener(mPlaybackListener);
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -21,16 +21,17 @@
 // X11 has a #define for CurrentTime. Unbelievable :-(.
 // See dom/media/webaudio/AudioContext.h for more fun!
 #ifdef CurrentTime
 #undef CurrentTime
 #endif
 
 namespace mozilla {
 
+class AbstractThread;
 class DOMHwMediaStream;
 class DOMLocalMediaStream;
 class DOMMediaStream;
 class MediaStream;
 class MediaInputPort;
 class DirectMediaStreamListener;
 class MediaStreamGraph;
 class ProcessedMediaStream;
@@ -584,16 +585,18 @@ public:
   // being destroyed, so we don't hold on to a dead pointer. Main thread only.
   void RegisterTrackListener(TrackListener* aListener);
 
   // Unregisters a track listener from this MediaStream. The caller must call
   // UnregisterTrackListener before being destroyed, so we don't hold on to
   // a dead pointer. Main thread only.
   void UnregisterTrackListener(TrackListener* aListener);
 
+  AbstractThread* AbstractMainThread() const { return mAbstractMainThread; }
+
 protected:
   virtual ~DOMMediaStream();
 
   void Destroy();
   void InitSourceStream(MediaStreamGraph* aGraph);
   void InitTrackUnionStream(MediaStreamGraph* aGraph);
   void InitAudioCaptureStream(nsIPrincipal* aPrincipal, MediaStreamGraph* aGraph);
 
@@ -748,16 +751,17 @@ private:
   // Principal identifying who may access the collected contents of this stream.
   // If null, this stream can be used by anyone because it has no content yet.
   nsCOMPtr<nsIPrincipal> mPrincipal;
   // Video principal is used by video element as access is requested to its
   // image data.
   nsCOMPtr<nsIPrincipal> mVideoPrincipal;
   nsTArray<dom::PrincipalChangeObserver<DOMMediaStream>*> mPrincipalChangeObservers;
   CORSMode mCORSMode;
+  const RefPtr<AbstractThread> mAbstractMainThread;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(DOMMediaStream,
                               NS_DOMMEDIASTREAM_IID)
 
 #define NS_DOMLOCALMEDIASTREAM_IID \
 { 0xb1437260, 0xec61, 0x4dfa, \
   { 0x92, 0x54, 0x04, 0x44, 0xe2, 0xb5, 0x94, 0x9c } }
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -1045,17 +1045,17 @@ public:
     // DOMMediaStream::NotifyMediaStreamGraphShutdown is called.
     RefPtr<DOMMediaStream> mStream;
   };
 
   NS_IMETHOD
   Run() override
   {
     MOZ_ASSERT(NS_IsMainThread());
-    auto* globalWindow = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
+    nsGlobalWindow* globalWindow = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
     nsPIDOMWindowInner* window = globalWindow ? globalWindow->AsInner() : nullptr;
 
     // We're on main-thread, and the windowlist can only
     // be invalidated from the main-thread (see OnNavigation)
     StreamListeners* listeners = mManager->GetWindowListeners(mWindowID);
     if (!listeners || !window || !window->GetExtantDoc()) {
       // This window is no longer live.  mListener has already been removed
       return NS_OK;
@@ -1077,17 +1077,18 @@ public:
     if (mAudioDevice &&
         mAudioDevice->GetMediaSource() == MediaSourceEnum::AudioCapture) {
       // It should be possible to pipe the capture stream to anything. CORS is
       // not a problem here, we got explicit user content.
       nsCOMPtr<nsIPrincipal> principal = window->GetExtantDoc()->NodePrincipal();
       domStream =
         DOMMediaStream::CreateAudioCaptureStreamAsInput(window, principal, msg);
 
-      stream = msg->CreateSourceStream(); // Placeholder
+      stream = msg->CreateSourceStream(
+        globalWindow->AbstractMainThreadFor(dom::TaskCategory::Other)); // Placeholder
       msg->RegisterCaptureStreamForWindow(
             mWindowID, domStream->GetInputStream()->AsProcessedStream());
       window->SetAudioCapture(true);
     } else {
       class LocalTrackSource : public MediaStreamTrackSource
       {
       public:
         LocalTrackSource(nsIPrincipal* aPrincipal,
--- a/dom/media/MediaRecorder.cpp
+++ b/dom/media/MediaRecorder.cpp
@@ -416,16 +416,17 @@ class MediaRecorder::Session: public nsI
 public:
   Session(MediaRecorder* aRecorder, int32_t aTimeSlice)
     : mRecorder(aRecorder)
     , mTimeSlice(aTimeSlice)
     , mStopIssued(false)
     , mIsStartEventFired(false)
     , mNeedSessionEndTask(true)
     , mSelectedVideoTrackID(TRACK_NONE)
+    , mAbstractMainThread(aRecorder->mAbstractMainThread)
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     uint32_t maxMem = Preferences::GetUint("media.recorder.max_memory",
                                            MAX_ALLOW_MEMORY_BUFFER);
     mEncodedBufferCache = new EncodedBufferCache(maxMem);
     mLastBlobTimeStamp = TimeStamp::Now();
   }
@@ -471,17 +472,17 @@ public:
   void Start()
   {
     LOG(LogLevel::Debug, ("Session.Start %p", this));
     MOZ_ASSERT(NS_IsMainThread());
 
     // Create a Track Union Stream
     MediaStreamGraph* gm = mRecorder->GetSourceMediaStream()->Graph();
     TrackRate trackRate = gm->GraphRate();
-    mTrackUnionStream = gm->CreateTrackUnionStream();
+    mTrackUnionStream = gm->CreateTrackUnionStream(mAbstractMainThread);
     MOZ_ASSERT(mTrackUnionStream, "CreateTrackUnionStream failed");
 
     mTrackUnionStream->SetAutofinish(true);
 
     DOMMediaStream* domStream = mRecorder->Stream();
     if (domStream) {
       // Get the available tracks from the DOMMediaStream.
       // The callback will report back tracks that we have to connect to
@@ -931,16 +932,17 @@ private:
   bool mStopIssued;
   // Indicate the session had fire start event. Encoding thread only.
   bool mIsStartEventFired;
   // False if the InitEncoder called successfully, ensure the
   // ExtractRunnable/DestroyRunnable will end the session.
   // Main thread only.
   bool mNeedSessionEndTask;
   TrackID mSelectedVideoTrackID;
+  const RefPtr<AbstractThread> mAbstractMainThread;
 };
 
 NS_IMPL_ISUPPORTS(MediaRecorder::Session, nsIObserver)
 
 MediaRecorder::~MediaRecorder()
 {
   if (mPipeStream != nullptr) {
     mInputPort->Destroy();
@@ -949,29 +951,31 @@ MediaRecorder::~MediaRecorder()
   LOG(LogLevel::Debug, ("~MediaRecorder (%p)", this));
   UnRegisterActivityObserver();
 }
 
 MediaRecorder::MediaRecorder(DOMMediaStream& aSourceMediaStream,
                              nsPIDOMWindowInner* aOwnerWindow)
   : DOMEventTargetHelper(aOwnerWindow)
   , mState(RecordingState::Inactive)
+  , mAbstractMainThread(aSourceMediaStream.AbstractMainThread())
 {
   MOZ_ASSERT(aOwnerWindow);
   MOZ_ASSERT(aOwnerWindow->IsInnerWindow());
   mDOMStream = &aSourceMediaStream;
 
   RegisterActivityObserver();
 }
 
 MediaRecorder::MediaRecorder(AudioNode& aSrcAudioNode,
                              uint32_t aSrcOutput,
                              nsPIDOMWindowInner* aOwnerWindow)
   : DOMEventTargetHelper(aOwnerWindow)
   , mState(RecordingState::Inactive)
+  , mAbstractMainThread(aSrcAudioNode.AbstractMainThread())
 {
   MOZ_ASSERT(aOwnerWindow);
   MOZ_ASSERT(aOwnerWindow->IsInnerWindow());
 
   // Only AudioNodeStream of kind EXTERNAL_STREAM stores output audio data in
   // the track (see AudioNodeStream::AdvanceOutputSegment()). That means track
   // union stream in recorder session won't be able to copy data from the
   // stream of non-destination node. Create a pipe stream in this case.
--- a/dom/media/MediaRecorder.h
+++ b/dom/media/MediaRecorder.h
@@ -11,16 +11,17 @@
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/MemoryReporting.h"
 #include "nsIDocumentActivity.h"
 
 // Max size for allowing queue encoded data in memory
 #define MAX_ALLOW_MEMORY_BUFFER 1024000
 namespace mozilla {
 
+class AbstractThread;
 class AudioNodeStream;
 class DOMMediaStream;
 class ErrorResult;
 class MediaInputPort;
 struct MediaRecorderOptions;
 class MediaStream;
 class GlobalObject;
 
@@ -152,16 +153,18 @@ protected:
   nsCOMPtr<nsIDocument> mDocument;
 
   // It specifies the container format as well as the audio and video capture formats.
   nsString mMimeType;
 
   uint32_t mAudioBitsPerSecond;
   uint32_t mVideoBitsPerSecond;
   uint32_t mBitsPerSecond;
+
+  const RefPtr<AbstractThread> mAbstractMainThread;
 private:
   // Register MediaRecorder into Document to listen the activity changes.
   void RegisterActivityObserver();
   void UnRegisterActivityObserver();
 
   bool CheckPermission(const nsString &aType);
 };
 
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -24,18 +24,18 @@
 #include "AudioNodeExternalInputStream.h"
 #include "MediaStreamListener.h"
 #include "MediaStreamVideoSink.h"
 #include "mozilla/dom/BaseAudioContextBinding.h"
 #include "mozilla/media/MediaUtils.h"
 #include <algorithm>
 #include "GeckoProfiler.h"
 #include "VideoFrameContainer.h"
+#include "mozilla/AbstractThread.h"
 #include "mozilla/Unused.h"
-#include "mozilla/media/MediaUtils.h"
 #ifdef MOZ_WEBRTC
 #include "AudioOutputObserver.h"
 #endif
 #include "mtransport/runnable_utils.h"
 
 #include "webaudio/blink/DenormalDisabler.h"
 #include "webaudio/blink/HRTFDatabaseLoader.h"
 
@@ -1755,26 +1755,16 @@ MediaStreamGraphImpl::RunInStableState(b
 
 #ifdef DEBUG
   mCanRunMessagesSynchronously = mDetectedNotRunning &&
     mLifecycleState >= LIFECYCLE_WAITING_FOR_THREAD_SHUTDOWN;
 #endif
 
   for (uint32_t i = 0; i < runnables.Length(); ++i) {
     runnables[i]->Run();
-    // "Direct" tail dispatcher are supposed to run immediately following the
-    // execution of the current task. So the meta-tasking that we do here in
-    // RunInStableState() breaks that abstraction a bit unless we handle it here.
-    //
-    // This is particularly important because we can end up with a "stream
-    // ended" notification immediately following a "stream available" notification,
-    // and we need to make sure that the watcher responding to "stream available"
-    // has a chance to run before the second notification starts tearing things
-    // down.
-    AbstractThread::MainThread()->TailDispatcher().DrainDirectTasks();
   }
 }
 
 
 void
 MediaStreamGraphImpl::EnsureRunInStableState()
 {
   NS_ASSERTION(NS_IsMainThread(), "main thread only");
@@ -1834,32 +1824,33 @@ MediaStreamGraphImpl::AppendMessage(Uniq
     }
     return;
   }
 
   mCurrentTaskMessageQueue.AppendElement(Move(aMessage));
   EnsureRunInStableState();
 }
 
-MediaStream::MediaStream()
+MediaStream::MediaStream(AbstractThread* aMainThread)
   : mTracksStartTime(0)
   , mStartBlocking(GRAPH_TIME_MAX)
   , mSuspendedCount(0)
   , mFinished(false)
   , mNotifiedFinished(false)
   , mNotifiedBlocked(false)
   , mHasCurrentData(false)
   , mNotifiedHasCurrentData(false)
   , mMainThreadCurrentTime(0)
   , mMainThreadFinished(false)
   , mFinishedNotificationSent(false)
   , mMainThreadDestroyed(false)
   , mNrOfMainThreadUsers(0)
   , mGraph(nullptr)
   , mAudioChannelType(dom::AudioChannel::Normal)
+  , mAbstractMainThread(aMainThread)
 {
   MOZ_COUNT_CTOR(MediaStream);
 }
 
 MediaStream::~MediaStream()
 {
   MOZ_COUNT_DTOR(MediaStream);
   NS_ASSERTION(mMainThreadDestroyed, "Should have been destroyed already");
@@ -2494,37 +2485,43 @@ MediaStream::RunAfterPendingUpdates(alre
   // runnable will run in finite time.
   if (!(graph->mRealtime || graph->mNonRealtimeProcessing)) {
     runnable->Run();
     return;
   }
 
   class Message : public ControlMessage {
   public:
-    explicit Message(MediaStream* aStream,
-                     already_AddRefed<nsIRunnable> aRunnable)
+    Message(MediaStream* aStream,
+            already_AddRefed<nsIRunnable> aRunnable,
+            AbstractThread* aMainThread)
       : ControlMessage(aStream)
-      , mRunnable(aRunnable) {}
+      , mRunnable(aRunnable)
+      , mAbstractMainThread(aMainThread)
+      {}
     void Run() override
     {
       mStream->Graph()->
-        DispatchToMainThreadAfterStreamStateUpdate(mRunnable.forget());
+        DispatchToMainThreadAfterStreamStateUpdate(mAbstractMainThread,
+                                                   mRunnable.forget());
     }
     void RunDuringShutdown() override
     {
       // Don't run mRunnable now as it may call AppendMessage() which would
       // assume that there are no remaining controlMessagesToRunDuringShutdown.
       MOZ_ASSERT(NS_IsMainThread());
       NS_DispatchToCurrentThread(mRunnable);
     }
   private:
     nsCOMPtr<nsIRunnable> mRunnable;
+    const RefPtr<AbstractThread> mAbstractMainThread;
   };
 
-  graph->AppendMessage(MakeUnique<Message>(this, runnable.forget()));
+  graph->AppendMessage(
+    MakeUnique<Message>(this, runnable.forget(), mAbstractMainThread));
 }
 
 void
 MediaStream::SetTrackEnabledImpl(TrackID aTrackID, DisabledTrackMode aMode)
 {
   if (aMode == DisabledTrackMode::ENABLED) {
     for (int32_t i = mDisabledTracks.Length() - 1; i >= 0; --i) {
       if (aTrackID == mDisabledTracks[i].mTrackID) {
@@ -2628,18 +2625,18 @@ MediaStream::AddMainThreadListener(MainT
 
     RefPtr<MediaStream> mStream;
   };
 
   nsCOMPtr<nsIRunnable> runnable = new NotifyRunnable(this);
   Unused << NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(runnable.forget())));
 }
 
-SourceMediaStream::SourceMediaStream() :
-  MediaStream(),
+SourceMediaStream::SourceMediaStream(AbstractThread* aMainThread) :
+  MediaStream(aMainThread),
   mMutex("mozilla::media::SourceMediaStream"),
   mUpdateKnownTracksTime(0),
   mPullEnabled(false),
   mUpdateFinished(false),
   mNeedsMixing(false)
 {
 }
 
@@ -3202,50 +3199,56 @@ MediaInputPort::BlockSourceTrackIdImpl(T
   mBlockedTracks.AppendElement(Pair<TrackID, BlockingMode>(aTrackId, aBlockingMode));
 }
 
 already_AddRefed<Pledge<bool>>
 MediaInputPort::BlockSourceTrackId(TrackID aTrackId, BlockingMode aBlockingMode)
 {
   class Message : public ControlMessage {
   public:
-    explicit Message(MediaInputPort* aPort,
-                     TrackID aTrackId,
-                     BlockingMode aBlockingMode,
-                     already_AddRefed<nsIRunnable> aRunnable)
+    Message(MediaInputPort* aPort,
+            TrackID aTrackId,
+            BlockingMode aBlockingMode,
+            already_AddRefed<nsIRunnable> aRunnable,
+            AbstractThread* aMainThread)
       : ControlMessage(aPort->GetDestination()),
         mPort(aPort), mTrackId(aTrackId), mBlockingMode(aBlockingMode),
-        mRunnable(aRunnable) {}
+        mRunnable(aRunnable), mAbstractMainThread(aMainThread) {}
     void Run() override
     {
       mPort->BlockSourceTrackIdImpl(mTrackId, mBlockingMode);
       if (mRunnable) {
-        mStream->Graph()->DispatchToMainThreadAfterStreamStateUpdate(mRunnable.forget());
+        mStream->Graph()->
+          DispatchToMainThreadAfterStreamStateUpdate(mAbstractMainThread,
+                                                     mRunnable.forget());
       }
     }
     void RunDuringShutdown() override
     {
       Run();
     }
     RefPtr<MediaInputPort> mPort;
     TrackID mTrackId;
     BlockingMode mBlockingMode;
     nsCOMPtr<nsIRunnable> mRunnable;
+    const RefPtr<AbstractThread> mAbstractMainThread;
   };
 
   MOZ_ASSERT(IsTrackIDExplicit(aTrackId),
              "Only explicit TrackID is allowed");
 
   RefPtr<Pledge<bool>> pledge = new Pledge<bool>();
   nsCOMPtr<nsIRunnable> runnable = NewRunnableFrom([pledge]() {
     MOZ_ASSERT(NS_IsMainThread());
     pledge->Resolve(true);
     return NS_OK;
   });
-  GraphImpl()->AppendMessage(MakeUnique<Message>(this, aTrackId, aBlockingMode, runnable.forget()));
+  GraphImpl()->AppendMessage(MakeUnique<Message>(this, aTrackId, aBlockingMode,
+                                                 runnable.forget(),
+                                                 mAbstractMainThread));
   return pledge.forget();
 }
 
 already_AddRefed<MediaInputPort>
 ProcessedMediaStream::AllocateInputPort(MediaStream* aStream, TrackID aTrackID,
                                         TrackID aDestTrackID,
                                         uint16_t aInputNumber, uint16_t aOutputNumber,
                                         nsTArray<TrackID>* aBlockedTracks)
@@ -3275,17 +3278,17 @@ ProcessedMediaStream::AllocateInputPort(
   MOZ_ASSERT(aTrackID == TRACK_ANY || IsTrackIDExplicit(aTrackID),
              "Only TRACK_ANY and explicit ID are allowed for source track");
   MOZ_ASSERT(aDestTrackID == TRACK_ANY || IsTrackIDExplicit(aDestTrackID),
              "Only TRACK_ANY and explicit ID are allowed for destination track");
   MOZ_ASSERT(aTrackID != TRACK_ANY || aDestTrackID == TRACK_ANY,
              "Generic MediaInputPort cannot produce a single destination track");
   RefPtr<MediaInputPort> port =
     new MediaInputPort(aStream, aTrackID, this, aDestTrackID,
-                       aInputNumber, aOutputNumber);
+                       aInputNumber, aOutputNumber, mAbstractMainThread);
   if (aBlockedTracks) {
     for (TrackID trackID : *aBlockedTracks) {
       port->BlockSourceTrackIdImpl(trackID, BlockingMode::CREATION);
     }
   }
   port->SetGraphImpl(GraphImpl());
   GraphImpl()->AppendMessage(MakeUnique<Message>(port));
   return port.forget();
@@ -3629,35 +3632,35 @@ FinishCollectReports(nsIHandleReportCall
   }
 
 #undef REPORT
 
   manager->EndReport();
 }
 
 SourceMediaStream*
-MediaStreamGraph::CreateSourceStream()
+MediaStreamGraph::CreateSourceStream(AbstractThread* aMainThread)
 {
-  SourceMediaStream* stream = new SourceMediaStream();
+  SourceMediaStream* stream = new SourceMediaStream(aMainThread);
   AddStream(stream);
   return stream;
 }
 
 ProcessedMediaStream*
-MediaStreamGraph::CreateTrackUnionStream()
+MediaStreamGraph::CreateTrackUnionStream(AbstractThread* aMainThread)
 {
-  TrackUnionStream* stream = new TrackUnionStream();
+  TrackUnionStream* stream = new TrackUnionStream(aMainThread);
   AddStream(stream);
   return stream;
 }
 
 ProcessedMediaStream*
-MediaStreamGraph::CreateAudioCaptureStream(TrackID aTrackId)
+MediaStreamGraph::CreateAudioCaptureStream(TrackID aTrackId, AbstractThread* aMainThread)
 {
-  AudioCaptureStream* stream = new AudioCaptureStream(aTrackId);
+  AudioCaptureStream* stream = new AudioCaptureStream(aTrackId, aMainThread);
   AddStream(stream);
   return stream;
 }
 
 void
 MediaStreamGraph::AddStream(MediaStream* aStream)
 {
   NS_ADDREF(aStream);
@@ -4048,9 +4051,20 @@ MediaStreamGraphImpl::ConnectToCaptureSt
     if (mWindowCaptureStreams[i].mWindowId == aWindowId) {
       ProcessedMediaStream* sink = mWindowCaptureStreams[i].mCaptureStreamSink;
       return sink->AllocateInputPort(aMediaStream);
     }
   }
   return nullptr;
 }
 
+void
+MediaStreamGraph::
+DispatchToMainThreadAfterStreamStateUpdate(AbstractThread* aMainThread,
+                                           already_AddRefed<nsIRunnable> aRunnable)
+{
+  MOZ_ASSERT(aMainThread);
+  AssertOnGraphThreadOrNotRunning();
+  *mPendingUpdateRunnables.AppendElement() =
+    aMainThread->CreateDirectTaskDrainer(Move(aRunnable));
+}
+
 } // namespace mozilla
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -72,16 +72,17 @@ namespace media {
  *
  * Media decoding, audio processing and media playback use thread-safe APIs to
  * the media graph to ensure they can continue while the main thread is blocked.
  *
  * When the graph is changed, we may need to throw out buffered data and
  * reprocess it. This is triggered automatically by the MediaStreamGraph.
  */
 
+class AbstractThread;
 class AudioNodeEngine;
 class AudioNodeExternalInputStream;
 class AudioNodeStream;
 class MediaInputPort;
 class MediaStream;
 class MediaStreamGraph;
 class MediaStreamGraphImpl;
 class ProcessedMediaStream;
@@ -270,17 +271,17 @@ struct DisabledTrack {
 #undef GetCurrentTime
 #endif
 
 class MediaStream : public mozilla::LinkedListElement<MediaStream>
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaStream)
 
-  MediaStream();
+  explicit MediaStream(AbstractThread* aMainThread);
 
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~MediaStream();
 
 public:
   /**
    * Returns the graph that owns this stream.
@@ -680,28 +681,30 @@ protected:
   bool mFinishedNotificationSent;
   bool mMainThreadDestroyed;
   int mNrOfMainThreadUsers;
 
   // Our media stream graph.  null if destroyed on the graph thread.
   MediaStreamGraphImpl* mGraph;
 
   dom::AudioChannel mAudioChannelType;
+
+  const RefPtr<AbstractThread> mAbstractMainThread;
 };
 
 /**
  * This is a stream into which a decoder can write audio and video.
  *
  * Audio and video can be written on any thread, but you probably want to
  * always write from the same thread to avoid unexpected interleavings.
  */
 class SourceMediaStream : public MediaStream
 {
 public:
-  explicit SourceMediaStream();
+  explicit SourceMediaStream(AbstractThread* aMainThread);
 
   SourceMediaStream* AsSourceStream() override { return this; }
 
   // Media graph thread only
 
   // Users of audio inputs go through the stream so it can track when the
   // last stream referencing an input goes away, so it can close the cubeb
   // input.  Also note: callable on any thread (though it bounces through
@@ -972,24 +975,26 @@ enum class BlockingMode
  * the port and drop the graph's reference, destroying the object.
  */
 class MediaInputPort final
 {
 private:
   // Do not call this constructor directly. Instead call aDest->AllocateInputPort.
   MediaInputPort(MediaStream* aSource, TrackID& aSourceTrack,
                  ProcessedMediaStream* aDest, TrackID& aDestTrack,
-                 uint16_t aInputNumber, uint16_t aOutputNumber)
+                 uint16_t aInputNumber, uint16_t aOutputNumber,
+                 AbstractThread* aMainThread)
     : mSource(aSource)
     , mSourceTrack(aSourceTrack)
     , mDest(aDest)
     , mDestTrack(aDestTrack)
     , mInputNumber(aInputNumber)
     , mOutputNumber(aOutputNumber)
     , mGraph(nullptr)
+    , mAbstractMainThread(aMainThread)
   {
     MOZ_COUNT_CTOR(MediaInputPort);
   }
 
   // Private destructor, to discourage deletion outside of Release():
   ~MediaInputPort()
   {
     MOZ_COUNT_DTOR(MediaInputPort);
@@ -1122,28 +1127,30 @@ private:
   const uint16_t mInputNumber;
   const uint16_t mOutputNumber;
 
   typedef Pair<TrackID, BlockingMode> BlockedTrack;
   nsTArray<BlockedTrack> mBlockedTracks;
 
   // Our media stream graph
   MediaStreamGraphImpl* mGraph;
+
+  const RefPtr<AbstractThread> mAbstractMainThread;
 };
 
 /**
  * This stream processes zero or more input streams in parallel to produce
  * its output. The details of how the output is produced are handled by
  * subclasses overriding the ProcessInput method.
  */
 class ProcessedMediaStream : public MediaStream
 {
 public:
-  explicit ProcessedMediaStream()
-    : MediaStream(), mAutofinish(false), mCycleMarker(0)
+  explicit ProcessedMediaStream(AbstractThread* aMainThread)
+    : MediaStream(aMainThread), mAutofinish(false), mCycleMarker(0)
   {}
 
   // Control API.
   /**
    * Allocates a new input port attached to source aStream.
    * This stream can be removed by calling MediaInputPort::Remove().
    *
    * The input port is tied to aTrackID in the source stream.
@@ -1300,36 +1307,37 @@ public:
   }
   virtual void CloseAudioInput(AudioDataListener *aListener) {}
 
   // Control API.
   /**
    * Create a stream that a media decoder (or some other source of
    * media data, such as a camera) can write to.
    */
-  SourceMediaStream* CreateSourceStream();
+  SourceMediaStream* CreateSourceStream(AbstractThread* aMainThread);
   /**
    * Create a stream that will form the union of the tracks of its input
    * streams.
    * A TrackUnionStream contains all the tracks of all its input streams.
    * Adding a new input stream makes that stream's tracks immediately appear as new
    * tracks starting at the time the input stream was added.
    * Removing an input stream makes the output tracks corresponding to the
    * removed tracks immediately end.
    * For each added track, the track ID of the output track is the track ID
    * of the input track or one plus the maximum ID of all previously added
    * tracks, whichever is greater.
    * TODO at some point we will probably need to add API to select
    * particular tracks of each input stream.
    */
-  ProcessedMediaStream* CreateTrackUnionStream();
+  ProcessedMediaStream* CreateTrackUnionStream(AbstractThread* aMainThread);
   /**
    * Create a stream that will mix all its audio input.
    */
-  ProcessedMediaStream* CreateAudioCaptureStream(TrackID aTrackId);
+  ProcessedMediaStream* CreateAudioCaptureStream(TrackID aTrackId,
+                                                 AbstractThread* aMainThread);
 
   /**
    * Add a new stream to the graph.  Main thread.
    */
   void AddStream(MediaStream* aStream);
 
   /* From the main thread, ask the MSG to send back an event when the graph
    * thread is running, and audio is being processed. */
@@ -1354,24 +1362,32 @@ public:
    * Start processing non-realtime for a specific number of ticks.
    */
   void StartNonRealtimeProcessing(uint32_t aTicksToProcess);
 
   /**
    * Media graph thread only.
    * Dispatches a runnable that will run on the main thread after all
    * main-thread stream state has been next updated.
+   *
    * Should only be called during MediaStreamListener callbacks or during
    * ProcessedMediaStream::ProcessInput().
+   *
+   * |aMainThread| is the corresponding AbstractThread on the main thread to
+   * drain the direct tasks generated by |aRunnable|.
+   * Note: The reasons for assigning proper |aMainThread| are
+   * - MSG serves media elements in multiple windows run on main thread.
+   * - DocGroup-specific AbstractMainThread is introduced to cluster the tasks
+   *   of the same window for prioritizing tasks among different windows.
+   * - Proper |aMainThread| ensures that tasks dispatched to the main thread are
+   *   clustered to the right queue and are executed in right order.
    */
-  virtual void DispatchToMainThreadAfterStreamStateUpdate(already_AddRefed<nsIRunnable> aRunnable)
-  {
-    AssertOnGraphThreadOrNotRunning();
-    *mPendingUpdateRunnables.AppendElement() = aRunnable;
-  }
+  virtual void
+  DispatchToMainThreadAfterStreamStateUpdate(AbstractThread* aMainThread,
+                                             already_AddRefed<nsIRunnable> aRunnable);
 
   /**
    * Returns graph sample rate in Hz.
    */
   TrackRate GraphRate() const { return mSampleRate; }
 
   void RegisterCaptureStreamForWindow(uint64_t aWindowId,
                                       ProcessedMediaStream* aCaptureStream);
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -78,17 +78,19 @@ NS_IMPL_CYCLE_COLLECTION_0(MediaStreamTr
  * In case of multiple changes to the main thread state, the track's principal
  * will be a combination of its old principal and all the new ones until the
  * latest main thread principal matches the PrincipalHandle on the MSG thread.
  */
 class MediaStreamTrack::PrincipalHandleListener : public MediaStreamTrackListener
 {
 public:
   explicit PrincipalHandleListener(MediaStreamTrack* aTrack)
-    : mTrack(aTrack) {}
+    : mTrack(aTrack)
+    , mAbstractMainThread(aTrack->mOwningStream->AbstractMainThread())
+    {}
 
   void Forget()
   {
     MOZ_ASSERT(NS_IsMainThread());
     mTrack = nullptr;
   }
 
   void DoNotifyPrincipalHandleChanged(const PrincipalHandle& aNewPrincipalHandle)
@@ -100,25 +102,26 @@ public:
     }
 
     mTrack->NotifyPrincipalHandleChanged(aNewPrincipalHandle);
   }
 
   void NotifyPrincipalHandleChanged(MediaStreamGraph* aGraph,
                                     const PrincipalHandle& aNewPrincipalHandle) override
   {
-    nsCOMPtr<nsIRunnable> runnable =
+    aGraph->DispatchToMainThreadAfterStreamStateUpdate(
+      mAbstractMainThread,
       NewRunnableMethod<StoreCopyPassByConstLRef<PrincipalHandle>>(
-        this, &PrincipalHandleListener::DoNotifyPrincipalHandleChanged, aNewPrincipalHandle);
-    aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
+        this, &PrincipalHandleListener::DoNotifyPrincipalHandleChanged, aNewPrincipalHandle));
   }
 
 protected:
   // These fields may only be accessed on the main thread
   MediaStreamTrack* mTrack;
+  const RefPtr<AbstractThread> mAbstractMainThread;
 };
 
 MediaStreamTrack::MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID,
                                    TrackID aInputTrackID,
                                    MediaStreamTrackSource* aSource,
                                    const MediaTrackConstraints& aConstraints)
   : mOwningStream(aStream), mTrackID(aTrackID),
     mInputTrackID(aInputTrackID), mSource(aSource),
--- a/dom/media/TrackUnionStream.cpp
+++ b/dom/media/TrackUnionStream.cpp
@@ -41,18 +41,18 @@ namespace mozilla {
 
 #ifdef STREAM_LOG
 #undef STREAM_LOG
 #endif
 
 LazyLogModule gTrackUnionStreamLog("TrackUnionStream");
 #define STREAM_LOG(type, msg) MOZ_LOG(gTrackUnionStreamLog, type, msg)
 
-TrackUnionStream::TrackUnionStream() :
-  ProcessedMediaStream(), mNextAvailableTrackID(1)
+TrackUnionStream::TrackUnionStream(AbstractThread* aMainThread) :
+  ProcessedMediaStream(aMainThread), mNextAvailableTrackID(1)
 {
 }
 
   void TrackUnionStream::RemoveInput(MediaInputPort* aPort)
   {
     STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p removing input %p", this, aPort));
     for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) {
       if (mTrackMap[i].mInputPort == aPort) {
--- a/dom/media/TrackUnionStream.h
+++ b/dom/media/TrackUnionStream.h
@@ -12,17 +12,17 @@
 
 namespace mozilla {
 
 /**
  * See MediaStreamGraph::CreateTrackUnionStream.
  */
 class TrackUnionStream : public ProcessedMediaStream {
 public:
-  explicit TrackUnionStream();
+  explicit TrackUnionStream(AbstractThread* aMainThread);
 
   virtual TrackUnionStream* AsTrackUnionStream() override { return this; }
   friend class DOMMediaStream;
 
   void RemoveInput(MediaInputPort* aPort) override;
   void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override;
 
   void SetTrackEnabledImpl(TrackID aTrackID, DisabledTrackMode aMode) override;
--- a/dom/media/mediasink/DecodedStream.cpp
+++ b/dom/media/mediasink/DecodedStream.cpp
@@ -32,19 +32,21 @@ namespace mozilla {
 struct PlaybackInfoInit {
   int64_t mStartTime;
   MediaInfo mInfo;
 };
 
 class DecodedStreamGraphListener : public MediaStreamListener {
 public:
   DecodedStreamGraphListener(MediaStream* aStream,
-                             MozPromiseHolder<GenericPromise>&& aPromise)
+                             MozPromiseHolder<GenericPromise>&& aPromise,
+                             AbstractThread* aMainThread)
     : mMutex("DecodedStreamGraphListener::mMutex")
     , mStream(aStream)
+    , mAbstractMainThread(aMainThread)
   {
     mFinishPromise = Move(aPromise);
   }
 
   void NotifyOutput(MediaStreamGraph* aGraph, GraphTime aCurrentTime) override
   {
     MutexAutoLock lock(mMutex);
     if (mStream) {
@@ -52,19 +54,19 @@ public:
         mStream->GraphTimeToStreamTime(aCurrentTime));
       mOnOutput.Notify(t);
     }
   }
 
   void NotifyEvent(MediaStreamGraph* aGraph, MediaStreamGraphEvent event) override
   {
     if (event == MediaStreamGraphEvent::EVENT_FINISHED) {
-      nsCOMPtr<nsIRunnable> event =
-        NewRunnableMethod(this, &DecodedStreamGraphListener::DoNotifyFinished);
-      aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
+      aGraph->DispatchToMainThreadAfterStreamStateUpdate(
+        mAbstractMainThread,
+        NewRunnableMethod(this, &DecodedStreamGraphListener::DoNotifyFinished));
     }
   }
 
   void DoNotifyFinished()
   {
     MOZ_ASSERT(NS_IsMainThread());
     mFinishPromise.ResolveIfExists(true, __func__);
   }
@@ -87,16 +89,18 @@ public:
 private:
   MediaEventProducer<int64_t> mOnOutput;
 
   Mutex mMutex;
   // Members below are protected by mMutex.
   RefPtr<MediaStream> mStream;
   // Main thread only.
   MozPromiseHolder<GenericPromise> mFinishPromise;
+
+  const RefPtr<AbstractThread> mAbstractMainThread;
 };
 
 static void
 UpdateStreamSuspended(AbstractThread* aMainThread, MediaStream* aStream, bool aBlocking)
 {
   if (NS_IsMainThread()) {
     if (aBlocking) {
       aStream->Suspend();
@@ -169,17 +173,17 @@ DecodedStreamData::DecodedStreamData(Out
                                      MozPromiseHolder<GenericPromise>&& aPromise,
                                      AbstractThread* aMainThread)
   : mAudioFramesWritten(0)
   , mNextVideoTime(aInit.mStartTime)
   , mNextAudioTime(aInit.mStartTime)
   , mHaveSentFinish(false)
   , mHaveSentFinishAudio(false)
   , mHaveSentFinishVideo(false)
-  , mStream(aOutputStreamManager->Graph()->CreateSourceStream())
+  , mStream(aOutputStreamManager->Graph()->CreateSourceStream(aMainThread))
   // DecodedStreamGraphListener will resolve this promise.
   , mListener(new DecodedStreamGraphListener(mStream, Move(aPromise), aMainThread))
   // mPlaying is initially true because MDSM won't start playback until playing
   // becomes true. This is consistent with the settings of AudioSink.
   , mPlaying(true)
   , mEOSVideoCompensation(false)
   , mOutputStreamManager(aOutputStreamManager)
   , mAbstractMainThread(aMainThread)
--- a/dom/media/webaudio/AudioDestinationNode.cpp
+++ b/dom/media/webaudio/AudioDestinationNode.cpp
@@ -257,17 +257,18 @@ public:
 
     bool newInputMuted = aInput.IsNull() || aInput.IsMuted();
     if (newInputMuted != mLastInputMuted) {
       mLastInputMuted = newInputMuted;
 
       RefPtr<InputMutedRunnable> runnable =
         new InputMutedRunnable(aStream, newInputMuted);
       aStream->Graph()->
-        DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
+        DispatchToMainThreadAfterStreamStateUpdate(mAbstractMainThread,
+                                                   runnable.forget());
     }
   }
 
   bool IsActive() const override
   {
     // Keep processing to track stream time, which is used for all timelines
     // associated with the same AudioContext.  If there are no other engines
     // for the AudioContext, then this could return false to suspend the
--- a/dom/media/webaudio/AudioNode.cpp
+++ b/dom/media/webaudio/AudioNode.cpp
@@ -49,16 +49,17 @@ AudioNode::AudioNode(AudioContext* aCont
                      ChannelInterpretation aChannelInterpretation)
   : DOMEventTargetHelper(aContext->GetParentObject())
   , mContext(aContext)
   , mChannelCount(aChannelCount)
   , mChannelCountMode(aChannelCountMode)
   , mChannelInterpretation(aChannelInterpretation)
   , mId(gId++)
   , mPassThrough(false)
+  , mAbstractMainThread(aContext->GetOwnerGlobal()->AbstractMainThreadFor(TaskCategory::Other))
 {
   MOZ_ASSERT(aContext);
   DOMEventTargetHelper::BindToOwner(aContext->GetParentObject());
   aContext->RegisterNode(this);
 }
 
 AudioNode::~AudioNode()
 {
--- a/dom/media/webaudio/AudioNode.h
+++ b/dom/media/webaudio/AudioNode.h
@@ -15,16 +15,18 @@
 #include "MediaStreamGraph.h"
 #include "WebAudioUtils.h"
 #include "mozilla/MemoryReporting.h"
 #include "nsWeakReference.h"
 #include "SelfRef.h"
 
 namespace mozilla {
 
+class AbstractThread;
+
 namespace dom {
 
 class AudioContext;
 class AudioBufferSourceNode;
 class AudioParam;
 class AudioParamTimeline;
 struct ThreeDPoint;
 
@@ -220,16 +222,18 @@ public:
 
   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
 
   // Returns a string from constant static storage identifying the dom node
   // type.
   virtual const char* NodeType() const = 0;
 
+  AbstractThread* AbstractMainThread() const { return mAbstractMainThread; }
+
 private:
   // Given:
   //
   // - a DestinationType, that can be an AudioNode or an AudioParam ;
   // - a Predicate, a function that takes an InputNode& and returns a bool ;
   //
   // This method iterates on the InputNodes() of the node at the index
   // aDestinationIndex, and calls `DisconnectFromOutputIfConnected` with this
@@ -284,14 +288,16 @@ private:
   nsTArray<RefPtr<AudioParam> > mOutputParams;
   uint32_t mChannelCount;
   ChannelCountMode mChannelCountMode;
   ChannelInterpretation mChannelInterpretation;
   const uint32_t mId;
   // Whether the node just passes through its input.  This is a devtools API that
   // only works for some node types.
   bool mPassThrough;
+  // DocGroup-specifc AbstractThread::MainThread() for MediaStreamGraph operations.
+  const RefPtr<AbstractThread> mAbstractMainThread;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif
--- a/dom/media/webaudio/AudioNodeEngine.cpp
+++ b/dom/media/webaudio/AudioNodeEngine.cpp
@@ -1,15 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AudioNodeEngine.h"
+
+#include "mozilla/AbstractThread.h"
 #ifdef BUILD_ARM_NEON
 #include "mozilla/arm.h"
 #include "AudioNodeEngineNEON.h"
 #endif
 #ifdef USE_SSE2
 #include "mozilla/SSE.h"
 #include "AlignmentUtils.h"
 #include "AudioNodeEngineSSE2.h"
@@ -370,16 +372,28 @@ AudioBufferSumOfSquares(const float* aIn
 
   while (aLength--) {
     sum += *aInput * *aInput;
     ++aInput;
   }
   return sum;
 }
 
+AudioNodeEngine::AudioNodeEngine(dom::AudioNode* aNode)
+  : mNode(aNode)
+  , mNodeType(aNode ? aNode->NodeType() : nullptr)
+  , mInputCount(aNode ? aNode->NumberOfInputs() : 1)
+  , mOutputCount(aNode ? aNode->NumberOfOutputs() : 0)
+  , mAbstractMainThread(
+      aNode ? aNode->AbstractMainThread() : AbstractThread::MainThread())
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_COUNT_CTOR(AudioNodeEngine);
+}
+
 void
 AudioNodeEngine::ProcessBlock(AudioNodeStream* aStream,
                               GraphTime aFrom,
                               const AudioBlock& aInput,
                               AudioBlock* aOutput,
                               bool* aFinished)
 {
   MOZ_ASSERT(mInputCount <= 1 && mOutputCount <= 1);
--- a/dom/media/webaudio/AudioNodeEngine.h
+++ b/dom/media/webaudio/AudioNodeEngine.h
@@ -15,16 +15,17 @@ namespace mozilla {
 
 namespace dom {
 struct ThreeDPoint;
 class AudioParamTimeline;
 class DelayNodeEngine;
 struct AudioTimelineEvent;
 } // namespace dom
 
+class AbstractThread;
 class AudioBlock;
 class AudioNodeStream;
 
 /**
  * This class holds onto a set of immutable channel buffers. The storage
  * for the buffers must be malloced, but the buffer pointers and the malloc
  * pointers can be different (e.g. if the buffers are contained inside
  * some malloced object).
@@ -250,25 +251,18 @@ AudioBufferSumOfSquares(const float* aIn
  * MediaStreamGraph thread.
  */
 class AudioNodeEngine
 {
 public:
   // This should be compatible with AudioNodeStream::OutputChunks.
   typedef AutoTArray<AudioBlock, 1> OutputChunks;
 
-  explicit AudioNodeEngine(dom::AudioNode* aNode)
-    : mNode(aNode)
-    , mNodeType(aNode ? aNode->NodeType() : nullptr)
-    , mInputCount(aNode ? aNode->NumberOfInputs() : 1)
-    , mOutputCount(aNode ? aNode->NumberOfOutputs() : 0)
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    MOZ_COUNT_CTOR(AudioNodeEngine);
-  }
+  explicit AudioNodeEngine(dom::AudioNode* aNode);
+
   virtual ~AudioNodeEngine()
   {
     MOZ_ASSERT(!mNode, "The node reference must be already cleared");
     MOZ_COUNT_DTOR(AudioNodeEngine);
   }
 
   virtual dom::DelayNodeEngine* AsDelayNodeEngine() { return nullptr; }
 
@@ -398,13 +392,16 @@ public:
     aUsage.mNodeType = mNodeType;
   }
 
 private:
   dom::AudioNode* mNode; // main thread only
   const char* const mNodeType;
   const uint16_t mInputCount;
   const uint16_t mOutputCount;
+
+protected:
+  const RefPtr<AbstractThread> mAbstractMainThread;
 };
 
 } // namespace mozilla
 
 #endif /* MOZILLA_AUDIONODEENGINE_H_ */
--- a/dom/media/webaudio/AudioNodeExternalInputStream.cpp
+++ b/dom/media/webaudio/AudioNodeExternalInputStream.cpp
@@ -9,37 +9,39 @@
 #include "AudioNodeExternalInputStream.h"
 #include "AudioChannelFormat.h"
 #include "mozilla/dom/MediaStreamAudioSourceNode.h"
 
 using namespace mozilla::dom;
 
 namespace mozilla {
 
-AudioNodeExternalInputStream::AudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate)
-  : AudioNodeStream(aEngine, NO_STREAM_FLAGS, aSampleRate)
+AudioNodeExternalInputStream::AudioNodeExternalInputStream(
+  AudioNodeEngine* aEngine, TrackRate aSampleRate, AbstractThread* aMainThread)
+  : AudioNodeStream(aEngine, NO_STREAM_FLAGS, aSampleRate, aMainThread)
 {
   MOZ_COUNT_CTOR(AudioNodeExternalInputStream);
 }
 
 AudioNodeExternalInputStream::~AudioNodeExternalInputStream()
 {
   MOZ_COUNT_DTOR(AudioNodeExternalInputStream);
 }
 
 /* static */ already_AddRefed<AudioNodeExternalInputStream>
 AudioNodeExternalInputStream::Create(MediaStreamGraph* aGraph,
-                                     AudioNodeEngine* aEngine)
+                                     AudioNodeEngine* aEngine,
+                                     AbstractThread* aMainThread)
 {
   AudioContext* ctx = aEngine->NodeMainThread()->Context();
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aGraph->GraphRate() == ctx->SampleRate());
 
   RefPtr<AudioNodeExternalInputStream> stream =
-    new AudioNodeExternalInputStream(aEngine, aGraph->GraphRate());
+    new AudioNodeExternalInputStream(aEngine, aGraph->GraphRate(), aMainThread);
   stream->mSuspendedCount += ctx->ShouldSuspendNewStream();
   aGraph->AddStream(stream);
   return stream.forget();
 }
 
 /**
  * Copies the data in aInput to aOffsetInBlock within aBlock.
  * aBlock must have been allocated with AllocateInputBlock and have a channel
--- a/dom/media/webaudio/AudioNodeExternalInputStream.h
+++ b/dom/media/webaudio/AudioNodeExternalInputStream.h
@@ -7,30 +7,33 @@
 #define MOZILLA_AUDIONODEEXTERNALINPUTSTREAM_H_
 
 #include "MediaStreamGraph.h"
 #include "AudioNodeStream.h"
 #include "mozilla/Atomics.h"
 
 namespace mozilla {
 
+class AbstractThread;
+
 /**
  * This is a MediaStream implementation that acts for a Web Audio node but
  * unlike other AudioNodeStreams, supports any kind of MediaStream as an
  * input --- handling any number of audio tracks and handling blocking of
  * the input MediaStream.
  */
 class AudioNodeExternalInputStream final : public AudioNodeStream
 {
 public:
   static already_AddRefed<AudioNodeExternalInputStream>
-  Create(MediaStreamGraph* aGraph, AudioNodeEngine* aEngine);
+  Create(MediaStreamGraph* aGraph, AudioNodeEngine* aEngine, AbstractThread* aMainThread);
 
 protected:
-  AudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate);
+  AudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate,
+                               AbstractThread* aMainThread);
   ~AudioNodeExternalInputStream();
 
 public:
   void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override;
 
 private:
   /**
    * Determines if this is enabled or not.  Disabled nodes produce silence.
--- a/dom/media/webaudio/AudioNodeStream.cpp
+++ b/dom/media/webaudio/AudioNodeStream.cpp
@@ -24,18 +24,19 @@ namespace mozilla {
  * for regular audio contexts, and the rate requested by the web content
  * for offline audio contexts.
  * Each chunk in the track is a single block of WEBAUDIO_BLOCK_SIZE samples.
  * Note: This must be a different value than MEDIA_STREAM_DEST_TRACK_ID
  */
 
 AudioNodeStream::AudioNodeStream(AudioNodeEngine* aEngine,
                                  Flags aFlags,
-                                 TrackRate aSampleRate)
-  : ProcessedMediaStream(),
+                                 TrackRate aSampleRate,
+                                 AbstractThread* aMainThread)
+  : ProcessedMediaStream(aMainThread),
     mEngine(aEngine),
     mSampleRate(aSampleRate),
     mFlags(aFlags),
     mNumberOfInputChannels(2),
     mIsActive(aEngine->IsActive()),
     mMarkAsFinishedAfterThisBlock(false),
     mAudioParamStream(false),
     mPassThrough(false)
@@ -72,17 +73,18 @@ AudioNodeStream::Create(AudioContext* aC
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(aGraph);
 
   // MediaRecorders use an AudioNodeStream, but no AudioNode
   AudioNode* node = aEngine->NodeMainThread();
 
   RefPtr<AudioNodeStream> stream =
-    new AudioNodeStream(aEngine, aFlags, aGraph->GraphRate());
+    new AudioNodeStream(aEngine, aFlags, aGraph->GraphRate(),
+      aCtx->GetOwnerGlobal()->AbstractMainThreadFor(TaskCategory::Other));
   stream->mSuspendedCount += aCtx->ShouldSuspendNewStream();
   if (node) {
     stream->SetChannelMixingParametersImpl(node->ChannelCount(),
                                            node->ChannelCountModeValue(),
                                            node->ChannelInterpretationValue());
   }
   aGraph->AddStream(stream);
   return stream.forget();
--- a/dom/media/webaudio/AudioNodeStream.h
+++ b/dom/media/webaudio/AudioNodeStream.h
@@ -16,16 +16,17 @@
 namespace mozilla {
 
 namespace dom {
 struct ThreeDPoint;
 struct AudioTimelineEvent;
 class AudioContext;
 } // namespace dom
 
+class AbstractThread;
 class ThreadSharedFloatArrayBufferList;
 class AudioNodeEngine;
 
 typedef AlignedAutoTArray<float, GUESS_AUDIO_CHANNELS*WEBAUDIO_BLOCK_SIZE, 16> DownmixBufferType;
 
 /**
  * An AudioNodeStream produces one audio track with ID AUDIO_TRACK.
  * The start time of the AudioTrack is aligned to the start time of the
@@ -70,17 +71,18 @@ public:
          MediaStreamGraph* aGraph);
 
 protected:
   /**
    * Transfers ownership of aEngine to the new AudioNodeStream.
    */
   AudioNodeStream(AudioNodeEngine* aEngine,
                   Flags aFlags,
-                  TrackRate aSampleRate);
+                  TrackRate aSampleRate,
+                  AbstractThread* aMainThread);
 
   ~AudioNodeStream();
 
 public:
   // Control API
   /**
    * Sets a parameter that's a time relative to some stream's played time.
    * This time is converted to a time relative to this stream when it's set.
--- a/dom/media/webaudio/BiquadFilterNode.cpp
+++ b/dom/media/webaudio/BiquadFilterNode.cpp
@@ -155,31 +155,33 @@ public:
       if (!hasTail) {
         if (!mBiquads.IsEmpty()) {
           mBiquads.Clear();
           aStream->ScheduleCheckForInactive();
 
           RefPtr<PlayingRefChangeHandler> refchanged =
             new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::RELEASE);
           aStream->Graph()->
-            DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
+            DispatchToMainThreadAfterStreamStateUpdate(mAbstractMainThread,
+                                                       refchanged.forget());
         }
 
         aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
         return;
       }
 
       PodArrayZero(inputBuffer);
 
     } else if(mBiquads.Length() != aInput.ChannelCount()){
       if (mBiquads.IsEmpty()) {
         RefPtr<PlayingRefChangeHandler> refchanged =
           new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::ADDREF);
         aStream->Graph()->
-          DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
+          DispatchToMainThreadAfterStreamStateUpdate(mAbstractMainThread,
+                                                     refchanged.forget());
       } else { // Help people diagnose bug 924718
         WebAudioUtils::LogToDeveloperConsole(mWindowID,
                                              "BiquadFilterChannelCountChangeWarning");
       }
 
       // Adjust the number of biquads based on the number of channels
       mBiquads.SetLength(aInput.ChannelCount());
     }
--- a/dom/media/webaudio/ConvolverNode.cpp
+++ b/dom/media/webaudio/ConvolverNode.cpp
@@ -121,17 +121,18 @@ public:
         WriteZeroesToAudioBlock(&input, 0, WEBAUDIO_BLOCK_SIZE);
       } else {
         if (mLeftOverData != INT32_MIN) {
           mLeftOverData = INT32_MIN;
           aStream->ScheduleCheckForInactive();
           RefPtr<PlayingRefChanged> refchanged =
             new PlayingRefChanged(aStream, PlayingRefChanged::RELEASE);
           aStream->Graph()->
-            DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
+            DispatchToMainThreadAfterStreamStateUpdate(mAbstractMainThread,
+                                                       refchanged.forget());
         }
         aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
         return;
       }
     } else {
       if (aInput.mVolume != 1.0f) {
         // Pre-multiply the input's volume
         uint32_t numChannels = aInput.ChannelCount();
@@ -142,17 +143,18 @@ public:
           AudioBlockCopyChannelWithScale(src, aInput.mVolume, dest);
         }
       }
 
       if (mLeftOverData <= 0) {
         RefPtr<PlayingRefChanged> refchanged =
           new PlayingRefChanged(aStream, PlayingRefChanged::ADDREF);
         aStream->Graph()->
-          DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
+          DispatchToMainThreadAfterStreamStateUpdate(mAbstractMainThread,
+                                                     refchanged.forget());
       }
       mLeftOverData = mBufferLength;
       MOZ_ASSERT(mLeftOverData > 0);
     }
     aOutput->AllocateChannels(2);
 
     mReverb->process(&input, aOutput);
   }
--- a/dom/media/webaudio/DelayNode.cpp
+++ b/dom/media/webaudio/DelayNode.cpp
@@ -78,33 +78,35 @@ public:
   {
     MOZ_ASSERT(aStream->SampleRate() == mDestination->SampleRate());
 
     if (!aInput.IsSilentOrSubnormal()) {
       if (mLeftOverData <= 0) {
         RefPtr<PlayingRefChanged> refchanged =
           new PlayingRefChanged(aStream, PlayingRefChanged::ADDREF);
         aStream->Graph()->
-          DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
+          DispatchToMainThreadAfterStreamStateUpdate(mAbstractMainThread,
+                                                     refchanged.forget());
       }
       mLeftOverData = mBuffer.MaxDelayTicks();
     } else if (mLeftOverData > 0) {
       mLeftOverData -= WEBAUDIO_BLOCK_SIZE;
     } else {
       if (mLeftOverData != INT32_MIN) {
         mLeftOverData = INT32_MIN;
         aStream->ScheduleCheckForInactive();
 
         // Delete our buffered data now we no longer need it
         mBuffer.Reset();
 
         RefPtr<PlayingRefChanged> refchanged =
           new PlayingRefChanged(aStream, PlayingRefChanged::RELEASE);
         aStream->Graph()->
-          DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
+          DispatchToMainThreadAfterStreamStateUpdate(mAbstractMainThread,
+                                                     refchanged.forget());
       }
       aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
       return;
     }
 
     mBuffer.Write(aInput);
 
     // Skip output update if mLastChunks has already been set by
--- a/dom/media/webaudio/IIRFilterNode.cpp
+++ b/dom/media/webaudio/IIRFilterNode.cpp
@@ -52,30 +52,32 @@ public:
         // as well.
         if (allZero) {
           mIIRFilters.Clear();
           aStream->ScheduleCheckForInactive();
 
           RefPtr<PlayingRefChangeHandler> refchanged =
             new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::RELEASE);
           aStream->Graph()->
-            DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
+            DispatchToMainThreadAfterStreamStateUpdate(mAbstractMainThread,
+                                                       refchanged.forget());
 
           aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
           return;
         }
 
         PodZero(alignedInputBuffer, WEBAUDIO_BLOCK_SIZE);
       }
     } else if(mIIRFilters.Length() != aInput.ChannelCount()){
       if (mIIRFilters.IsEmpty()) {
         RefPtr<PlayingRefChangeHandler> refchanged =
           new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::ADDREF);
         aStream->Graph()->
-          DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
+          DispatchToMainThreadAfterStreamStateUpdate(mAbstractMainThread,
+                                                     refchanged.forget());
       } else {
         WebAudioUtils::LogToDeveloperConsole(mWindowID,
                                              "IIRFilterChannelCountChangeWarning");
       }
 
       // Adjust the number of filters based on the number of channels
       mIIRFilters.SetLength(aInput.ChannelCount());
       for (size_t i = 0; i < aInput.ChannelCount(); ++i) {
--- a/dom/media/webaudio/MediaStreamAudioSourceNode.cpp
+++ b/dom/media/webaudio/MediaStreamAudioSourceNode.cpp
@@ -79,17 +79,19 @@ MediaStreamAudioSourceNode::Init(DOMMedi
   MediaStreamGraph* graph = Context()->Graph();
   if (NS_WARN_IF(graph != inputStream->Graph())) {
     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return;
   }
 
   mInputStream = aMediaStream;
   AudioNodeEngine* engine = new MediaStreamAudioSourceNodeEngine(this);
-  mStream = AudioNodeExternalInputStream::Create(graph, engine);
+  mStream =
+    AudioNodeExternalInputStream::Create(graph, engine,
+                                         aMediaStream->AbstractMainThread());
   mInputStream->AddConsumerToKeepAlive(static_cast<nsIDOMEventTarget*>(this));
 
   mInputStream->RegisterTrackListener(this);
   AttachToFirstTrack(mInputStream);
 }
 
 void
 MediaStreamAudioSourceNode::Destroy()
--- a/dom/media/webaudio/PannerNode.cpp
+++ b/dom/media/webaudio/PannerNode.cpp
@@ -204,27 +204,29 @@ public:
         if (mLeftOverData != INT_MIN) {
           mLeftOverData = INT_MIN;
           aStream->ScheduleCheckForInactive();
           mHRTFPanner->reset();
 
           RefPtr<PlayingRefChangeHandler> refchanged =
             new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::RELEASE);
           aStream->Graph()->
-            DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
+          DispatchToMainThreadAfterStreamStateUpdate(mAbstractMainThread,
+                                                     refchanged.forget());
         }
         aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
         return;
       }
     } else if (mPanningModelFunction == &PannerNodeEngine::HRTFPanningFunction) {
       if (mLeftOverData == INT_MIN) {
         RefPtr<PlayingRefChangeHandler> refchanged =
           new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::ADDREF);
         aStream->Graph()->
-          DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
+          DispatchToMainThreadAfterStreamStateUpdate(mAbstractMainThread,
+                                                     refchanged.forget());
       }
       mLeftOverData = mHRTFPanner->maxTailFrames();
     }
 
     StreamTime tick = mDestination->GraphTimeToStreamTime(aFrom);
     (this->*mPanningModelFunction)(aInput, aOutput, tick);
   }
 
--- a/dom/media/webspeech/synth/nsSpeechTask.cpp
+++ b/dom/media/webspeech/synth/nsSpeechTask.cpp
@@ -26,21 +26,23 @@ extern mozilla::LogModule* GetSpeechSynt
 #define AUDIO_TRACK 1
 
 namespace mozilla {
 namespace dom {
 
 class SynthStreamListener : public MediaStreamListener
 {
 public:
-  explicit SynthStreamListener(nsSpeechTask* aSpeechTask,
-                               MediaStream* aStream) :
-    mSpeechTask(aSpeechTask),
-    mStream(aStream),
-    mStarted(false)
+  SynthStreamListener(nsSpeechTask* aSpeechTask,
+                      MediaStream* aStream,
+                      AbstractThread* aMainThread)
+    : mSpeechTask(aSpeechTask)
+    , mStream(aStream)
+    , mStarted(false)
+    , mAbstractMainThread(aMainThread)
   {
   }
 
   void DoNotifyStarted()
   {
     if (mSpeechTask) {
       mSpeechTask->DispatchStartInner();
     }
@@ -57,54 +59,56 @@ public:
   void NotifyEvent(MediaStreamGraph* aGraph,
                    MediaStreamGraphEvent event) override
   {
     switch (event) {
       case MediaStreamGraphEvent::EVENT_FINISHED:
         {
           if (!mStarted) {
             mStarted = true;
-            nsCOMPtr<nsIRunnable> startRunnable =
-              NewRunnableMethod(this, &SynthStreamListener::DoNotifyStarted);
-            aGraph->DispatchToMainThreadAfterStreamStateUpdate(startRunnable.forget());
+            aGraph->DispatchToMainThreadAfterStreamStateUpdate(
+              mAbstractMainThread,
+              NewRunnableMethod(this, &SynthStreamListener::DoNotifyStarted));
           }
 
-          nsCOMPtr<nsIRunnable> endRunnable =
-            NewRunnableMethod(this, &SynthStreamListener::DoNotifyFinished);
-          aGraph->DispatchToMainThreadAfterStreamStateUpdate(endRunnable.forget());
+          aGraph->DispatchToMainThreadAfterStreamStateUpdate(
+            mAbstractMainThread,
+            NewRunnableMethod(this, &SynthStreamListener::DoNotifyFinished));
         }
         break;
       case MediaStreamGraphEvent::EVENT_REMOVED:
         mSpeechTask = nullptr;
         // Dereference MediaStream to destroy safety
         mStream = nullptr;
         break;
       default:
         break;
     }
   }
 
   void NotifyBlockingChanged(MediaStreamGraph* aGraph, Blocking aBlocked) override
   {
     if (aBlocked == MediaStreamListener::UNBLOCKED && !mStarted) {
       mStarted = true;
-      nsCOMPtr<nsIRunnable> event =
-        NewRunnableMethod(this, &SynthStreamListener::DoNotifyStarted);
-      aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
+      aGraph->DispatchToMainThreadAfterStreamStateUpdate(
+        mAbstractMainThread,
+        NewRunnableMethod(this, &SynthStreamListener::DoNotifyStarted));
     }
   }
 
 private:
   // Raw pointer; if we exist, the stream exists,
   // and 'mSpeechTask' exclusively owns it and therefor exists as well.
   nsSpeechTask* mSpeechTask;
   // This is KungFuDeathGrip for MediaStream
   RefPtr<MediaStream> mStream;
 
   bool mStarted;
+
+  const RefPtr<AbstractThread> mAbstractMainThread;
 };
 
 // nsSpeechTask
 
 NS_IMPL_CYCLE_COLLECTION(nsSpeechTask, mSpeechSynthesis, mUtterance, mCallback);
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSpeechTask)
   NS_INTERFACE_MAP_ENTRY(nsISpeechTask)
@@ -159,17 +163,17 @@ nsSpeechTask::~nsSpeechTask()
   }
 }
 
 void
 nsSpeechTask::InitDirectAudio()
 {
   mStream = MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER,
                                           AudioChannel::Normal)->
-    CreateSourceStream();
+    CreateSourceStream(AbstractThread::MainThread() /* Non DocGroup-version for the task in parent. */);
   mIndirectAudio = false;
   mInited = true;
 }
 
 void
 nsSpeechTask::InitIndirectAudio()
 {
   mIndirectAudio = true;
@@ -198,17 +202,19 @@ nsSpeechTask::Setup(nsISpeechTaskCallbac
       NS_WARNING("Audio info arguments in Setup() are ignored for indirect audio services.");
     }
     return NS_OK;
   }
 
   // mStream is set up in Init() that should be called before this.
   MOZ_ASSERT(mStream);
 
-  mStream->AddListener(new SynthStreamListener(this, mStream));
+  mStream->AddListener(
+    // Non DocGroup-version of AbstractThread::MainThread for the task in parent.
+    new SynthStreamListener(this, mStream, AbstractThread::MainThread()));
 
   // XXX: Support more than one channel
   if(NS_WARN_IF(!(aChannels == 1))) {
     return NS_ERROR_FAILURE;
   }
 
   mChannels = aChannels;