Bug 1320744 - Part 2, Allow ChannelEventQueue to perform flush on multiple threads. r=mayhemer draft
authorShih-Chiang Chien <schien@mozilla.com>
Sat, 18 Mar 2017 11:36:08 +0800
changeset 551269 5627acad147bb0881897d93a7eddd2715cf9f3c5
parent 551268 568b0e6178389222197bfbc0093e05fc6cae0794
child 551270 88e9c56a73d268e1313c654c78a14e7b7d04fd9b
push id51005
push userbmo:schien@mozilla.com
push dateSat, 25 Mar 2017 08:55:30 +0000
reviewersmayhemer
bugs1320744
milestone55.0a1
Bug 1320744 - Part 2, Allow ChannelEventQueue to perform flush on multiple threads. r=mayhemer MozReview-Commit-ID: Egu2mvwFTUF
netwerk/ipc/ChannelEventQueue.cpp
netwerk/ipc/ChannelEventQueue.h
netwerk/protocol/ftp/FTPChannelChild.cpp
netwerk/protocol/ftp/FTPChannelChild.h
netwerk/protocol/ftp/FTPChannelParent.cpp
netwerk/protocol/http/HttpChannelChild.cpp
netwerk/protocol/http/HttpChannelChild.h
netwerk/protocol/http/HttpChannelParent.cpp
netwerk/protocol/websocket/WebSocketChannelChild.cpp
netwerk/protocol/websocket/WebSocketChannelChild.h
netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp
--- a/netwerk/ipc/ChannelEventQueue.cpp
+++ b/netwerk/ipc/ChannelEventQueue.cpp
@@ -1,20 +1,21 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: set sw=2 ts=8 et tw=80 :
  */
 /* 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 "nsISupports.h"
-#include "mozilla/net/ChannelEventQueue.h"
+#include "ChannelEventQueue.h"
+
+#include "mozilla/Assertions.h"
 #include "mozilla/Unused.h"
+#include "nsISupports.h"
 #include "nsThreadUtils.h"
-#include "mozilla/Unused.h"
 
 namespace mozilla {
 namespace net {
 
 ChannelEvent*
 ChannelEventQueue::TakeEvent()
 {
   MutexAutoLock lock(mMutex);
@@ -34,80 +35,125 @@ void
 ChannelEventQueue::FlushQueue()
 {
   // Events flushed could include destruction of channel (and our own
   // destructor) unless we make sure its refcount doesn't drop to 0 while this
   // method is running.
   nsCOMPtr<nsISupports> kungFuDeathGrip(mOwner);
   mozilla::Unused << kungFuDeathGrip; // Not used in this function
 
-  // Prevent flushed events from flushing the queue recursively
+  bool needResumeOnOtherThread = false;
   {
-    MutexAutoLock lock(mMutex);
-    mFlushing = true;
+    // Don't allow event enqueued during flush to make sure all events
+    // are run.
+    ReentrantMonitorAutoEnter monitor(mRunningMonitor);
+
+    // Prevent flushed events from flushing the queue recursively
+    {
+      MutexAutoLock lock(mMutex);
+      MOZ_ASSERT(!mFlushing);
+      mFlushing = true;
+    }
+
+    while (true) {
+      UniquePtr<ChannelEvent> event(TakeEvent());
+      if (!event) {
+        break;
+      }
+
+      nsCOMPtr<nsIEventTarget> target = event->GetEventTarget();
+      MOZ_ASSERT(target);
+
+      bool isCurrentThread = false;
+      nsresult rv = target->IsOnCurrentThread(&isCurrentThread);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        // Simply run this event on current thread if we are not sure about it
+        // in release channel, or assert in Aurora/Nightly channel.
+        MOZ_DIAGNOSTIC_ASSERT(false);
+        isCurrentThread = true;
+      }
+
+      if (!isCurrentThread) {
+        // Next event needs to run on another thread. Put it back to
+        // the front of the queue can try resume on that thread.
+        Suspend();
+        PrependEvent(event);
+
+        needResumeOnOtherThread = true;
+        break;
+      }
+
+      event->Run();
+    }
+
+    {
+      MutexAutoLock lock(mMutex);
+      MOZ_ASSERT(mFlushing);
+      mFlushing = false;
+      MOZ_ASSERT(mEventQueue.IsEmpty() || (needResumeOnOtherThread || mSuspended || !!mForcedCount));
+    }
   }
 
-  while (true) {
-    UniquePtr<ChannelEvent> event(TakeEvent());
-    if (!event) {
-      break;
-    }
-
-    event->Run();
+  // The flush procedure is aborted because next event cannot be run on current
+  // thread. We need to resume the event processing right after flush procedure
+  // is finished.
+  // Note: we cannot call Resume() while "mFlushing == true" because
+  // CompleteResume will not trigger FlushQueue while there is an ongoing flush.
+  if (needResumeOnOtherThread) {
+    Resume();
   }
-
-  MutexAutoLock lock(mMutex);
-  mFlushing = false;
 }
 
 void
-ChannelEventQueue::Resume()
+ChannelEventQueue::Suspend()
 {
   MutexAutoLock lock(mMutex);
+  SuspendInternal();
+}
+
+void
+ChannelEventQueue::SuspendInternal()
+{
+  mMutex.AssertCurrentThreadOwns();
+
+  mSuspended = true;
+  mSuspendCount++;
+}
+
+void ChannelEventQueue::Resume()
+{
+  MutexAutoLock lock(mMutex);
+  ResumeInternal();
+}
+
+void
+ChannelEventQueue::ResumeInternal()
+{
+  mMutex.AssertCurrentThreadOwns();
 
   // Resuming w/o suspend: error in debug mode, ignore in build
   MOZ_ASSERT(mSuspendCount > 0);
   if (mSuspendCount <= 0) {
     return;
   }
 
   if (!--mSuspendCount) {
-    RefPtr<Runnable> event =
-      NewRunnableMethod(this, &ChannelEventQueue::CompleteResume);
-    if (mTargetThread) {
-      mTargetThread->Dispatch(event.forget(), NS_DISPATCH_NORMAL);
-    } else {
-      MOZ_RELEASE_ASSERT(NS_IsMainThread());
-      Unused << NS_WARN_IF(NS_FAILED(NS_DispatchToCurrentThread(event.forget())));
+    if (mEventQueue.IsEmpty()) {
+      // Nothing in queue to flush, simply clear the flag.
+      mSuspended = false;
+      return;
     }
-  }
-}
-
-nsresult
-ChannelEventQueue::RetargetDeliveryTo(nsIEventTarget* aTargetThread)
-{
-  MOZ_RELEASE_ASSERT(NS_IsMainThread());
-  MOZ_RELEASE_ASSERT(!mTargetThread);
-  MOZ_RELEASE_ASSERT(aTargetThread);
 
-  mTargetThread = do_QueryInterface(aTargetThread);
-  MOZ_RELEASE_ASSERT(mTargetThread);
-
-  return NS_OK;
-}
+    // Worker thread requires a CancelableRunnable.
+    RefPtr<Runnable> event =
+      NewCancelableRunnableMethod(this, &ChannelEventQueue::CompleteResume);
 
-nsresult
-ChannelEventQueue::ResetDeliveryTarget()
-{
-  MutexAutoLock lock(mMutex);
+    nsCOMPtr<nsIEventTarget> target;
+      target = mEventQueue[0]->GetEventTarget();
+    MOZ_ASSERT(target);
 
-  MOZ_RELEASE_ASSERT(mEventQueue.IsEmpty());
-  MOZ_RELEASE_ASSERT(mSuspendCount == 0);
-  MOZ_RELEASE_ASSERT(!mSuspended);
-  MOZ_RELEASE_ASSERT(!mForced);
-  MOZ_RELEASE_ASSERT(!mFlushing);
-  mTargetThread = nullptr;
-
-  return NS_OK;
+    Unused << NS_WARN_IF(NS_FAILED(target->Dispatch(event.forget(),
+                                                    NS_DISPATCH_NORMAL)));
+  }
 }
 
 } // namespace net
 } // namespace mozilla
--- a/netwerk/ipc/ChannelEventQueue.h
+++ b/netwerk/ipc/ChannelEventQueue.h
@@ -5,31 +5,49 @@
  * 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/. */
 
 #ifndef mozilla_net_ChannelEventQueue_h
 #define mozilla_net_ChannelEventQueue_h
 
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
+#include "nsIEventTarget.h"
+#include "nsThreadUtils.h"
+#include "mozilla/DebugOnly.h"
 #include "mozilla/Mutex.h"
+#include "mozilla/ReentrantMonitor.h"
 #include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
 
 class nsISupports;
-class nsIEventTarget;
 
 namespace mozilla {
 namespace net {
 
 class ChannelEvent
 {
  public:
   ChannelEvent() { MOZ_COUNT_CTOR(ChannelEvent); }
   virtual ~ChannelEvent() { MOZ_COUNT_DTOR(ChannelEvent); }
   virtual void Run() = 0;
+  virtual already_AddRefed<nsIEventTarget> GetEventTarget() = 0;
+};
+
+class MainThreadChannelEvent : public ChannelEvent
+{
+ public:
+  MainThreadChannelEvent() { MOZ_COUNT_CTOR(MainThreadChannelEvent); }
+  virtual ~MainThreadChannelEvent() { MOZ_COUNT_DTOR(MainThreadChannelEvent); }
+
+  already_AddRefed<nsIEventTarget>
+  GetEventTarget() override
+  {
+    return do_GetMainThread();
+  }
 };
 
 // Workaround for Necko re-entrancy dangers. We buffer IPDL messages in a
 // queue if still dispatching previous one(s) to listeners/observers.
 // Otherwise synchronous XMLHttpRequests and/or other code that spins the
 // event loop (ex: IPDL rpc) could cause listener->OnDataAvailable (for
 // instance) to be dispatched and called before mListener->OnStartRequest has
 // completed.
@@ -37,186 +55,238 @@ class ChannelEvent
 class ChannelEventQueue final
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChannelEventQueue)
 
  public:
   explicit ChannelEventQueue(nsISupports *owner)
     : mSuspendCount(0)
     , mSuspended(false)
-    , mForced(false)
+    , mForcedCount(0)
     , mFlushing(false)
     , mOwner(owner)
     , mMutex("ChannelEventQueue::mMutex")
+    , mRunningMonitor("ChannelEventQueue::mRunningMonitor")
   {}
 
   // Puts IPDL-generated channel event into queue, to be run later
   // automatically when EndForcedQueueing and/or Resume is called.
   //
   // @param aCallback - the ChannelEvent
   // @param aAssertionWhenNotQueued - this optional param will be used in an
   //   assertion when the event is executed directly.
   inline void RunOrEnqueue(ChannelEvent* aCallback,
                            bool aAssertionWhenNotQueued = false);
+
+  // Append ChannelEvent in front of the event queue.
+  inline nsresult PrependEvent(UniquePtr<ChannelEvent>& aEvent);
   inline nsresult PrependEvents(nsTArray<UniquePtr<ChannelEvent>>& aEvents);
 
   // After StartForcedQueueing is called, RunOrEnqueue() will start enqueuing
   // events that will be run/flushed when EndForcedQueueing is called.
   // - Note: queueing may still be required after EndForcedQueueing() (if the
   //   queue is suspended, etc):  always call RunOrEnqueue() to avoid race
   //   conditions.
   inline void StartForcedQueueing();
   inline void EndForcedQueueing();
 
   // Suspend/resume event queue.  RunOrEnqueue() will start enqueuing
   // events and they will be run/flushed when resume is called.  These should be
   // called when the channel owning the event queue is suspended/resumed.
-  inline void Suspend();
+  void Suspend();
   // Resume flushes the queue asynchronously, i.e. items in queue will be
   // dispatched in a new event on the current thread.
   void Resume();
 
-  // Retargets delivery of events to the target thread specified.
-  nsresult RetargetDeliveryTo(nsIEventTarget* aTargetThread);
-
-  // Nulls out the delivery target so events are delivered to the main
-  // thread. Should only be called when the queue is known to be empty.
-  // Useful if the queue will be re-used.
-  nsresult ResetDeliveryTarget();
-
  private:
   // Private destructor, to discourage deletion outside of Release():
   ~ChannelEventQueue()
   {
   }
 
+  void SuspendInternal();
+  void ResumeInternal();
+
   inline void MaybeFlushQueue();
   void FlushQueue();
   inline void CompleteResume();
 
   ChannelEvent* TakeEvent();
 
   nsTArray<UniquePtr<ChannelEvent>> mEventQueue;
 
   uint32_t mSuspendCount;
-  bool     mSuspended;
-  bool mForced;
+  bool mSuspended;
+  uint32_t mForcedCount; // Support ForcedQueueing on multiple thread.
   bool mFlushing;
 
   // Keep ptr to avoid refcount cycle: only grab ref during flushing.
   nsISupports *mOwner;
 
+  // For atomic mEventQueue operation and state update
   Mutex mMutex;
 
-  // EventTarget for delivery of events to the correct thread.
-  nsCOMPtr<nsIEventTarget> mTargetThread;
+  // To guarantee event execution order among threads
+  ReentrantMonitor mRunningMonitor;
 
   friend class AutoEventEnqueuer;
 };
 
 inline void
 ChannelEventQueue::RunOrEnqueue(ChannelEvent* aCallback,
                                 bool aAssertionWhenNotQueued)
 {
   MOZ_ASSERT(aCallback);
 
+  // Events execution could be a destruction of the channel (and our own
+  // destructor) unless we make sure its refcount doesn't drop to 0 while this
+  // method is running.
+  nsCOMPtr<nsISupports> kungFuDeathGrip(mOwner);
+  Unused << kungFuDeathGrip; // Not used in this function
+
   // To avoid leaks.
   UniquePtr<ChannelEvent> event(aCallback);
 
+  // To guarantee that the running event and all the events generated within
+  // it will be finished before events on other threads.
+  ReentrantMonitorAutoEnter monitor(mRunningMonitor);
+
   {
     MutexAutoLock lock(mMutex);
 
-    bool enqueue =  mForced || mSuspended || mFlushing;
-    MOZ_ASSERT(enqueue == true || mEventQueue.IsEmpty(),
-               "Should always enqueue if ChannelEventQueue not empty");
+    bool enqueue =  !!mForcedCount || mSuspended || mFlushing || !mEventQueue.IsEmpty();
 
     if (enqueue) {
       mEventQueue.AppendElement(Move(event));
       return;
     }
+
+    nsCOMPtr<nsIEventTarget> target = event->GetEventTarget();
+    MOZ_ASSERT(target);
+
+    bool isCurrentThread = false;
+    DebugOnly<nsresult> rv = target->IsOnCurrentThread(&isCurrentThread);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+    if (!isCurrentThread) {
+      // Leverage Suspend/Resume mechanism to trigger flush procedure without
+      // creating a new one.
+      SuspendInternal();
+      mEventQueue.AppendElement(Move(event));
+      ResumeInternal();
+      return;
+    }
   }
 
   MOZ_RELEASE_ASSERT(!aAssertionWhenNotQueued);
   event->Run();
 }
 
 inline void
 ChannelEventQueue::StartForcedQueueing()
 {
   MutexAutoLock lock(mMutex);
-  mForced = true;
+  ++mForcedCount;
 }
 
 inline void
 ChannelEventQueue::EndForcedQueueing()
 {
+  bool tryFlush = false;
   {
     MutexAutoLock lock(mMutex);
-    mForced = false;
+    MOZ_ASSERT(mForcedCount > 0);
+    if(!--mForcedCount) {
+      tryFlush = true;
+    }
   }
 
-  MaybeFlushQueue();
+  if (tryFlush) {
+    MaybeFlushQueue();
+  }
+}
+
+inline nsresult
+ChannelEventQueue::PrependEvent(UniquePtr<ChannelEvent>& aEvent)
+{
+  MutexAutoLock lock(mMutex);
+
+  // Prepending event while no queue flush foreseen might cause the following
+  // channel events not run. This assertion here guarantee there must be a
+  // queue flush, either triggered by Resume or EndForcedQueueing, to execute
+  // the added event.
+  MOZ_ASSERT(mSuspended || !!mForcedCount);
+
+  UniquePtr<ChannelEvent>* newEvent =
+    mEventQueue.InsertElementAt(0, Move(aEvent));
+
+  if (!newEvent) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  return NS_OK;
 }
 
 inline nsresult
 ChannelEventQueue::PrependEvents(nsTArray<UniquePtr<ChannelEvent>>& aEvents)
 {
   MutexAutoLock lock(mMutex);
 
+  // Prepending event while no queue flush foreseen might cause the following
+  // channel events not run. This assertion here guarantee there must be a
+  // queue flush, either triggered by Resume or EndForcedQueueing, to execute
+  // the added events.
+  MOZ_ASSERT(mSuspended || !!mForcedCount);
+
   UniquePtr<ChannelEvent>* newEvents =
     mEventQueue.InsertElementsAt(0, aEvents.Length());
   if (!newEvents) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   for (uint32_t i = 0; i < aEvents.Length(); i++) {
     newEvents[i] = Move(aEvents[i]);
   }
 
   return NS_OK;
 }
 
 inline void
-ChannelEventQueue::Suspend()
-{
-  MutexAutoLock lock(mMutex);
-
-  mSuspended = true;
-  mSuspendCount++;
-}
-
-inline void
 ChannelEventQueue::CompleteResume()
 {
+  bool tryFlush = false;
   {
     MutexAutoLock lock(mMutex);
 
     // channel may have been suspended again since Resume fired event to call
     // this.
     if (!mSuspendCount) {
       // we need to remain logically suspended (for purposes of queuing incoming
       // messages) until this point, else new incoming messages could run before
       // queued ones.
       mSuspended = false;
+      tryFlush = true;
     }
   }
 
-  MaybeFlushQueue();
+  if (tryFlush) {
+    MaybeFlushQueue();
+  }
 }
 
 inline void
 ChannelEventQueue::MaybeFlushQueue()
 {
   // Don't flush if forced queuing on, we're already being flushed, or
   // suspended, or there's nothing to flush
   bool flushQueue = false;
 
   {
     MutexAutoLock lock(mMutex);
-    flushQueue = !mForced && !mFlushing && !mSuspended &&
+    flushQueue = !mForcedCount && !mFlushing && !mSuspended &&
                  !mEventQueue.IsEmpty();
   }
 
   if (flushQueue) {
     FlushQueue();
   }
 }
 
--- a/netwerk/protocol/ftp/FTPChannelChild.cpp
+++ b/netwerk/protocol/ftp/FTPChannelChild.cpp
@@ -258,16 +258,22 @@ public:
   {
   }
   void Run()
   {
     mChild->DoOnStartRequest(mChannelStatus, mContentLength, mContentType,
                              mLastModified, mEntityID, mURI);
   }
 
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    MOZ_ASSERT(mChild);
+    nsCOMPtr<nsIEventTarget> target = mChild->GetNeckoTarget();
+    return target.forget();
+  }
 private:
   FTPChannelChild* mChild;
   nsresult mChannelStatus;
   int64_t mContentLength;
   nsCString mContentType;
   PRTime mLastModified;
   nsCString mEntityID;
   URIParams mURI;
@@ -364,16 +370,22 @@ public:
     , mCount(aCount)
   {
   }
   void Run()
   {
     mChild->DoOnDataAvailable(mChannelStatus, mData, mOffset, mCount);
   }
 
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    MOZ_ASSERT(mChild);
+    nsCOMPtr<nsIEventTarget> target = mChild->GetNeckoTarget();
+    return target.forget();
+  }
 private:
   FTPChannelChild* mChild;
   nsresult mChannelStatus;
   nsCString mData;
   uint64_t mOffset;
   uint32_t mCount;
 };
 
@@ -407,16 +419,22 @@ class MaybeDivertOnDataFTPEvent : public
   , mOffset(offset)
   , mCount(count) {}
 
   void Run()
   {
     mChild->MaybeDivertOnData(mData, mOffset, mCount);
   }
 
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    MOZ_ASSERT(mChild);
+    nsCOMPtr<nsIEventTarget> target = mChild->GetNeckoTarget();
+    return target.forget();
+  }
  private:
   FTPChannelChild* mChild;
   nsCString mData;
   uint64_t mOffset;
   uint32_t mCount;
 };
 
 void
@@ -493,16 +511,20 @@ public:
     , mUseUTF8(aUseUTF8)
   {
   }
   void Run()
   {
     mChild->DoOnStopRequest(mChannelStatus, mErrorMsg, mUseUTF8);
   }
 
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    return do_GetMainThread();
+  }
 private:
   FTPChannelChild* mChild;
   nsresult mChannelStatus;
   nsCString mErrorMsg;
   bool mUseUTF8;
 };
 
 mozilla::ipc::IPCResult
@@ -554,16 +576,22 @@ class MaybeDivertOnStopFTPEvent : public
   : mChild(child)
   , mChannelStatus(aChannelStatus) {}
 
   void Run()
   {
     mChild->MaybeDivertOnStop(mChannelStatus);
   }
 
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    MOZ_ASSERT(mChild);
+    nsCOMPtr<nsIEventTarget> target = mChild->GetNeckoTarget();
+    return target.forget();
+  }
  private:
   FTPChannelChild* mChild;
   nsresult mChannelStatus;
 };
 
 void
 FTPChannelChild::MaybeDivertOnStop(const nsresult& aChannelStatus)
 {
@@ -641,16 +669,23 @@ FTPChannelChild::DoOnStopRequest(const n
 }
 
 class FTPFailedAsyncOpenEvent : public ChannelEvent
 {
  public:
   FTPFailedAsyncOpenEvent(FTPChannelChild* aChild, nsresult aStatus)
   : mChild(aChild), mStatus(aStatus) {}
   void Run() { mChild->DoFailedAsyncOpen(mStatus); }
+
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    MOZ_ASSERT(mChild);
+    nsCOMPtr<nsIEventTarget> target = mChild->GetNeckoTarget();
+    return target.forget();
+  }
  private:
   FTPChannelChild* mChild;
   nsresult mStatus;
 };
 
 mozilla::ipc::IPCResult
 FTPChannelChild::RecvFailedAsyncOpen(const nsresult& statusCode)
 {
@@ -693,16 +728,23 @@ class FTPFlushedForDiversionEvent : publ
   {
     MOZ_RELEASE_ASSERT(aChild);
   }
 
   void Run()
   {
     mChild->FlushedForDiversion();
   }
+
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    MOZ_ASSERT(mChild);
+    nsCOMPtr<nsIEventTarget> target = mChild->GetNeckoTarget();
+    return target.forget();
+  }
  private:
   FTPChannelChild* mChild;
 };
 
 mozilla::ipc::IPCResult
 FTPChannelChild::RecvFlushedForDiversion()
 {
   LOG(("FTPChannelChild::RecvFlushedForDiversion [this=%p]\n", this));
@@ -742,16 +784,23 @@ FTPChannelChild::RecvDivertMessages()
 }
 
 class FTPDeleteSelfEvent : public ChannelEvent
 {
  public:
   explicit FTPDeleteSelfEvent(FTPChannelChild* aChild)
   : mChild(aChild) {}
   void Run() { mChild->DoDeleteSelf(); }
+
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    MOZ_ASSERT(mChild);
+    nsCOMPtr<nsIEventTarget> target = mChild->GetNeckoTarget();
+    return target.forget();
+  }
  private:
   FTPChannelChild* mChild;
 };
 
 mozilla::ipc::IPCResult
 FTPChannelChild::RecvDeleteSelf()
 {
   mEventQ->RunOrEnqueue(new FTPDeleteSelfEvent(this));
@@ -956,14 +1005,26 @@ FTPChannelChild::EnsureDispatcher()
   mDispatcher = nsContentUtils::GetDispatcherByLoadInfo(loadInfo);
   if (!mDispatcher) {
     return;
   }
 
   nsCOMPtr<nsIEventTarget> target =
     mDispatcher->EventTargetFor(TaskCategory::Network);
   gNeckoChild->SetEventTargetForActor(this, target);
-  mEventQ->RetargetDeliveryTo(target);
+
+  mNeckoTarget = target;
+}
+
+already_AddRefed<nsIEventTarget>
+FTPChannelChild::GetNeckoTarget()
+{
+  nsCOMPtr<nsIEventTarget> target = mNeckoTarget;
+
+  if (!target) {
+    target = do_GetMainThread();
+  }
+  return target.forget();
 }
 
 } // namespace net
 } // namespace mozilla
 
--- a/netwerk/protocol/ftp/FTPChannelChild.h
+++ b/netwerk/protocol/ftp/FTPChannelChild.h
@@ -13,16 +13,17 @@
 #include "mozilla/net/ChannelEventQueue.h"
 #include "nsBaseChannel.h"
 #include "nsIFTPChannel.h"
 #include "nsIUploadChannel.h"
 #include "nsIProxiedChannel.h"
 #include "nsIResumableChannel.h"
 #include "nsIChildChannel.h"
 #include "nsIDivertableChannel.h"
+#include "nsIEventTarget.h"
 
 #include "nsIStreamListener.h"
 #include "PrivateBrowsingChannel.h"
 
 namespace mozilla {
 
 class Dispatcher;
 
@@ -118,19 +119,23 @@ protected:
   void DoDeleteSelf();
 
   friend class FTPStartRequestEvent;
   friend class FTPDataAvailableEvent;
   friend class MaybeDivertOnDataFTPEvent;
   friend class FTPStopRequestEvent;
   friend class MaybeDivertOnStopFTPEvent;
   friend class FTPFailedAsyncOpenEvent;
+  friend class FTPFlushedForDiversionEvent;
   friend class FTPDeleteSelfEvent;
 
 private:
+  // Get event target for processing network events.
+  already_AddRefed<nsIEventTarget> GetNeckoTarget();
+
   nsCOMPtr<nsIInputStream> mUploadStream;
 
   bool mIPCOpen;
   RefPtr<ChannelEventQueue> mEventQ;
 
   // If nsUnknownDecoder is involved we queue onDataAvailable (and possibly
   // OnStopRequest) so that we can divert them if needed when the listener's
   // OnStartRequest is finally called
@@ -149,16 +154,19 @@ private:
   bool mDivertingToParent;
   // Once set, no OnStart/OnData/OnStop callbacks should be received from the
   // parent channel, nor dequeued from the ChannelEventQueue.
   bool mFlushedForDiversion;
   // Set if SendSuspend is called. Determines if SendResume is needed when
   // diverting callbacks to parent.
   bool mSuspendSent;
 
+  // EventTarget for labeling networking events.
+  nsCOMPtr<nsIEventTarget> mNeckoTarget;
+
   RefPtr<Dispatcher> mDispatcher;
 
   void EnsureDispatcher();
 };
 
 inline bool
 FTPChannelChild::IsSuspended()
 {
--- a/netwerk/protocol/ftp/FTPChannelParent.cpp
+++ b/netwerk/protocol/ftp/FTPChannelParent.cpp
@@ -235,17 +235,17 @@ mozilla::ipc::IPCResult
 FTPChannelParent::RecvResume()
 {
   if (mChannel) {
     ResumeChannel();
   }
   return IPC_OK();
 }
 
-class FTPDivertDataAvailableEvent : public ChannelEvent
+class FTPDivertDataAvailableEvent : public MainThreadChannelEvent
 {
 public:
   FTPDivertDataAvailableEvent(FTPChannelParent* aParent,
                               const nsCString& data,
                               const uint64_t& offset,
                               const uint32_t& count)
   : mParent(aParent)
   , mData(data)
@@ -326,17 +326,17 @@ FTPChannelParent::DivertOnDataAvailable(
   if (NS_FAILED(rv)) {
     if (mChannel) {
       mChannel->Cancel(rv);
     }
     mStatus = rv;
   }
 }
 
-class FTPDivertStopRequestEvent : public ChannelEvent
+class FTPDivertStopRequestEvent : public MainThreadChannelEvent
 {
 public:
   FTPDivertStopRequestEvent(FTPChannelParent* aParent,
                             const nsresult& statusCode)
   : mParent(aParent)
   , mStatusCode(statusCode)
   {
   }
@@ -386,17 +386,17 @@ FTPChannelParent::DivertOnStopRequest(co
       forcePendingIChan->ForcePending(false);
     }
   }
 
   AutoEventEnqueuer ensureSerialDispatch(mEventQ);
   OnStopRequest(mChannel, nullptr, status);
 }
 
-class FTPDivertCompleteEvent : public ChannelEvent
+class FTPDivertCompleteEvent : public MainThreadChannelEvent
 {
 public:
   explicit FTPDivertCompleteEvent(FTPChannelParent* aParent)
   : mParent(aParent)
   {
   }
 
   void Run() {
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -175,16 +175,17 @@ HttpChannelChild::HttpChannelChild()
   , mSuspendSent(false)
   , mSynthesizedResponse(false)
   , mShouldInterceptSubsequentRedirect(false)
   , mRedirectingForSubsequentSynthesizedResponse(false)
   , mPostRedirectChannelShouldIntercept(false)
   , mPostRedirectChannelShouldUpgrade(false)
   , mShouldParentIntercept(false)
   , mSuspendParentAfterSynthesizeResponse(false)
+  , mEventTargetMutex("HttpChannelChild::EventTargetMutex")
 {
   LOG(("Creating HttpChannelChild @%p\n", this));
 
   mChannelCreationTime = PR_Now();
   mChannelCreationTimestamp = TimeStamp::Now();
   mAsyncOpenTime = TimeStamp::Now();
   mEventQ = new ChannelEventQueue(static_cast<nsIHttpChannel*>(this));
 }
@@ -288,16 +289,23 @@ class AssociateApplicationCacheEvent : p
     AssociateApplicationCacheEvent(HttpChannelChild* aChild,
                                    const nsCString &aGroupID,
                                    const nsCString &aClientID)
     : mChild(aChild)
     , groupID(aGroupID)
     , clientID(aClientID) {}
 
     void Run() { mChild->AssociateApplicationCache(groupID, clientID); }
+
+    already_AddRefed<nsIEventTarget> GetEventTarget()
+    {
+      MOZ_ASSERT(mChild);
+      nsCOMPtr<nsIEventTarget> target = mChild->GetNeckoTarget();
+      return target.forget();
+    }
   private:
     HttpChannelChild* mChild;
     nsCString groupID;
     nsCString clientID;
 };
 
 mozilla::ipc::IPCResult
 HttpChannelChild::RecvAssociateApplicationCache(const nsCString &groupID,
@@ -362,16 +370,23 @@ class StartRequestEvent : public Channel
   {
     LOG(("StartRequestEvent [this=%p]\n", mChild));
     mChild->OnStartRequest(mChannelStatus, mResponseHead, mUseResponseHead,
                            mRequestHeaders, mIsFromCache, mCacheEntryAvailable,
                            mCacheExpirationTime, mCachedCharset,
                            mSecurityInfoSerialization, mSelfAddr, mPeerAddr,
                            mCacheKey, mAltDataType, mAltDataLen);
   }
+
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    MOZ_ASSERT(mChild);
+    nsCOMPtr<nsIEventTarget> target = mChild->GetNeckoTarget();
+    return target.forget();
+  }
  private:
   HttpChannelChild* mChild;
   nsresult mChannelStatus;
   nsHttpResponseHead mResponseHead;
   nsHttpHeaderArray mRequestHeaders;
   bool mUseResponseHead;
   bool mIsFromCache;
   bool mCacheEntryAvailable;
@@ -621,16 +636,23 @@ class TransportAndDataEvent : public Cha
   , mOffset(offset)
   , mCount(count) {}
 
   void Run()
   {
     mChild->OnTransportAndData(mChannelStatus, mTransportStatus,
                                mOffset, mCount, mData);
   }
+
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    MOZ_ASSERT(mChild);
+    nsCOMPtr<nsIEventTarget> target = mChild->GetNeckoTarget();
+    return target.forget();
+  }
  private:
   HttpChannelChild* mChild;
   nsresult mChannelStatus;
   nsresult mTransportStatus;
   nsCString mData;
   uint64_t mOffset;
   uint32_t mCount;
 };
@@ -665,16 +687,22 @@ class MaybeDivertOnDataHttpEvent : publi
   , mOffset(offset)
   , mCount(count) {}
 
   void Run()
   {
     mChild->MaybeDivertOnData(mData, mOffset, mCount);
   }
 
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    MOZ_ASSERT(mChild);
+    nsCOMPtr<nsIEventTarget> target = mChild->GetNeckoTarget();
+    return target.forget();
+  }
  private:
   HttpChannelChild* mChild;
   nsCString mData;
   uint64_t mOffset;
   uint32_t mCount;
 };
 
 void
@@ -828,16 +856,23 @@ class StopRequestEvent : public ChannelE
   StopRequestEvent(HttpChannelChild* child,
                    const nsresult& channelStatus,
                    const ResourceTimingStruct& timing)
   : mChild(child)
   , mChannelStatus(channelStatus)
   , mTiming(timing) {}
 
   void Run() { mChild->OnStopRequest(mChannelStatus, mTiming); }
+
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    MOZ_ASSERT(mChild);
+    nsCOMPtr<nsIEventTarget> target = mChild->GetNeckoTarget();
+    return target.forget();
+  }
  private:
   HttpChannelChild* mChild;
   nsresult mChannelStatus;
   ResourceTimingStruct mTiming;
 };
 
 mozilla::ipc::IPCResult
 HttpChannelChild::RecvOnStopRequest(const nsresult& channelStatus,
@@ -861,16 +896,22 @@ class MaybeDivertOnStopHttpEvent : publi
   , mChannelStatus(channelStatus)
   {}
 
   void Run()
   {
     mChild->MaybeDivertOnStop(mChannelStatus);
   }
 
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    MOZ_ASSERT(mChild);
+    nsCOMPtr<nsIEventTarget> target = mChild->GetNeckoTarget();
+    return target.forget();
+  }
  private:
   HttpChannelChild* mChild;
   nsresult mChannelStatus;
 };
 
 void
 HttpChannelChild::MaybeDivertOnStop(const nsresult& aChannelStatus)
 {
@@ -1031,16 +1072,23 @@ class ProgressEvent : public ChannelEven
   ProgressEvent(HttpChannelChild* child,
                 const int64_t& progress,
                 const int64_t& progressMax)
   : mChild(child)
   , mProgress(progress)
   , mProgressMax(progressMax) {}
 
   void Run() { mChild->OnProgress(mProgress, mProgressMax); }
+
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    MOZ_ASSERT(mChild);
+    nsCOMPtr<nsIEventTarget> target = mChild->GetNeckoTarget();
+    return target.forget();
+  }
  private:
   HttpChannelChild* mChild;
   int64_t mProgress, mProgressMax;
 };
 
 mozilla::ipc::IPCResult
 HttpChannelChild::RecvOnProgress(const int64_t& progress,
                                  const int64_t& progressMax)
@@ -1079,16 +1127,23 @@ class StatusEvent : public ChannelEvent
 {
  public:
   StatusEvent(HttpChannelChild* child,
               const nsresult& status)
   : mChild(child)
   , mStatus(status) {}
 
   void Run() { mChild->OnStatus(mStatus); }
+
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    MOZ_ASSERT(mChild);
+    nsCOMPtr<nsIEventTarget> target = mChild->GetNeckoTarget();
+    return target.forget();
+  }
  private:
   HttpChannelChild* mChild;
   nsresult mStatus;
 };
 
 mozilla::ipc::IPCResult
 HttpChannelChild::RecvOnStatus(const nsresult& status)
 {
@@ -1126,16 +1181,23 @@ HttpChannelChild::OnStatus(const nsresul
 class FailedAsyncOpenEvent : public ChannelEvent
 {
  public:
   FailedAsyncOpenEvent(HttpChannelChild* child, const nsresult& status)
   : mChild(child)
   , mStatus(status) {}
 
   void Run() { mChild->FailedAsyncOpen(mStatus); }
+
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    MOZ_ASSERT(mChild);
+    nsCOMPtr<nsIEventTarget> target = mChild->GetNeckoTarget();
+    return target.forget();
+  }
  private:
   HttpChannelChild* mChild;
   nsresult mStatus;
 };
 
 mozilla::ipc::IPCResult
 HttpChannelChild::RecvFailedAsyncOpen(const nsresult& status)
 {
@@ -1180,16 +1242,23 @@ HttpChannelChild::DoNotifyListenerCleanu
   }
 }
 
 class DeleteSelfEvent : public ChannelEvent
 {
  public:
   explicit DeleteSelfEvent(HttpChannelChild* child) : mChild(child) {}
   void Run() { mChild->DeleteSelf(); }
+
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    MOZ_ASSERT(mChild);
+    nsCOMPtr<nsIEventTarget> target = mChild->GetNeckoTarget();
+    return target.forget();
+  }
  private:
   HttpChannelChild* mChild;
 };
 
 mozilla::ipc::IPCResult
 HttpChannelChild::RecvDeleteSelf()
 {
   LOG(("HttpChannelChild::RecvDeleteSelf [this=%p]\n", this));
@@ -1237,19 +1306,22 @@ HttpChannelChild::OverrideRunnable::Run(
 
 mozilla::ipc::IPCResult
 HttpChannelChild::RecvFinishInterceptedRedirect()
 {
   // Hold a ref to this to keep it from being deleted by Send__delete__()
   RefPtr<HttpChannelChild> self(this);
   Send__delete__(this);
 
-  // Reset the event target since the IPC actor is about to be destroyed.
-  // Following channel event should be handled on main thread.
-  mEventQ->ResetDeliveryTarget();
+  {
+    // Reset the event target since the IPC actor is about to be destroyed.
+    // Following channel event should be handled on main thread.
+    MutexAutoLock lock(mEventTargetMutex);
+    mNeckoTarget = nullptr;
+  }
 
   // The IPDL connection was torn down by a interception logic in
   // CompleteRedirectSetup, and we need to call FinishInterceptedRedirect.
   NS_DispatchToMainThread(NewRunnableMethod(this, &HttpChannelChild::FinishInterceptedRedirect));
 
   return IPC_OK();
 }
 
@@ -1310,16 +1382,23 @@ class Redirect1Event : public ChannelEve
   , mChannelId(channelId) {}
 
   void Run()
   {
     mChild->Redirect1Begin(mRegistrarId, mNewURI, mRedirectFlags,
                            mResponseHead, mSecurityInfoSerialization,
                            mChannelId);
   }
+
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    MOZ_ASSERT(mChild);
+    nsCOMPtr<nsIEventTarget> target = mChild->GetNeckoTarget();
+    return target.forget();
+  }
  private:
   HttpChannelChild*   mChild;
   uint32_t            mRegistrarId;
   URIParams           mNewURI;
   uint32_t            mRedirectFlags;
   nsHttpResponseHead  mResponseHead;
   nsCString           mSecurityInfoSerialization;
   nsCString           mChannelId;
@@ -1483,16 +1562,23 @@ HttpChannelChild::OverrideSecurityInfoFo
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 }
 
 class Redirect3Event : public ChannelEvent
 {
  public:
   explicit Redirect3Event(HttpChannelChild* child) : mChild(child) {}
   void Run() { mChild->Redirect3Complete(nullptr); }
+
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    MOZ_ASSERT(mChild);
+    nsCOMPtr<nsIEventTarget> target = mChild->GetNeckoTarget();
+    return target.forget();
+  }
  private:
   HttpChannelChild* mChild;
 };
 
 mozilla::ipc::IPCResult
 HttpChannelChild::RecvRedirect3Complete()
 {
   LOG(("HttpChannelChild::RecvRedirect3Complete [this=%p]\n", this));
@@ -1508,16 +1594,23 @@ class HttpFlushedForDiversionEvent : pub
   {
     MOZ_RELEASE_ASSERT(aChild);
   }
 
   void Run()
   {
     mChild->FlushedForDiversion();
   }
+
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    MOZ_ASSERT(mChild);
+    nsCOMPtr<nsIEventTarget> target = mChild->GetNeckoTarget();
+    return target.forget();
+  }
  private:
   HttpChannelChild* mChild;
 };
 
 mozilla::ipc::IPCResult
 HttpChannelChild::RecvFlushedForDiversion()
 {
   LOG(("HttpChannelChild::RecvFlushedForDiversion [this=%p]\n", this));
@@ -2101,17 +2194,36 @@ HttpChannelChild::SetEventTarget()
     GetIsMainDocumentChannel(&isMainDocumentChannel);
     MOZ_ASSERT(isMainDocumentChannel);
   }
 #endif
 
   nsCOMPtr<nsIEventTarget> target =
     dispatcher->EventTargetFor(TaskCategory::Network);
   gNeckoChild->SetEventTargetForActor(this, target);
-  mEventQ->RetargetDeliveryTo(target);
+
+  {
+    MutexAutoLock lock(mEventTargetMutex);
+    mNeckoTarget = target;
+  }
+}
+
+already_AddRefed<nsIEventTarget>
+HttpChannelChild::GetNeckoTarget()
+{
+  nsCOMPtr<nsIEventTarget> target;
+  {
+    MutexAutoLock lock(mEventTargetMutex);
+    target = mNeckoTarget;
+  }
+
+  if (!target) {
+    target = do_GetMainThread();
+  }
+  return target.forget();
 }
 
 nsresult
 HttpChannelChild::ContinueAsyncOpen()
 {
   nsCString appCacheClientId;
   if (mInheritApplicationCache) {
     // Pick up an application cache from the notification
--- a/netwerk/protocol/http/HttpChannelChild.h
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -3,16 +3,17 @@
 
 /* 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/. */
 
 #ifndef mozilla_net_HttpChannelChild_h
 #define mozilla_net_HttpChannelChild_h
 
+#include "mozilla/Mutex.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/net/HttpBaseChannel.h"
 #include "mozilla/net/PHttpChannelChild.h"
 #include "mozilla/net/ChannelEventQueue.h"
 
 #include "nsIStreamListener.h"
 #include "nsILoadGroup.h"
 #include "nsIInterfaceRequestor.h"
@@ -192,16 +193,19 @@ private:
   };
 
   // Sets the event target for future IPC messages. Messages will either be
   // directed to the TabGroup or DocGroup, depending on the LoadInfo associated
   // with the channel. Should be called when a new channel is being set up,
   // before the constructor message is sent to the parent.
   void SetEventTarget();
 
+  // Get event target for processing network events.
+  already_AddRefed<nsIEventTarget> GetNeckoTarget();
+
   MOZ_MUST_USE nsresult ContinueAsyncOpen();
 
   void DoOnStartRequest(nsIRequest* aRequest, nsISupports* aContext);
   void DoOnStatus(nsIRequest* aRequest, nsresult status);
   void DoOnProgress(nsIRequest* aRequest, int64_t progress, int64_t progressMax);
   void DoOnDataAvailable(nsIRequest* aRequest, nsISupports* aContext, nsIInputStream* aStream,
                          uint64_t offset, uint32_t count);
   void DoPreOnStopRequest(nsresult aStatus);
@@ -290,16 +294,21 @@ private:
   // Needed to call AsyncOpen in FinishInterceptedRedirect
   nsCOMPtr<nsIStreamListener> mInterceptedRedirectListener;
   nsCOMPtr<nsISupports> mInterceptedRedirectContext;
   // Needed to call CleanupRedirectingChannel in FinishInterceptedRedirect
   RefPtr<HttpChannelChild> mInterceptingChannel;
   // Used to call OverrideWithSynthesizedResponse in FinishInterceptedRedirect
   RefPtr<OverrideRunnable> mOverrideRunnable;
 
+  // EventTarget for labeling networking events.
+  nsCOMPtr<nsIEventTarget> mNeckoTarget;
+  // Used to ensure atomicity of mNeckoTarget;
+  Mutex mEventTargetMutex;
+
   void FinishInterceptedRedirect();
   void CleanupRedirectingChannel(nsresult rv);
 
   // true after successful AsyncOpen until OnStopRequest completes.
   bool RemoteChannelExists() { return mIPCOpen && !mKeptAlive; }
 
   void AssociateApplicationCache(const nsCString &groupID,
                                  const nsCString &clientID);
@@ -361,16 +370,17 @@ private:
   friend class MaybeDivertOnDataHttpEvent;
   friend class MaybeDivertOnStopHttpEvent;
   friend class ProgressEvent;
   friend class StatusEvent;
   friend class FailedAsyncOpenEvent;
   friend class Redirect1Event;
   friend class Redirect3Event;
   friend class DeleteSelfEvent;
+  friend class HttpFlushedForDiversionEvent;
   friend class HttpAsyncAborter<HttpChannelChild>;
   friend class InterceptStreamListener;
   friend class InterceptedChannelContent;
 };
 
 // A stream listener interposed between the nsInputStreamPump used for intercepted channels
 // and this channel's original listener. This is only used to ensure the original listener
 // sees the channel as the request object, and to synthesize OnStatus and OnProgress notifications.
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -833,17 +833,17 @@ HttpChannelParent::RecvMarkOfflineCacheE
   if (mOfflineForeignMarker) {
     mOfflineForeignMarker->MarkAsForeign();
     mOfflineForeignMarker = 0;
   }
 
   return IPC_OK();
 }
 
-class DivertDataAvailableEvent : public ChannelEvent
+class DivertDataAvailableEvent : public MainThreadChannelEvent
 {
 public:
   DivertDataAvailableEvent(HttpChannelParent* aParent,
                            const nsCString& data,
                            const uint64_t& offset,
                            const uint32_t& count)
   : mParent(aParent)
   , mData(data)
@@ -928,17 +928,17 @@ HttpChannelParent::DivertOnDataAvailable
   if (NS_FAILED(rv)) {
     if (mChannel) {
       mChannel->Cancel(rv);
     }
     mStatus = rv;
   }
 }
 
-class DivertStopRequestEvent : public ChannelEvent
+class DivertStopRequestEvent : public MainThreadChannelEvent
 {
 public:
   DivertStopRequestEvent(HttpChannelParent* aParent,
                          const nsresult& statusCode)
   : mParent(aParent)
   , mStatusCode(statusCode)
   {
   }
@@ -989,17 +989,17 @@ HttpChannelParent::DivertOnStopRequest(c
   if (mChannel) {
     mChannel->ForcePending(false);
   }
 
   AutoEventEnqueuer ensureSerialDispatch(mEventQ);
   mParentListener->OnStopRequest(mChannel, nullptr, status);
 }
 
-class DivertCompleteEvent : public ChannelEvent
+class DivertCompleteEvent : public MainThreadChannelEvent
 {
 public:
   explicit DivertCompleteEvent(HttpChannelParent* aParent)
   : mParent(aParent)
   {
   }
 
   void Run() {
--- a/netwerk/protocol/websocket/WebSocketChannelChild.cpp
+++ b/netwerk/protocol/websocket/WebSocketChannelChild.cpp
@@ -171,16 +171,25 @@ public:
       mEventTarget->Dispatch(new WrappedChannelEvent(mChannelEvent.forget()),
                              NS_DISPATCH_NORMAL);
       return;
     }
 
     mChannelEvent->Run();
   }
 
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    nsCOMPtr<nsIEventTarget> target = mEventTarget;
+    if (!target) {
+      target = do_GetMainThread();
+    }
+    return target.forget();
+  }
+
 private:
   nsAutoPtr<ChannelEvent> mChannelEvent;
   nsCOMPtr<nsIEventTarget> mEventTarget;
 };
 
 class StartEvent : public ChannelEvent
 {
  public:
@@ -195,16 +204,23 @@ class StartEvent : public ChannelEvent
   , mEffectiveURL(aEffectiveURL)
   , mEncrypted(aEncrypted)
   {}
 
   void Run()
   {
     mChild->OnStart(mProtocol, mExtensions, mEffectiveURL, mEncrypted);
   }
+
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    nsCOMPtr<nsIEventTarget> target = do_GetCurrentThread();
+    return target.forget();
+  }
+
  private:
   RefPtr<WebSocketChannelChild> mChild;
   nsCString mProtocol;
   nsCString mExtensions;
   nsString mEffectiveURL;
   bool mEncrypted;
 };
 
@@ -253,16 +269,23 @@ class StopEvent : public ChannelEvent
   : mChild(aChild)
   , mStatusCode(aStatusCode)
   {}
 
   void Run()
   {
     mChild->OnStop(mStatusCode);
   }
+
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    nsCOMPtr<nsIEventTarget> target = do_GetCurrentThread();
+    return target.forget();
+  }
+
  private:
   RefPtr<WebSocketChannelChild> mChild;
   nsresult mStatusCode;
 };
 
 mozilla::ipc::IPCResult
 WebSocketChannelChild::RecvOnStop(const nsresult& aStatusCode)
 {
@@ -303,16 +326,23 @@ class MessageEvent : public ChannelEvent
   void Run()
   {
     if (!mBinary) {
       mChild->OnMessageAvailable(mMessage);
     } else {
       mChild->OnBinaryMessageAvailable(mMessage);
     }
   }
+
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    nsCOMPtr<nsIEventTarget> target = do_GetCurrentThread();
+    return target.forget();
+  }
+
  private:
   RefPtr<WebSocketChannelChild> mChild;
   nsCString mMessage;
   bool mBinary;
 };
 
 mozilla::ipc::IPCResult
 WebSocketChannelChild::RecvOnMessageAvailable(const nsCString& aMsg)
@@ -375,16 +405,23 @@ class AcknowledgeEvent : public ChannelE
   : mChild(aChild)
   , mSize(aSize)
   {}
 
   void Run()
   {
     mChild->OnAcknowledge(mSize);
   }
+
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    nsCOMPtr<nsIEventTarget> target = do_GetCurrentThread();
+    return target.forget();
+  }
+
  private:
   RefPtr<WebSocketChannelChild> mChild;
   uint32_t mSize;
 };
 
 mozilla::ipc::IPCResult
 WebSocketChannelChild::RecvOnAcknowledge(const uint32_t& aSize)
 {
@@ -421,16 +458,23 @@ class ServerCloseEvent : public ChannelE
   , mCode(aCode)
   , mReason(aReason)
   {}
 
   void Run()
   {
     mChild->OnServerClose(mCode, mReason);
   }
+
+  already_AddRefed<nsIEventTarget> GetEventTarget()
+  {
+    nsCOMPtr<nsIEventTarget> target = do_GetCurrentThread();
+    return target.forget();
+  }
+
  private:
   RefPtr<WebSocketChannelChild> mChild;
   uint16_t mCode;
   nsCString mReason;
 };
 
 mozilla::ipc::IPCResult
 WebSocketChannelChild::RecvOnServerClose(const uint16_t& aCode,
@@ -716,29 +760,16 @@ WebSocketChannelChild::SendBinaryStream(
 
 NS_IMETHODIMP
 WebSocketChannelChild::GetSecurityInfo(nsISupports **aSecurityInfo)
 {
   LOG(("WebSocketChannelChild::GetSecurityInfo() %p\n", this));
   return NS_ERROR_NOT_AVAILABLE;
 }
 
-//-----------------------------------------------------------------------------
-// WebSocketChannelChild::nsIThreadRetargetableRequest
-//-----------------------------------------------------------------------------
-
-NS_IMETHODIMP
-WebSocketChannelChild::RetargetDeliveryTo(nsIEventTarget* aTargetThread)
-{
-  nsresult rv = BaseWebSocketChannel::RetargetDeliveryTo(aTargetThread);
-  MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
-
-  return mEventQ->RetargetDeliveryTo(aTargetThread);
-}
-
 bool
 WebSocketChannelChild::IsOnTargetThread()
 {
   MOZ_ASSERT(mTargetThread);
   bool isOnTargetThread = false;
   nsresult rv = mTargetThread->IsOnCurrentThread(&isOnTargetThread);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
   return NS_FAILED(rv) ? false : isOnTargetThread;
--- a/netwerk/protocol/websocket/WebSocketChannelChild.h
+++ b/netwerk/protocol/websocket/WebSocketChannelChild.h
@@ -20,17 +20,16 @@ class ChannelEventQueue;
 
 class WebSocketChannelChild final : public BaseWebSocketChannel,
                                     public PWebSocketChild
 {
  public:
   explicit WebSocketChannelChild(bool aSecure);
 
   NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSITHREADRETARGETABLEREQUEST
 
   // nsIWebSocketChannel methods BaseWebSocketChannel didn't implement for us
   //
   NS_IMETHOD AsyncOpen(nsIURI *aURI, const nsACString &aOrigin,
                        uint64_t aInnerWindowID,
                        nsIWebSocketListener *aListener,
                        nsISupports *aContext) override;
   NS_IMETHOD Close(uint16_t code, const nsACString & reason) override;
--- a/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp
+++ b/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp
@@ -125,17 +125,17 @@ WyciwygChannelChild::Init(nsIURI* uri)
            policyType);
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // WyciwygChannelChild::PWyciwygChannelChild
 //-----------------------------------------------------------------------------
 
-class WyciwygStartRequestEvent : public ChannelEvent
+class WyciwygStartRequestEvent : public MainThreadChannelEvent
 {
 public:
   WyciwygStartRequestEvent(WyciwygChannelChild* child,
                            const nsresult& statusCode,
                            const int64_t& contentLength,
                            const int32_t& source,
                            const nsCString& charset,
                            const nsCString& securityInfo)
@@ -187,17 +187,17 @@ WyciwygChannelChild::OnStartRequest(cons
 
   AutoEventEnqueuer ensureSerialDispatch(mEventQ);
 
   nsresult rv = mListener->OnStartRequest(this, mListenerContext);
   if (NS_FAILED(rv))
     Cancel(rv);
 }
 
-class WyciwygDataAvailableEvent : public ChannelEvent
+class WyciwygDataAvailableEvent : public MainThreadChannelEvent
 {
 public:
   WyciwygDataAvailableEvent(WyciwygChannelChild* child,
                             const nsCString& data,
                             const uint64_t& offset)
   : mChild(child), mData(data), mOffset(offset) {}
   void Run() { mChild->OnDataAvailable(mData, mOffset); }
 private:
@@ -248,17 +248,17 @@ WyciwygChannelChild::OnDataAvailable(con
     Cancel(rv);
 
   if (mProgressSink && NS_SUCCEEDED(rv)) {
     mProgressSink->OnProgress(this, nullptr, offset + data.Length(),
                               mContentLength);
   }
 }
 
-class WyciwygStopRequestEvent : public ChannelEvent
+class WyciwygStopRequestEvent : public MainThreadChannelEvent
 {
 public:
   WyciwygStopRequestEvent(WyciwygChannelChild* child,
                           const nsresult& statusCode)
   : mChild(child), mStatusCode(statusCode) {}
   void Run() { mChild->OnStopRequest(mStatusCode); }
 private:
   WyciwygChannelChild* mChild;
@@ -300,17 +300,17 @@ WyciwygChannelChild::OnStopRequest(const
     mCallbacks = nullptr;
     mProgressSink = nullptr;
   }
 
   if (mIPCOpen)
     PWyciwygChannelChild::Send__delete__(this);
 }
 
-class WyciwygCancelEvent : public ChannelEvent
+class WyciwygCancelEvent : public MainThreadChannelEvent
 {
  public:
   WyciwygCancelEvent(WyciwygChannelChild* child, const nsresult& status)
   : mChild(child)
   , mStatus(status) {}
 
   void Run() { mChild->CancelEarly(mStatus); }
  private: