Bug 1382922 - Refactor event queue to allow multiple implementations (r=erahm)
authorBill McCloskey <billm@mozilla.com>
Tue, 20 Jun 2017 19:42:13 -0700
changeset 375188 36ef70762b74b3c6b8bd0f26c57ab4b54467f64b
parent 375187 ca6618d0bc1745e290a176bb86f1c7a09bba0948
child 375190 fcd32d51e2a84d8b2e5df5186bd63a1e7935b504
push id93848
push userwmccloskey@mozilla.com
push dateThu, 17 Aug 2017 03:56:01 +0000
treeherdermozilla-inbound@36ef70762b74 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerserahm
bugs1382922
milestone57.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 1382922 - Refactor event queue to allow multiple implementations (r=erahm) This patch refactors the nsThread event queue to clean it up and to make it easier to restructure. The fundamental concepts are as follows: Each nsThread will have a pointer to a refcounted SynchronizedEventQueue. A SynchronizedEQ takes care of doing the locking and condition variable work when posting and popping events. For the actual storage of events, it delegates to an AbstractEventQueue data structure. It keeps a UniquePtr to the AbstractEventQueue that it uses for storage. Both SynchronizedEQ and AbstractEventQueue are abstract classes. There is only one concrete implementation of SynchronizedEQ in this patch, which is called ThreadEventQueue. ThreadEventQueue uses locks and condition variables to post and pop events the same way nsThread does. It also encapsulates the functionality that DOM workers need to implement their special event loops (PushEventQueue and PopEventQueue). In later Quantum DOM work, I plan to have another SynchronizedEQ implementation for the main thread, called SchedulerEventQueue. It will have special code for the cooperatively scheduling threads in Quantum DOM. There are two concrete implementations of AbstractEventQueue in this patch: EventQueue and PrioritizedEventQueue. EventQueue replaces the old nsEventQueue. The other AbstractEventQueue implementation is PrioritizedEventQueue, which uses multiple queues for different event priorities. The final major piece here is ThreadEventTarget, which splits some of the code for posting events out of nsThread. Eventually, my plan is for multiple cooperatively scheduled nsThreads to be able to share a ThreadEventTarget. In this patch, though, each nsThread has its own ThreadEventTarget. The class's purpose is just to collect some related code together. One final note: I tried to avoid virtual dispatch overhead as much as possible. Calls to SynchronizedEQ methods do use virtual dispatch, since I plan to use different implementations for different threads with Quantum DOM. But all the calls to EventQueue methods should be non-virtual. Although the methods are declared virtual, all the classes used are final and the concrete classes involved should all be known through templatization. MozReview-Commit-ID: 9Evtr9oIJvx
dom/ipc/TabChild.cpp
dom/workers/WorkerPrivate.cpp
dom/workers/WorkerThread.cpp
dom/workers/WorkerThread.h
netwerk/base/nsSocketTransportService2.h
widget/android/AndroidUiThread.cpp
xpcom/tests/gtest/TestEventPriorities.cpp
xpcom/tests/gtest/TestEventTargetQI.cpp
xpcom/tests/gtest/TestThreadUtils.cpp
xpcom/tests/gtest/moz.build
xpcom/threads/AbstractEventQueue.h
xpcom/threads/EventQueue.cpp
xpcom/threads/EventQueue.h
xpcom/threads/LazyIdleThread.cpp
xpcom/threads/PrioritizedEventQueue.cpp
xpcom/threads/PrioritizedEventQueue.h
xpcom/threads/Queue.h
xpcom/threads/SynchronizedEventQueue.h
xpcom/threads/ThreadEventQueue.cpp
xpcom/threads/ThreadEventQueue.h
xpcom/threads/ThreadEventTarget.cpp
xpcom/threads/ThreadEventTarget.h
xpcom/threads/ThrottledEventQueue.cpp
xpcom/threads/moz.build
xpcom/threads/nsEventQueue.cpp
xpcom/threads/nsEventQueue.h
xpcom/threads/nsIThread.idl
xpcom/threads/nsIThreadInternal.idl
xpcom/threads/nsThread.cpp
xpcom/threads/nsThread.h
xpcom/threads/nsThreadManager.cpp
xpcom/threads/nsThreadManager.h
xpcom/threads/nsThreadPool.cpp
xpcom/threads/nsThreadPool.h
xpcom/threads/nsThreadSyncDispatch.h
xpcom/threads/nsTimerImpl.cpp
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -293,63 +293,81 @@ ContentListener::HandleEvent(nsIDOMEvent
   remoteEvent.mEvent = do_QueryInterface(aEvent);
   NS_ENSURE_STATE(remoteEvent.mEvent);
   mTabChild->SendEvent(remoteEvent);
   return NS_OK;
 }
 
 class TabChild::DelayedDeleteRunnable final
   : public Runnable
+  , public nsIRunnablePriority
 {
     RefPtr<TabChild> mTabChild;
 
+    // In order to ensure that this runnable runs after everything that could
+    // possibly touch this tab, we send it through the event queue twice. The
+    // first time it runs at normal priority and the second time it runs at
+    // input priority. This ensures that it runs after all events that were in
+    // either queue at the time it was first dispatched. mReadyToDelete starts
+    // out false (when it runs at normal priority) and is then set to true.
+    bool mReadyToDelete = false;
+
 public:
     explicit DelayedDeleteRunnable(TabChild* aTabChild)
       : Runnable("TabChild::DelayedDeleteRunnable")
       , mTabChild(aTabChild)
     {
         MOZ_ASSERT(NS_IsMainThread());
         MOZ_ASSERT(aTabChild);
     }
 
+    NS_DECL_ISUPPORTS_INHERITED
+
 private:
     ~DelayedDeleteRunnable()
     {
         MOZ_ASSERT(NS_IsMainThread());
         MOZ_ASSERT(!mTabChild);
     }
 
+    NS_IMETHOD GetPriority(uint32_t* aPriority) override
+    {
+      *aPriority = mReadyToDelete
+                 ? nsIRunnablePriority::PRIORITY_INPUT
+                 : nsIRunnablePriority::PRIORITY_NORMAL;
+      return NS_OK;
+    }
+
     NS_IMETHOD
     Run() override
     {
         MOZ_ASSERT(NS_IsMainThread());
         MOZ_ASSERT(mTabChild);
-        // When enabling input event prioritization, we reserve limited time
-        // to process input events. We may handle the rest in the next frame
-        // when running out of time of the current frame. In that case, input
-        // events may be dispatched after ActorDestroy. Delay
-        // DelayedDeleteRunnable to avoid it to happen.
-        nsThread* thread = nsThreadManager::get().GetCurrentThread();
-        MOZ_ASSERT(thread);
-        bool eventPrioritizationEnabled = false;
-        thread->IsEventPrioritizationEnabled(&eventPrioritizationEnabled);
-        if (eventPrioritizationEnabled && thread->HasPendingInputEvents()) {
+
+        if (!mReadyToDelete) {
+          // This time run this runnable at input priority.
+          mReadyToDelete = true;
           MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this));
           return NS_OK;
         }
+
         // Check in case ActorDestroy was called after RecvDestroy message.
         if (mTabChild->IPCOpen()) {
-            Unused << PBrowserChild::Send__delete__(mTabChild);
+          Unused << PBrowserChild::Send__delete__(mTabChild);
         }
 
         mTabChild = nullptr;
         return NS_OK;
     }
 };
 
+NS_IMPL_ISUPPORTS_INHERITED(TabChild::DelayedDeleteRunnable,
+                            Runnable,
+                            nsIRunnablePriority)
+
 namespace {
 std::map<TabId, RefPtr<TabChild>>&
 NestedTabChildMap()
 {
   MOZ_ASSERT(NS_IsMainThread());
   static std::map<TabId, RefPtr<TabChild>> sNestedTabChildMap;
   return sNestedTabChildMap;
 }
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -68,16 +68,17 @@
 #include "mozilla/dom/SimpleGlobalObject.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/StructuredCloneHolder.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/WorkerBinding.h"
 #include "mozilla/dom/WorkerDebuggerGlobalScopeBinding.h"
 #include "mozilla/dom/WorkerGlobalScopeBinding.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/ThreadEventQueue.h"
 #include "mozilla/ThrottledEventQueue.h"
 #include "mozilla/TimelineConsumers.h"
 #include "mozilla/WorkerTimelineMarker.h"
 #include "nsAlgorithm.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollector.h"
 #include "nsError.h"
 #include "nsDOMJSUtils.h"
@@ -5831,21 +5832,19 @@ WorkerPrivate::CreateNewSyncLoop(Status 
   {
     MutexAutoLock lock(mMutex);
 
     if (mStatus >= aFailStatus) {
       return nullptr;
     }
   }
 
-  nsCOMPtr<nsIThreadInternal> thread = do_QueryInterface(NS_GetCurrentThread());
-  MOZ_ASSERT(thread);
-
-  nsCOMPtr<nsIEventTarget> realEventTarget;
-  MOZ_ALWAYS_SUCCEEDS(thread->PushEventQueue(getter_AddRefs(realEventTarget)));
+  auto queue = static_cast<ThreadEventQueue<EventQueue>*>(mThread->EventQueue());
+  nsCOMPtr<nsISerialEventTarget> realEventTarget = queue->PushEventQueue();
+  MOZ_ASSERT(realEventTarget);
 
   RefPtr<EventTarget> workerEventTarget =
     new EventTarget(this, realEventTarget);
 
   {
     // Modifications must be protected by mMutex in DEBUG builds, see comment
     // about mSyncLoopStack in WorkerPrivate.h.
 #ifdef DEBUG
@@ -5966,17 +5965,18 @@ WorkerPrivate::DestroySyncLoop(uint32_t 
 #ifdef DEBUG
     MutexAutoLock lock(mMutex);
 #endif
 
     // This will delete |loopInfo|!
     mSyncLoopStack.RemoveElementAt(aLoopIndex);
   }
 
-  MOZ_ALWAYS_SUCCEEDS(aThread->PopEventQueue(nestedEventTarget));
+  auto queue = static_cast<ThreadEventQueue<EventQueue>*>(mThread->EventQueue());
+  queue->PopEventQueue(nestedEventTarget);
 
   if (mSyncLoopStack.IsEmpty() && mPendingEventQueueClearing) {
     mPendingEventQueueClearing = false;
     ClearMainEventQueue(WorkerRan);
   }
 
   return result;
 }
--- a/dom/workers/WorkerThread.cpp
+++ b/dom/workers/WorkerThread.cpp
@@ -3,16 +3,18 @@
 /* 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 "WorkerThread.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/ipc/BackgroundChild.h"
+#include "EventQueue.h"
+#include "mozilla/ThreadEventQueue.h"
 #include "nsIThreadInternal.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 
 #ifdef DEBUG
 #include "nsThreadManager.h"
 #endif
 
@@ -60,17 +62,20 @@ private:
   {
     mWorkerPrivate->AssertIsOnWorkerThread();
   }
 
   NS_DECL_NSITHREADOBSERVER
 };
 
 WorkerThread::WorkerThread()
-  : nsThread(nsThread::NOT_MAIN_THREAD, kWorkerStackSize)
+  : nsThread(WrapNotNull(new ThreadEventQueue<mozilla::EventQueue>(
+                           MakeUnique<mozilla::EventQueue>())),
+             nsThread::NOT_MAIN_THREAD,
+             kWorkerStackSize)
   , mLock("WorkerThread::mLock")
   , mWorkerPrivateCondVar(mLock, "WorkerThread::mWorkerPrivateCondVar")
   , mWorkerPrivate(nullptr)
   , mOtherThreadsDispatchingViaEventTarget(0)
 #ifdef DEBUG
   , mAcceptingNonWorkerRunnables(true)
 #endif
 {
--- a/dom/workers/WorkerThread.h
+++ b/dom/workers/WorkerThread.h
@@ -73,24 +73,16 @@ public:
 
   nsresult
   DispatchAnyThread(const WorkerThreadFriendKey& aKey,
            already_AddRefed<WorkerRunnable> aWorkerRunnable);
 
   uint32_t
   RecursionDepth(const WorkerThreadFriendKey& aKey) const;
 
-  // Required for MinGW build #1336527 to handle compiler bug:
-  // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79582
-  NS_IMETHOD
-  RegisterIdlePeriod(already_AddRefed<nsIIdlePeriod> aIdlePeriod) override
-  {
-    return nsThread::RegisterIdlePeriod(already_AddRefed<nsIIdlePeriod>(aIdlePeriod.take()));
-  }
-
   NS_DECL_ISUPPORTS_INHERITED
 
 private:
   WorkerThread();
   ~WorkerThread();
 
   // This should only be called by consumers that have an
   // nsIEventTarget/nsIThread pointer.
--- a/netwerk/base/nsSocketTransportService2.h
+++ b/netwerk/base/nsSocketTransportService2.h
@@ -4,17 +4,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsSocketTransportService2_h__
 #define nsSocketTransportService2_h__
 
 #include "nsPISocketTransportService.h"
 #include "nsIThreadInternal.h"
 #include "nsIRunnable.h"
-#include "nsEventQueue.h"
 #include "nsCOMPtr.h"
 #include "prinrval.h"
 #include "mozilla/Logging.h"
 #include "prinit.h"
 #include "nsIObserver.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/net/DashboardTypes.h"
--- a/widget/android/AndroidUiThread.cpp
+++ b/widget/android/AndroidUiThread.cpp
@@ -1,22 +1,25 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "base/message_loop.h"
 #include "GeneratedJNIWrappers.h"
 #include "mozilla/Atomics.h"
+#include "mozilla/EventQueue.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/StaticPtr.h"
+#include "mozilla/ThreadEventQueue.h"
 #include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
 #include "nsThread.h"
 #include "nsThreadManager.h"
 #include "nsThreadUtils.h"
 
 using namespace mozilla;
 
 namespace {
 
@@ -45,17 +48,19 @@ void EnqueueTask(already_AddRefed<nsIRun
  * event loop in the Android UI thread and has no knowledge of when the nsThread
  * needs to be drained.
 */
 
 class AndroidUiThread : public nsThread
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
-  AndroidUiThread() : nsThread(nsThread::NOT_MAIN_THREAD, 0)
+  AndroidUiThread()
+    : nsThread(WrapNotNull(new ThreadEventQueue<mozilla::EventQueue>(MakeUnique<mozilla::EventQueue>())),
+               nsThread::NOT_MAIN_THREAD, 0)
   {}
 
   nsresult Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) override;
   nsresult DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aDelayMs) override;
 
 private:
   ~AndroidUiThread()
   {}
new file mode 100644
--- /dev/null
+++ b/xpcom/tests/gtest/TestEventPriorities.cpp
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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 "nsIThreadManager.h"
+#include "nsCOMPtr.h"
+#include "nsIRunnable.h"
+#include "nsXPCOM.h"
+#include "nsThreadUtils.h"
+#include "gtest/gtest.h"
+
+using mozilla::Runnable;
+
+class TestEvent final : public Runnable, nsIRunnablePriority
+{
+public:
+  explicit TestEvent(int* aCounter, std::function<void()> aCheck, uint32_t aPriority = nsIRunnablePriority::PRIORITY_NORMAL)
+    : Runnable("TestEvent")
+    , mCounter(aCounter)
+    , mCheck(Move(aCheck))
+    , mPriority(aPriority)
+  {
+  }
+
+  NS_DECL_ISUPPORTS_INHERITED
+
+  NS_IMETHOD GetPriority(uint32_t* aPriority) override
+  {
+    *aPriority = mPriority;
+    return NS_OK;
+  }
+
+  NS_IMETHODIMP Run() override
+  {
+    (*mCounter)++;
+    mCheck();
+    return NS_OK;
+  }
+
+private:
+  ~TestEvent() {}
+
+  int* mCounter;
+  std::function<void()> mCheck;
+  uint32_t mPriority;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(TestEvent,
+                            Runnable,
+                            nsIRunnablePriority)
+
+TEST(EventPriorities, IdleAfterNormal)
+{
+  int normalRan = 0, idleRan = 0;
+
+  RefPtr<TestEvent> evNormal = new TestEvent(&normalRan, [&] { ASSERT_EQ(idleRan, 0); });
+  RefPtr<TestEvent> evIdle = new TestEvent(&idleRan, [&] { ASSERT_EQ(normalRan, 3); });
+
+  NS_IdleDispatchToCurrentThread(do_AddRef(evIdle));
+  NS_IdleDispatchToCurrentThread(do_AddRef(evIdle));
+  NS_IdleDispatchToCurrentThread(do_AddRef(evIdle));
+  NS_DispatchToMainThread(evNormal);
+  NS_DispatchToMainThread(evNormal);
+  NS_DispatchToMainThread(evNormal);
+
+  MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return normalRan == 3 && idleRan == 3; }));
+}
+
+TEST(EventPriorities, InterleaveHighNormal)
+{
+  int normalRan = 0, highRan = 0;
+
+  RefPtr<TestEvent> evNormal = new TestEvent(&normalRan, [&] {
+      ASSERT_TRUE(abs(normalRan - highRan) <= 1);
+    });
+  RefPtr<TestEvent> evHigh = new TestEvent(&highRan, [&] {
+      ASSERT_TRUE(abs(normalRan - highRan) <= 1);
+    },
+    nsIRunnablePriority::PRIORITY_HIGH);
+
+  NS_DispatchToMainThread(evNormal);
+  NS_DispatchToMainThread(evNormal);
+  NS_DispatchToMainThread(evNormal);
+  NS_DispatchToMainThread(evHigh);
+  NS_DispatchToMainThread(evHigh);
+  NS_DispatchToMainThread(evHigh);
+
+  MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return normalRan == 3 && highRan == 3; }));
+}
--- a/xpcom/tests/gtest/TestEventTargetQI.cpp
+++ b/xpcom/tests/gtest/TestEventTargetQI.cpp
@@ -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/. */
 
 #include "mozilla/LazyIdleThread.h"
 #include "mozilla/SharedThreadPool.h"
 #include "mozilla/SystemGroup.h"
 #include "mozilla/ThrottledEventQueue.h"
+#include "nsComponentManagerUtils.h"
 #include "nsCOMPtr.h"
 #include "nsIThreadPool.h"
 #include "nsThreadUtils.h"
 #include "nsXPCOM.h"
 #include "nsXPCOMCIDInternal.h"
 #include "gtest/gtest.h"
 
 using namespace mozilla;
--- a/xpcom/tests/gtest/TestThreadUtils.cpp
+++ b/xpcom/tests/gtest/TestThreadUtils.cpp
@@ -1,12 +1,13 @@
 /* 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 "nsComponentManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "mozilla/IdleTaskRunner.h"
 #include "mozilla/UniquePtr.h"
 
 #include "gtest/gtest.h"
 
 using namespace mozilla;
 
--- a/xpcom/tests/gtest/moz.build
+++ b/xpcom/tests/gtest/moz.build
@@ -13,16 +13,17 @@ UNIFIED_SOURCES += [
     'TestBase64.cpp',
     'TestCallTemplates.cpp',
     'TestCloneInputStream.cpp',
     'TestCOMPtrEq.cpp',
     'TestCRT.cpp',
     'TestDafsa.cpp',
     'TestEncoding.cpp',
     'TestEscapeURL.cpp',
+    'TestEventPriorities.cpp',
     'TestEventTargetQI.cpp',
     'TestExpirationTracker.cpp',
     'TestFile.cpp',
     'TestGCPostBarriers.cpp',
     'TestID.cpp',
     'TestMozPromise.cpp',
     'TestMultiplexInputStream.cpp',
     'TestNsDeque.cpp',
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/AbstractEventQueue.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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/. */
+
+#ifndef mozilla_AbstractEventQueue_h
+#define mozilla_AbstractEventQueue_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Mutex.h"
+
+class nsIRunnable;
+
+namespace mozilla {
+
+enum class EventPriority
+{
+  High,
+  Input,
+  Normal,
+  Idle
+};
+
+// AbstractEventQueue is an abstract base class for all our unsynchronized event
+// queue implementations:
+// - EventQueue: A queue of runnables. Used for non-main threads.
+// - PrioritizedEventQueue: Contains a queue for each priority level.
+//       Has heuristics to decide which queue to pop from. Events are
+//       pushed into the queue corresponding to their priority.
+//       Used for the main thread.
+//
+// Since AbstractEventQueue implementations are unsynchronized, they should be
+// wrapped in an outer SynchronizedEventQueue implementation (like
+// ThreadEventQueue).
+class AbstractEventQueue
+{
+public:
+  // Add an event to the end of the queue. Implementors are free to use
+  // aPriority however they wish. They may ignore it if the runnable has its own
+  // intrinsic priority (via nsIRunnablePriority).
+  virtual void PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
+                        EventPriority aPriority,
+                        const MutexAutoLock& aProofOfLock) = 0;
+
+  // Get an event from the front of the queue. aPriority is an out param. If the
+  // implementation supports priorities, then this should be the same priority
+  // that the event was pushed with. aPriority may be null.
+  virtual already_AddRefed<nsIRunnable> GetEvent(EventPriority* aPriority,
+                                                 const MutexAutoLock& aProofOfLock) = 0;
+
+  // Returns true if the queue is non-empty.
+  virtual bool HasPendingEvent(const MutexAutoLock& aProofOfLock) = 0;
+
+  // Returns the number of events in the queue.
+  virtual size_t Count(const MutexAutoLock& aProofOfLock) const = 0;
+
+  virtual void EnableInputEventPrioritization(const MutexAutoLock& aProofOfLock) = 0;
+
+  virtual ~AbstractEventQueue() {}
+};
+
+} // namespace mozilla
+
+#endif // mozilla_AbstractEventQueue_h
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/EventQueue.cpp
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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 "mozilla/EventQueue.h"
+
+using namespace mozilla;
+
+EventQueue::EventQueue()
+{
+}
+
+void
+EventQueue::PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
+                     EventPriority aPriority,
+                     const MutexAutoLock& aProofOfLock)
+{
+  nsCOMPtr<nsIRunnable> event(aEvent);
+  mQueue.Push(Move(event));
+}
+
+already_AddRefed<nsIRunnable>
+EventQueue::GetEvent(EventPriority* aPriority,
+                     const MutexAutoLock& aProofOfLock)
+{
+  if (mQueue.IsEmpty()) {
+    return nullptr;
+  }
+
+  if (aPriority) {
+    *aPriority = EventPriority::Normal;
+  }
+
+  nsCOMPtr<nsIRunnable> result = mQueue.Pop();
+  return result.forget();
+}
+
+bool
+EventQueue::HasPendingEvent(const MutexAutoLock& aProofOfLock)
+{
+  return !mQueue.IsEmpty();
+}
+
+already_AddRefed<nsIRunnable>
+EventQueue::PeekEvent(const MutexAutoLock& aProofOfLock)
+{
+  if (mQueue.IsEmpty()) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIRunnable> result = mQueue.FirstElement();
+  return result.forget();
+}
+
+size_t
+EventQueue::Count(const MutexAutoLock& aProofOfLock) const
+{
+  return mQueue.Count();
+}
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/EventQueue.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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/. */
+
+#ifndef mozilla_EventQueue_h
+#define mozilla_EventQueue_h
+
+#include "mozilla/AbstractEventQueue.h"
+#include "mozilla/Queue.h"
+#include "nsCOMPtr.h"
+
+class nsIRunnable;
+
+namespace mozilla {
+
+class EventQueue final : public AbstractEventQueue
+{
+public:
+  EventQueue();
+
+  void PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
+                EventPriority aPriority,
+                const MutexAutoLock& aProofOfLock) final;
+  already_AddRefed<nsIRunnable> GetEvent(EventPriority* aPriority,
+                                         const MutexAutoLock& aProofOfLock) final;
+  bool HasPendingEvent(const MutexAutoLock& aProofOfLock) final;
+
+  size_t Count(const MutexAutoLock& aProofOfLock) const final;
+  already_AddRefed<nsIRunnable> PeekEvent(const MutexAutoLock& aProofOfLock);
+
+  void EnableInputEventPrioritization(const MutexAutoLock& aProofOfLock) final {}
+
+private:
+  mozilla::Queue<nsCOMPtr<nsIRunnable>> mQueue;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_EventQueue_h
--- a/xpcom/threads/LazyIdleThread.cpp
+++ b/xpcom/threads/LazyIdleThread.cpp
@@ -509,34 +509,16 @@ LazyIdleThread::HasPendingEvents(bool* a
 
 NS_IMETHODIMP
 LazyIdleThread::IdleDispatch(already_AddRefed<nsIRunnable> aEvent)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
-LazyIdleThread::EnableEventPrioritization()
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-NS_IMETHODIMP
-LazyIdleThread::IsEventPrioritizationEnabled(bool* aResult)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-NS_IMETHODIMP
-LazyIdleThread::RegisterIdlePeriod(already_AddRefed<nsIIdlePeriod> aIdlePeriod)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-NS_IMETHODIMP
 LazyIdleThread::ProcessNextEvent(bool aMayWait,
                                  bool* aEventWasProcessed)
 {
   // This is only supposed to be called from the thread itself so it's not
   // implemented here.
   NS_NOTREACHED("Shouldn't ever call this!");
   return NS_ERROR_UNEXPECTED;
 }
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/PrioritizedEventQueue.cpp
@@ -0,0 +1,310 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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 "PrioritizedEventQueue.h"
+#include "mozilla/EventQueue.h"
+#include "nsThreadManager.h"
+#include "nsXPCOMPrivate.h" // for gXPCOMThreadsShutDown
+
+using namespace mozilla;
+
+template<class InnerQueueT>
+PrioritizedEventQueue<InnerQueueT>::PrioritizedEventQueue(UniquePtr<InnerQueueT> aHighQueue,
+                                                          UniquePtr<InnerQueueT> aInputQueue,
+                                                          UniquePtr<InnerQueueT> aNormalQueue,
+                                                          UniquePtr<InnerQueueT> aIdleQueue,
+                                                          already_AddRefed<nsIIdlePeriod> aIdlePeriod)
+  : mHighQueue(Move(aHighQueue))
+  , mInputQueue(Move(aInputQueue))
+  , mNormalQueue(Move(aNormalQueue))
+  , mIdleQueue(Move(aIdleQueue))
+  , mIdlePeriod(aIdlePeriod)
+{
+  static_assert(IsBaseOf<AbstractEventQueue, InnerQueueT>::value,
+                "InnerQueueT must be an AbstractEventQueue subclass");
+}
+
+template<class InnerQueueT>
+void
+PrioritizedEventQueue<InnerQueueT>::PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
+                                             EventPriority aPriority,
+                                             const MutexAutoLock& aProofOfLock)
+{
+  // Double check the priority with a QI.
+  RefPtr<nsIRunnable> event(aEvent);
+  EventPriority priority = aPriority;
+  if (nsCOMPtr<nsIRunnablePriority> runnablePrio = do_QueryInterface(event)) {
+    uint32_t prio = nsIRunnablePriority::PRIORITY_NORMAL;
+    runnablePrio->GetPriority(&prio);
+    if (prio == nsIRunnablePriority::PRIORITY_HIGH) {
+      priority = EventPriority::High;
+    } else if (prio == nsIRunnablePriority::PRIORITY_INPUT) {
+      priority = EventPriority::Input;
+    }
+  }
+
+  if (priority == EventPriority::Input && !mWriteToInputQueue) {
+    priority = EventPriority::Normal;
+  }
+
+  switch (priority) {
+  case EventPriority::High:
+    mHighQueue->PutEvent(event.forget(), priority, aProofOfLock);
+    break;
+  case EventPriority::Input:
+    mInputQueue->PutEvent(event.forget(), priority, aProofOfLock);
+    break;
+  case EventPriority::Normal:
+    mNormalQueue->PutEvent(event.forget(), priority, aProofOfLock);
+    break;
+  case EventPriority::Idle:
+    mIdleQueue->PutEvent(event.forget(), priority, aProofOfLock);
+    break;
+  }
+}
+
+template<class InnerQueueT>
+TimeStamp
+PrioritizedEventQueue<InnerQueueT>::GetIdleDeadline()
+{
+  // If we are shutting down, we won't honor the idle period, and we will
+  // always process idle runnables.  This will ensure that the idle queue
+  // gets exhausted at shutdown time to prevent intermittently leaking
+  // some runnables inside that queue and even worse potentially leaving
+  // some important cleanup work unfinished.
+  if (gXPCOMThreadsShutDown || nsThreadManager::get().GetCurrentThread()->ShuttingDown()) {
+    return TimeStamp::Now();
+  }
+
+  TimeStamp idleDeadline;
+  {
+    // Releasing the lock temporarily since getting the idle period
+    // might need to lock the timer thread. Unlocking here might make
+    // us receive an event on the main queue, but we've committed to
+    // run an idle event anyhow.
+    MutexAutoUnlock unlock(*mMutex);
+    mIdlePeriod->GetIdlePeriodHint(&idleDeadline);
+  }
+
+  // If HasPendingEvents() has been called and it has returned true because of
+  // pending idle events, there is a risk that we may decide here that we aren't
+  // idle and return null, in which case HasPendingEvents() has effectively
+  // lied.  Since we can't go back and fix the past, we have to adjust what we
+  // do here and forcefully pick the idle queue task here.  Note that this means
+  // that we are choosing to run a task from the idle queue when we would
+  // normally decide that we aren't in an idle period, but this can only happen
+  // if we fall out of the idle period in between the call to HasPendingEvents()
+  // and here, which should hopefully be quite rare.  We are effectively
+  // choosing to prioritize the sanity of our API semantics over the optimal
+  // scheduling.
+  if (!mHasPendingEventsPromisedIdleEvent &&
+      (!idleDeadline || idleDeadline < TimeStamp::Now())) {
+    return TimeStamp();
+  }
+  if (mHasPendingEventsPromisedIdleEvent && !idleDeadline) {
+    // If HasPendingEvents() has been called and it has returned true, but we're no
+    // longer in the idle period, we must return a valid timestamp to pretend that
+    // we are still in the idle period.
+    return TimeStamp::Now();
+  }
+  return idleDeadline;
+}
+
+template<class InnerQueueT>
+already_AddRefed<nsIRunnable>
+PrioritizedEventQueue<InnerQueueT>::GetEvent(EventPriority* aPriority,
+                                             const MutexAutoLock& aProofOfLock)
+{
+  MakeScopeExit([&] {
+    mHasPendingEventsPromisedIdleEvent = false;
+  });
+
+#ifndef RELEASE_OR_BETA
+  // Clear mNextIdleDeadline so that it is possible to determine that
+  // we're running an idle runnable in ProcessNextEvent.
+  *mNextIdleDeadline = TimeStamp();
+#endif
+
+  bool highPending = mHighQueue->HasPendingEvent(aProofOfLock);
+  bool normalPending = mNormalQueue->HasPendingEvent(aProofOfLock);
+  size_t inputCount = mInputQueue->Count(aProofOfLock);
+
+  if (mReadFromInputQueue && mInputHandlingStartTime.IsNull() && inputCount > 0) {
+    mInputHandlingStartTime =
+      InputEventStatistics::Get()
+      .GetInputHandlingStartTime(inputCount);
+  }
+
+  // We check the different queues in the following order. The conditions we use
+  // are meant to avoid starvation and to ensure that we don't process an event
+  // at the wrong time.
+  //
+  // HIGH: if mProcessHighPriorityQueue
+  // INPUT: if inputCount > 0 && TimeStamp::Now() > mInputHandlingStartTime
+  // NORMAL: if normalPending
+  //
+  // If we still don't have an event, then we take events from the queues
+  // in the following order:
+  //
+  // HIGH
+  // INPUT
+  // IDLE: if GetIdleDeadline()
+  //
+  // If we don't get an event in this pass, then we return null since no events
+  // are ready.
+
+  // This variable determines which queue we will take an event from.
+  EventPriority queue;
+
+  if (mProcessHighPriorityQueue) {
+    queue = EventPriority::High;
+  } else if (inputCount > 0 && TimeStamp::Now() > mInputHandlingStartTime
+             && mReadFromInputQueue) {
+    queue = EventPriority::Input;
+  } else if (normalPending) {
+    queue = EventPriority::Normal;
+  } else if (highPending) {
+    queue = EventPriority::High;
+  } else if (inputCount > 0 && mReadFromInputQueue) {
+    queue = EventPriority::Input;
+  } else {
+    // We may not actually return an idle event in this case.
+    queue = EventPriority::Idle;
+  }
+
+  MOZ_ASSERT_IF(queue == EventPriority::Input, mReadFromInputQueue);
+
+  mProcessHighPriorityQueue = highPending;
+
+  if (aPriority) {
+    *aPriority = queue;
+  }
+
+  if (queue == EventPriority::High) {
+    nsCOMPtr<nsIRunnable> event = mHighQueue->GetEvent(aPriority, aProofOfLock);
+    MOZ_ASSERT(event);
+    mInputHandlingStartTime = TimeStamp();
+    mProcessHighPriorityQueue = false;
+    return event.forget();
+  }
+
+  if (queue == EventPriority::Input) {
+    nsCOMPtr<nsIRunnable> event = mInputQueue->GetEvent(aPriority, aProofOfLock);
+    MOZ_ASSERT(event);
+    return event.forget();
+  }
+
+  if (queue == EventPriority::Normal) {
+    nsCOMPtr<nsIRunnable> event = mNormalQueue->GetEvent(aPriority, aProofOfLock);
+    return event.forget();
+  }
+
+  // If we get here, then all queues except idle are empty.
+  MOZ_ASSERT(queue == EventPriority::Idle);
+
+  if (!mIdleQueue->HasPendingEvent(aProofOfLock)) {
+    MOZ_ASSERT(!mHasPendingEventsPromisedIdleEvent);
+    return nullptr;
+  }
+
+  TimeStamp idleDeadline = GetIdleDeadline();
+  if (!idleDeadline) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIRunnable> event = mIdleQueue->GetEvent(aPriority, aProofOfLock);
+  if (event) {
+    nsCOMPtr<nsIIdleRunnable> idleEvent = do_QueryInterface(event);
+    if (idleEvent) {
+      idleEvent->SetDeadline(idleDeadline);
+    }
+
+#ifndef RELEASE_OR_BETA
+    // Store the next idle deadline to be able to determine budget use
+    // in ProcessNextEvent.
+    *mNextIdleDeadline = idleDeadline;
+#endif
+  }
+
+  return event.forget();
+}
+
+template<class InnerQueueT>
+bool
+PrioritizedEventQueue<InnerQueueT>::HasPendingEvent(const MutexAutoLock& aProofOfLock)
+{
+  mHasPendingEventsPromisedIdleEvent = false;
+
+  if (mHighQueue->HasPendingEvent(aProofOfLock) ||
+      mInputQueue->HasPendingEvent(aProofOfLock) ||
+      mNormalQueue->HasPendingEvent(aProofOfLock)) {
+    return true;
+  }
+
+  bool hasPendingIdleEvent = false;
+
+  // Note that GetIdleDeadline() checks mHasPendingEventsPromisedIdleEvent,
+  // but that's OK since we set it to false in the beginning of this method!
+  TimeStamp idleDeadline = GetIdleDeadline();
+
+  // Only examine the idle queue if we are in an idle period.
+  if (idleDeadline) {
+    hasPendingIdleEvent = mIdleQueue->HasPendingEvent(aProofOfLock);
+    mHasPendingEventsPromisedIdleEvent = hasPendingIdleEvent;
+  }
+
+  return hasPendingIdleEvent;
+}
+
+template<class InnerQueueT>
+size_t
+PrioritizedEventQueue<InnerQueueT>::Count(const MutexAutoLock& aProofOfLock) const
+{
+  MOZ_CRASH("unimplemented");
+}
+
+// This is used to flush pending events in nsChainedEventQueue::mNormalQueue
+// before starting event prioritization.
+template<class InnerQueueT>
+class PrioritizedEventQueue<InnerQueueT>::EnablePrioritizationRunnable final
+  : public mozilla::Runnable
+{
+public:
+  explicit EnablePrioritizationRunnable(PrioritizedEventQueue<InnerQueueT>* aQueue)
+    : Runnable("EnablePrioritizationRunnable")
+  {}
+
+  NS_IMETHOD Run() override
+  {
+    // Do don't need to do this with the lock held because mReadFromInputQueue
+    // is only read from the main thread.
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(mQueue->mWriteToInputQueue);
+    MOZ_ASSERT(!mQueue->mReadFromInputQueue);
+    mQueue->mReadFromInputQueue = true;
+    return NS_OK;
+  }
+
+private:
+  PrioritizedEventQueue<InnerQueueT>* mQueue;
+};
+
+template<class InnerQueueT>
+void
+PrioritizedEventQueue<InnerQueueT>::EnableInputEventPrioritization(const MutexAutoLock& aProofOfLock)
+{
+  MOZ_ASSERT(!mWriteToInputQueue);
+  MOZ_ASSERT(!mReadFromInputQueue);
+  mWriteToInputQueue = true;
+  mInputHandlingStartTime = TimeStamp();
+
+  RefPtr<EnablePrioritizationRunnable> runnable = new EnablePrioritizationRunnable(this);
+  PutEvent(runnable.forget(), EventPriority::Normal, aProofOfLock);
+}
+
+namespace mozilla {
+template class PrioritizedEventQueue<EventQueue>;
+}
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/PrioritizedEventQueue.h
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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/. */
+
+#ifndef mozilla_PrioritizedEventQueue_h
+#define mozilla_PrioritizedEventQueue_h
+
+#include "mozilla/AbstractEventQueue.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/TypeTraits.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsIIdlePeriod.h"
+
+class nsIRunnable;
+
+namespace mozilla {
+
+// This AbstractEventQueue implementation has one queue for each EventPriority.
+// The type of queue used for each priority is determined by the template
+// parameter.
+//
+// When an event is pushed, its priority is determined by QIing the runnable to
+// nsIRunnablePriority, or by falling back to the aPriority parameter if the QI
+// fails.
+//
+// When an event is popped, a queue is selected based on heuristics that
+// optimize for performance. Roughly, events are selected from the highest
+// priority queue that is non-empty. However, there are a few exceptions:
+// - We try to avoid processing too many high-priority events in a row so
+//   that the normal priority queue is not starved. When there are high-
+//   and normal-priority events available, we interleave popping from the
+//   normal and high queues.
+// - We do not select events from the idle queue if the current idle period
+//   is almost over.
+template<class InnerQueueT>
+class PrioritizedEventQueue final : public AbstractEventQueue
+{
+public:
+  PrioritizedEventQueue(UniquePtr<InnerQueueT> aHighQueue,
+                        UniquePtr<InnerQueueT> aInputQueue,
+                        UniquePtr<InnerQueueT> aNormalQueue,
+                        UniquePtr<InnerQueueT> aIdleQueue,
+                        already_AddRefed<nsIIdlePeriod> aIdlePeriod);
+
+  void PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
+                EventPriority aPriority,
+                const MutexAutoLock& aProofOfLock) final;
+  already_AddRefed<nsIRunnable> GetEvent(EventPriority* aPriority,
+                                         const MutexAutoLock& aProofOfLock) final;
+  bool HasPendingEvent(const MutexAutoLock& aProofOfLock) final;
+  size_t Count(const MutexAutoLock& aProofOfLock) const final;
+
+  // When checking the idle deadline, we need to drop whatever mutex protects
+  // this queue. This method allows that mutex to be stored so that we can drop
+  // it and reacquire it when checking the idle deadline. The mutex must live at
+  // least as long as the queue.
+  void SetMutexRef(Mutex& aMutex) { mMutex = &aMutex; }
+
+  // nsThread.cpp sends telemetry containing the most recently computed idle
+  // deadline. We store a reference to a field in nsThread where this deadline
+  // will be stored so that it can be fetched quickly for telemetry.
+  void SetNextIdleDeadlineRef(TimeStamp& aDeadline) { mNextIdleDeadline = &aDeadline; }
+
+  void EnableInputEventPrioritization(const MutexAutoLock& aProofOfLock) final;
+
+private:
+  class EnablePrioritizationRunnable;
+
+  // Returns a null TimeStamp if we're not in the idle period.
+  mozilla::TimeStamp GetIdleDeadline();
+
+  UniquePtr<InnerQueueT> mHighQueue;
+  UniquePtr<InnerQueueT> mInputQueue;
+  UniquePtr<InnerQueueT> mNormalQueue;
+  UniquePtr<InnerQueueT> mIdleQueue;
+
+  // We need to drop the queue mutex when checking the idle deadline, so we keep
+  // a pointer to it here.
+  Mutex* mMutex = nullptr;
+
+  // Pointer to a place where the most recently computed idle deadline is
+  // stored.
+  TimeStamp* mNextIdleDeadline = nullptr;
+
+  // Try to process one high priority runnable after each normal
+  // priority runnable. This gives the processing model HTML spec has for
+  // 'Update the rendering' in the case only vsync messages are in the
+  // secondary queue and prevents starving the normal queue.
+  bool mProcessHighPriorityQueue = false;
+
+  // mIdlePeriod keeps track of the current idle period. If at any
+  // time the main event queue is empty, calling
+  // mIdlePeriod->GetIdlePeriodHint() will give an estimate of when
+  // the current idle period will end.
+  nsCOMPtr<nsIIdlePeriod> mIdlePeriod;
+
+  // Set to true if HasPendingEvents() has been called and returned true because
+  // of a pending idle event.  This is used to remember to return that idle
+  // event from GetIdleEvent() to ensure that HasPendingEvents() never lies.
+  bool mHasPendingEventsPromisedIdleEvent = false;
+
+  TimeStamp mInputHandlingStartTime;
+
+  // When we enable input event prioritization, we immediately begin adding new
+  // input events to the input event queue (and set
+  // mWriteToInputQueue). However, we do not begin processing events from the
+  // input queue until all events that were in the normal priority queue have
+  // been processed. That ensures that input events will not jump ahead of
+  // events that were in the queue before prioritization was enabled.
+  bool mWriteToInputQueue = false;
+  bool mReadFromInputQueue = false;
+};
+
+class EventQueue;
+extern template class PrioritizedEventQueue<EventQueue>;
+
+} // namespace mozilla
+
+#endif // mozilla_PrioritizedEventQueue_h
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/Queue.h
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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/. */
+
+#ifndef mozilla_Queue_h
+#define mozilla_Queue_h
+
+namespace mozilla {
+
+// A queue implements a singly linked list of pages, each of which contains some
+// number of elements. Since the queue needs to store a "next" pointer, the
+// actual number of elements per page won't be quite as many as were requested.
+//
+// This class should only be used if it's valid to construct T elements from all
+// zeroes. The class also fails to call the destructor on items. However, it
+// will only destroy items after it has moved out their contents. The queue is
+// required to be empty when it is destroyed.
+template<class T, size_t RequestedItemsPerPage = 256>
+class Queue
+{
+public:
+  Queue() {}
+
+  ~Queue()
+  {
+    MOZ_RELEASE_ASSERT(IsEmpty());
+
+    if (mHead) {
+      free(mHead);
+    }
+  }
+
+  T& Push(T&& aElement)
+  {
+    if (!mHead) {
+      mHead = NewPage();
+      MOZ_ASSERT(mHead);
+
+      mTail = mHead;
+      mOffsetHead = 0;
+      mOffsetTail = 0;
+    } else if (mOffsetTail == ItemsPerPage) {
+      Page* page = NewPage();
+      MOZ_ASSERT(page);
+
+      mTail->mNext = page;
+      mTail = page;
+      mOffsetTail = 0;
+    }
+
+    T& eltLocation = mTail->mEvents[mOffsetTail];
+    eltLocation = Move(aElement);
+    ++mOffsetTail;
+
+    return eltLocation;
+  }
+
+  bool IsEmpty() const
+  {
+    return !mHead || (mHead == mTail && mOffsetHead == mOffsetTail);
+  }
+
+  T Pop()
+  {
+    MOZ_ASSERT(!IsEmpty());
+
+    MOZ_ASSERT(mOffsetHead < ItemsPerPage);
+    MOZ_ASSERT_IF(mHead == mTail, mOffsetHead <= mOffsetTail);
+    T result = Move(mHead->mEvents[mOffsetHead++]);
+
+    MOZ_ASSERT(mOffsetHead <= ItemsPerPage);
+
+    // Check if mHead points to empty Page
+    if (mOffsetHead == ItemsPerPage) {
+      Page* dead = mHead;
+      mHead = mHead->mNext;
+      free(dead);
+      mOffsetHead = 0;
+    }
+
+    return result;
+  }
+
+  void FirstElementAssertions() const
+  {
+    MOZ_ASSERT(!IsEmpty());
+    MOZ_ASSERT(mOffsetHead < ItemsPerPage);
+    MOZ_ASSERT_IF(mHead == mTail, mOffsetHead <= mOffsetTail);
+  }
+
+  T& FirstElement()
+  {
+    FirstElementAssertions();
+    return mHead->mEvents[mOffsetHead];
+  }
+
+  const T& FirstElement() const
+  {
+    FirstElementAssertions();
+    return mHead->mEvents[mOffsetHead];
+  }
+
+  void LastElementAssertions() const
+  {
+    MOZ_ASSERT(!IsEmpty());
+    MOZ_ASSERT(mOffsetTail > 0);
+    MOZ_ASSERT(mOffsetTail <= ItemsPerPage);
+    MOZ_ASSERT_IF(mHead == mTail, mOffsetHead <= mOffsetTail);
+  }
+
+  T& LastElement()
+  {
+    LastElementAssertions();
+    return mTail->mEvents[mOffsetTail - 1];
+  }
+
+  const T& LastElement() const
+  {
+    LastElementAssertions();
+    return mTail->mEvents[mOffsetTail - 1];
+  }
+
+  size_t Count() const
+  {
+    // It is obvious count is 0 when the queue is empty.
+    if (!mHead) {
+      return 0;
+    }
+
+    /* How we count the number of events in the queue:
+     * 1. Let pageCount(x, y) denote the number of pages excluding the tail page
+     *    where x is the index of head page and y is the index of the tail page.
+     * 2. Then we have pageCount(x, y) = y - x.
+     *
+     * Ex: pageCount(0, 0) = 0 where both head and tail pages point to page 0.
+     *     pageCount(0, 1) = 1 where head points to page 0 and tail points page 1.
+     *
+     * 3. number of events = (ItemsPerPage * pageCount(x, y))
+     *      - (empty slots in head page) + (non-empty slots in tail page)
+     *      = (ItemsPerPage * pageCount(x, y)) - mOffsetHead + mOffsetTail
+     */
+
+    int count = -mOffsetHead;
+
+    // Compute (ItemsPerPage * pageCount(x, y))
+    for (Page* page = mHead; page != mTail; page = page->mNext) {
+      count += ItemsPerPage;
+    }
+
+    count += mOffsetTail;
+    MOZ_ASSERT(count >= 0);
+
+    return count;
+  }
+
+private:
+  static_assert((RequestedItemsPerPage & (RequestedItemsPerPage - 1)) == 0,
+                "RequestedItemsPerPage should be a power of two to avoid heap slop.");
+
+  // Since a Page must also contain a "next" pointer, we use one of the items to
+  // store this pointer. If sizeof(T) > sizeof(Page*), then some space will be
+  // wasted. So be it.
+  static const size_t ItemsPerPage = RequestedItemsPerPage - 1;
+
+  // Page objects are linked together to form a simple deque.
+  struct Page
+  {
+    struct Page* mNext;
+    T mEvents[ItemsPerPage];
+  };
+
+  static Page* NewPage()
+  {
+    return static_cast<Page*>(moz_xcalloc(1, sizeof(Page)));
+  }
+
+  Page* mHead = nullptr;
+  Page* mTail = nullptr;
+
+  uint16_t mOffsetHead = 0;  // offset into mHead where next item is removed
+  uint16_t mOffsetTail = 0;  // offset into mTail where next item is added
+};
+
+} // namespace mozilla
+
+#endif // mozilla_Queue_h
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/SynchronizedEventQueue.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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/. */
+
+#ifndef mozilla_SynchronizedEventQueue_h
+#define mozilla_SynchronizedEventQueue_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/AbstractEventQueue.h"
+#include "mozilla/Mutex.h"
+
+class nsIThreadObserver;
+
+namespace mozilla {
+
+// A SynchronizedEventQueue is an abstract class for event queues that can be
+// used across threads. A SynchronizedEventQueue implementation will typically
+// use locks and condition variables to guarantee consistency. The methods of
+// SynchronizedEventQueue are split between ThreadTargetSink (which contains
+// methods for posting events) and SynchronizedEventQueue (which contains
+// methods for getting events). This split allows event targets (specifically
+// ThreadEventTarget) to use a narrow interface, since they only need to post
+// events.
+//
+// ThreadEventQueue is the canonical implementation of
+// SynchronizedEventQueue. When Quantum DOM is implemented, we will use a
+// different synchronized queue on the main thread, SchedulerEventQueue, which
+// will handle the cooperative threading model.
+
+class ThreadTargetSink
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ThreadTargetSink)
+
+  virtual bool PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
+                        EventPriority aPriority) = 0;
+
+  // After this method is called, no more events can be posted.
+  virtual void Disconnect(const MutexAutoLock& aProofOfLock) = 0;
+
+protected:
+  virtual ~ThreadTargetSink() {}
+};
+
+class SynchronizedEventQueue : public ThreadTargetSink
+{
+public:
+  virtual already_AddRefed<nsIRunnable> GetEvent(bool aMayWait,
+                                                 EventPriority* aPriority) = 0;
+  virtual bool HasPendingEvent() = 0;
+
+  // This method atomically checks if there are pending events and, if there are
+  // none, forbids future events from being posted. It returns true if there
+  // were no pending events.
+  virtual bool ShutdownIfNoPendingEvents() = 0;
+
+  // These methods provide access to an nsIThreadObserver, whose methods are
+  // called when posting and processing events. SetObserver should only be
+  // called on the thread that processes events. GetObserver can be called from
+  // any thread. GetObserverOnThread must be used from the thread that processes
+  // events; it does not acquire a lock.
+  virtual already_AddRefed<nsIThreadObserver> GetObserver() = 0;
+  virtual already_AddRefed<nsIThreadObserver> GetObserverOnThread() = 0;
+  virtual void SetObserver(nsIThreadObserver* aObserver) = 0;
+
+  virtual void EnableInputEventPrioritization() = 0;
+
+protected:
+  virtual ~SynchronizedEventQueue() {}
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SynchronizedEventQueue_h
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/ThreadEventQueue.cpp
@@ -0,0 +1,248 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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 "mozilla/ThreadEventQueue.h"
+#include "mozilla/EventQueue.h"
+
+#include "LeakRefPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIThreadInternal.h"
+#include "nsThreadUtils.h"
+#include "PrioritizedEventQueue.h"
+#include "ThreadEventTarget.h"
+
+using namespace mozilla;
+
+template<class InnerQueueT>
+class ThreadEventQueue<InnerQueueT>::NestedSink : public ThreadTargetSink
+{
+public:
+  NestedSink(EventQueue* aQueue, ThreadEventQueue* aOwner)
+    : mQueue(aQueue)
+    , mOwner(aOwner)
+  {
+  }
+
+  bool PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
+                EventPriority aPriority) final
+  {
+    return mOwner->PutEventInternal(Move(aEvent), aPriority, this);
+  }
+
+  void Disconnect(const MutexAutoLock& aProofOfLock) final
+  {
+    mQueue = nullptr;
+  }
+
+private:
+  friend class ThreadEventQueue;
+
+  // This is a non-owning reference. It must live at least until Disconnect is
+  // called to clear it out.
+  EventQueue* mQueue;
+  RefPtr<ThreadEventQueue> mOwner;
+};
+
+template<class InnerQueueT>
+ThreadEventQueue<InnerQueueT>::ThreadEventQueue(UniquePtr<InnerQueueT> aQueue)
+  : mBaseQueue(Move(aQueue))
+  , mLock("ThreadEventQueue")
+  , mEventsAvailable(mLock, "EventsAvail")
+{
+  static_assert(IsBaseOf<AbstractEventQueue, InnerQueueT>::value,
+                "InnerQueueT must be an AbstractEventQueue subclass");
+}
+
+template<class InnerQueueT>
+ThreadEventQueue<InnerQueueT>::~ThreadEventQueue()
+{
+  MOZ_ASSERT(mNestedQueues.IsEmpty());
+}
+
+template<class InnerQueueT>
+bool
+ThreadEventQueue<InnerQueueT>::PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
+                                        EventPriority aPriority)
+{
+  return PutEventInternal(Move(aEvent), aPriority, nullptr);
+}
+
+template<class InnerQueueT>
+bool
+ThreadEventQueue<InnerQueueT>::PutEventInternal(already_AddRefed<nsIRunnable>&& aEvent,
+                                                EventPriority aPriority,
+                                                NestedSink* aSink)
+{
+  // We want to leak the reference when we fail to dispatch it, so that
+  // we won't release the event in a wrong thread.
+  LeakRefPtr<nsIRunnable> event(Move(aEvent));
+  nsCOMPtr<nsIThreadObserver> obs;
+
+  {
+    MutexAutoLock lock(mLock);
+
+    if (mEventsAreDoomed) {
+      return false;
+    }
+
+    if (aSink) {
+      if (!aSink->mQueue) {
+        return false;
+      }
+
+      aSink->mQueue->PutEvent(event.take(), aPriority, lock);
+    } else {
+      mBaseQueue->PutEvent(event.take(), aPriority, lock);
+    }
+
+    mEventsAvailable.Notify();
+
+    // Make sure to grab the observer before dropping the lock, otherwise the
+    // event that we just placed into the queue could run and eventually delete
+    // this nsThread before the calling thread is scheduled again. We would then
+    // crash while trying to access a dead nsThread.
+    obs = mObserver;
+  }
+
+  if (obs) {
+    obs->OnDispatchedEvent();
+  }
+
+  return true;
+}
+
+template<class InnerQueueT>
+already_AddRefed<nsIRunnable>
+ThreadEventQueue<InnerQueueT>::GetEvent(bool aMayWait,
+                                        EventPriority* aPriority)
+{
+  MutexAutoLock lock(mLock);
+
+  nsCOMPtr<nsIRunnable> event;
+  for (;;) {
+    if (mNestedQueues.IsEmpty()) {
+      event = mBaseQueue->GetEvent(aPriority, lock);
+    } else {
+      // We always get events from the topmost queue when there are nested
+      // queues.
+      event = mNestedQueues.LastElement().mQueue->GetEvent(aPriority, lock);
+    }
+
+    if (event || !aMayWait) {
+      break;
+    }
+
+    mEventsAvailable.Wait();
+  }
+
+  return event.forget();
+}
+
+template<class InnerQueueT>
+bool
+ThreadEventQueue<InnerQueueT>::HasPendingEvent()
+{
+  MutexAutoLock lock(mLock);
+
+  // We always get events from the topmost queue when there are nested queues.
+  if (mNestedQueues.IsEmpty()) {
+    return mBaseQueue->HasPendingEvent(lock);
+  } else {
+    return mNestedQueues.LastElement().mQueue->HasPendingEvent(lock);
+  }
+}
+
+template<class InnerQueueT>
+bool
+ThreadEventQueue<InnerQueueT>::ShutdownIfNoPendingEvents()
+{
+  MutexAutoLock lock(mLock);
+  if (mNestedQueues.IsEmpty() && !mBaseQueue->HasPendingEvent(lock)) {
+    mEventsAreDoomed = true;
+    return true;
+  }
+  return false;
+}
+
+template<class InnerQueueT>
+void
+ThreadEventQueue<InnerQueueT>::EnableInputEventPrioritization()
+{
+  MutexAutoLock lock(mLock);
+  mBaseQueue->EnableInputEventPrioritization(lock);
+}
+
+template<class InnerQueueT>
+already_AddRefed<nsISerialEventTarget>
+ThreadEventQueue<InnerQueueT>::PushEventQueue()
+{
+  auto queue = MakeUnique<EventQueue>();
+  RefPtr<NestedSink> sink = new NestedSink(queue.get(), this);
+  RefPtr<ThreadEventTarget> eventTarget = new ThreadEventTarget(sink, NS_IsMainThread());
+
+  MutexAutoLock lock(mLock);
+
+  mNestedQueues.AppendElement(NestedQueueItem(Move(queue), eventTarget));
+  return eventTarget.forget();
+}
+
+template<class InnerQueueT>
+void
+ThreadEventQueue<InnerQueueT>::PopEventQueue(nsIEventTarget* aTarget)
+{
+  MutexAutoLock lock(mLock);
+
+  MOZ_ASSERT(!mNestedQueues.IsEmpty());
+
+  NestedQueueItem& item = mNestedQueues.LastElement();
+
+  MOZ_ASSERT(aTarget == item.mEventTarget);
+
+  // Disconnect the event target that will be popped.
+  item.mEventTarget->Disconnect(lock);
+
+  AbstractEventQueue* prevQueue =
+    mNestedQueues.Length() == 1
+    ? static_cast<AbstractEventQueue*>(mBaseQueue.get())
+    : static_cast<AbstractEventQueue*>(mNestedQueues[mNestedQueues.Length() - 2].mQueue.get());
+
+  // Move events from the old queue to the new one.
+  nsCOMPtr<nsIRunnable> event;
+  EventPriority prio;
+  while ((event = item.mQueue->GetEvent(&prio, lock))) {
+    prevQueue->PutEvent(event.forget(), prio, lock);
+  }
+
+  mNestedQueues.RemoveElementAt(mNestedQueues.Length() - 1);
+}
+
+template<class InnerQueueT>
+already_AddRefed<nsIThreadObserver>
+ThreadEventQueue<InnerQueueT>::GetObserver()
+{
+  MutexAutoLock lock(mLock);
+  return do_AddRef(mObserver.get());
+}
+
+template<class InnerQueueT>
+already_AddRefed<nsIThreadObserver>
+ThreadEventQueue<InnerQueueT>::GetObserverOnThread()
+{
+  return do_AddRef(mObserver.get());
+}
+
+template<class InnerQueueT>
+void
+ThreadEventQueue<InnerQueueT>::SetObserver(nsIThreadObserver* aObserver)
+{
+  MutexAutoLock lock(mLock);
+  mObserver = aObserver;
+}
+
+namespace mozilla {
+template class ThreadEventQueue<EventQueue>;
+template class ThreadEventQueue<PrioritizedEventQueue<EventQueue>>;
+}
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/ThreadEventQueue.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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/. */
+
+#ifndef mozilla_ThreadEventQueue_h
+#define mozilla_ThreadEventQueue_h
+
+#include "mozilla/AbstractEventQueue.h"
+#include "mozilla/CondVar.h"
+#include "mozilla/SynchronizedEventQueue.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+
+class nsIEventTarget;
+class nsISerialEventTarget;
+class nsIThreadObserver;
+
+namespace mozilla {
+
+class EventQueue;
+
+template<typename InnerQueueT>
+class PrioritizedEventQueue;
+
+class ThreadEventTarget;
+
+// A ThreadEventQueue implements normal monitor-style synchronization over the
+// InnerQueueT AbstractEventQueue. It also implements PushEventQueue and
+// PopEventQueue for workers (see the documentation below for an explanation of
+// those). All threads use a ThreadEventQueue as their event queue. InnerQueueT
+// is a template parameter to avoid virtual dispatch overhead.
+template<class InnerQueueT>
+class ThreadEventQueue final : public SynchronizedEventQueue
+{
+public:
+  explicit ThreadEventQueue(UniquePtr<InnerQueueT> aQueue);
+
+  bool PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
+                EventPriority aPriority) final;
+
+  already_AddRefed<nsIRunnable> GetEvent(bool aMayWait,
+                                         EventPriority* aPriority) final;
+  bool HasPendingEvent() final;
+
+  bool ShutdownIfNoPendingEvents() final;
+
+  void Disconnect(const MutexAutoLock& aProofOfLock) final {}
+
+  void EnableInputEventPrioritization() final;
+
+  /**
+   * This method causes any events currently enqueued on the thread to be
+   * suppressed until PopEventQueue is called, and any event dispatched to this
+   * thread's nsIEventTarget will queue as well. Calls to PushEventQueue may be
+   * nested and must each be paired with a call to PopEventQueue in order to
+   * restore the original state of the thread. The returned nsIEventTarget may
+   * be used to push events onto the nested queue. Dispatching will be disabled
+   * once the event queue is popped. The thread will only ever process pending
+   * events for the innermost event queue. Must only be called on the target
+   * thread.
+   */
+  already_AddRefed<nsISerialEventTarget> PushEventQueue();
+
+  /**
+   * Revert a call to PushEventQueue. When an event queue is popped, any events
+   * remaining in the queue are appended to the elder queue. This also causes
+   * the nsIEventTarget returned from PushEventQueue to stop dispatching events.
+   * Must only be called on the target thread, and with the innermost event
+   * queue.
+   */
+  void PopEventQueue(nsIEventTarget* aTarget);
+
+  already_AddRefed<nsIThreadObserver> GetObserver() final;
+  already_AddRefed<nsIThreadObserver> GetObserverOnThread() final;
+  void SetObserver(nsIThreadObserver* aObserver) final;
+
+  Mutex& MutexRef() { return mLock; }
+
+private:
+  class NestedSink;
+
+  virtual ~ThreadEventQueue();
+
+  bool PutEventInternal(already_AddRefed<nsIRunnable>&& aEvent,
+                        EventPriority aPriority,
+                        NestedSink* aQueue);
+
+  UniquePtr<InnerQueueT> mBaseQueue;
+
+  struct NestedQueueItem
+  {
+    UniquePtr<EventQueue> mQueue;
+    RefPtr<ThreadEventTarget> mEventTarget;
+
+    NestedQueueItem(UniquePtr<EventQueue> aQueue,
+                    ThreadEventTarget* aEventTarget)
+      : mQueue(Move(aQueue))
+      , mEventTarget(aEventTarget)
+    {}
+  };
+
+  nsTArray<NestedQueueItem> mNestedQueues;
+
+  Mutex mLock;
+  CondVar mEventsAvailable;
+
+  bool mEventsAreDoomed = false;
+  nsCOMPtr<nsIThreadObserver> mObserver;
+};
+
+extern template class ThreadEventQueue<EventQueue>;
+extern template class ThreadEventQueue<PrioritizedEventQueue<EventQueue>>;
+
+}; // namespace mozilla
+
+#endif // mozilla_ThreadEventQueue_h
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/ThreadEventTarget.cpp
@@ -0,0 +1,210 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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 "ThreadEventTarget.h"
+#include "mozilla/ThreadEventQueue.h"
+
+#include "LeakRefPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsThreadManager.h"
+#include "nsThreadSyncDispatch.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOMPrivate.h" // for gXPCOMThreadsShutDown
+
+#ifdef MOZ_TASK_TRACER
+#include "GeckoTaskTracer.h"
+#include "TracedTaskCommon.h"
+using namespace mozilla::tasktracer;
+#endif
+
+using namespace mozilla;
+
+namespace {
+
+class DelayedRunnable : public Runnable,
+                        public nsITimerCallback
+{
+public:
+  DelayedRunnable(already_AddRefed<nsIEventTarget> aTarget,
+                  already_AddRefed<nsIRunnable> aRunnable,
+                  uint32_t aDelay)
+    : mozilla::Runnable("DelayedRunnable")
+    , mTarget(aTarget)
+    , mWrappedRunnable(aRunnable)
+    , mDelayedFrom(TimeStamp::NowLoRes())
+    , mDelay(aDelay)
+  { }
+
+  NS_DECL_ISUPPORTS_INHERITED
+
+  nsresult Init()
+  {
+    nsresult rv;
+    mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    MOZ_ASSERT(mTimer);
+    rv = mTimer->SetTarget(mTarget);
+
+    NS_ENSURE_SUCCESS(rv, rv);
+    return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT);
+  }
+
+  nsresult DoRun()
+  {
+    nsCOMPtr<nsIRunnable> r = mWrappedRunnable.forget();
+    return r->Run();
+  }
+
+  NS_IMETHOD Run() override
+  {
+    // Already ran?
+    if (!mWrappedRunnable) {
+      return NS_OK;
+    }
+
+    // Are we too early?
+    if ((TimeStamp::NowLoRes() - mDelayedFrom).ToMilliseconds() < mDelay) {
+      return NS_OK; // Let the nsITimer run us.
+    }
+
+    mTimer->Cancel();
+    return DoRun();
+  }
+
+  NS_IMETHOD Notify(nsITimer* aTimer) override
+  {
+    // If we already ran, the timer should have been canceled.
+    MOZ_ASSERT(mWrappedRunnable);
+    MOZ_ASSERT(aTimer == mTimer);
+
+    return DoRun();
+  }
+
+private:
+  ~DelayedRunnable() {}
+
+  nsCOMPtr<nsIEventTarget> mTarget;
+  nsCOMPtr<nsIRunnable> mWrappedRunnable;
+  nsCOMPtr<nsITimer> mTimer;
+  TimeStamp mDelayedFrom;
+  uint32_t mDelay;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(DelayedRunnable, Runnable, nsITimerCallback)
+
+} // anonymous namespace
+
+ThreadEventTarget::ThreadEventTarget(ThreadTargetSink* aSink,
+                                     bool aIsMainThread)
+  : mSink(aSink)
+  , mIsMainThread(aIsMainThread)
+{
+  mVirtualThread = GetCurrentVirtualThread();
+}
+
+void
+ThreadEventTarget::SetCurrentThread()
+{
+  mVirtualThread = GetCurrentVirtualThread();
+}
+
+NS_IMPL_ISUPPORTS(ThreadEventTarget,
+                  nsIEventTarget,
+                  nsISerialEventTarget)
+
+NS_IMETHODIMP
+ThreadEventTarget::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags)
+{
+  return Dispatch(do_AddRef(aRunnable), aFlags);
+}
+
+NS_IMETHODIMP
+ThreadEventTarget::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
+{
+  // We want to leak the reference when we fail to dispatch it, so that
+  // we won't release the event in a wrong thread.
+  LeakRefPtr<nsIRunnable> event(Move(aEvent));
+  if (NS_WARN_IF(!event)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  if (gXPCOMThreadsShutDown && !mIsMainThread) {
+    NS_ASSERTION(false, "Failed Dispatch after xpcom-shutdown-threads");
+    return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+  }
+
+#ifdef MOZ_TASK_TRACER
+  nsCOMPtr<nsIRunnable> tracedRunnable = CreateTracedRunnable(event.take());
+  (static_cast<TracedRunnable*>(tracedRunnable.get()))->DispatchTask();
+  // XXX tracedRunnable will always leaked when we fail to disptch.
+  event = tracedRunnable.forget();
+#endif
+
+  if (aFlags & DISPATCH_SYNC) {
+    nsCOMPtr<nsIEventTarget> current = GetCurrentThreadEventTarget();
+    if (NS_WARN_IF(!current)) {
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+
+    // XXX we should be able to do something better here... we should
+    //     be able to monitor the slot occupied by this event and use
+    //     that to tell us when the event has been processed.
+
+    RefPtr<nsThreadSyncDispatch> wrapper =
+      new nsThreadSyncDispatch(current.forget(), event.take());
+    bool success = mSink->PutEvent(do_AddRef(wrapper), EventPriority::Normal); // hold a ref
+    if (!success) {
+      // PutEvent leaked the wrapper runnable object on failure, so we
+      // explicitly release this object once for that. Note that this
+      // object will be released again soon because it exits the scope.
+      wrapper.get()->Release();
+      return NS_ERROR_UNEXPECTED;
+    }
+
+    // Allows waiting; ensure no locks are held that would deadlock us!
+    SpinEventLoopUntil([&, wrapper]() -> bool {
+        return !wrapper->IsPending();
+      });
+
+    return NS_OK;
+  }
+
+  NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL ||
+               aFlags == NS_DISPATCH_AT_END, "unexpected dispatch flags");
+  if (!mSink->PutEvent(event.take(), EventPriority::Normal)) {
+    return NS_ERROR_UNEXPECTED;
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ThreadEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aDelayMs)
+{
+  NS_ENSURE_TRUE(!!aDelayMs, NS_ERROR_UNEXPECTED);
+
+  RefPtr<DelayedRunnable> r = new DelayedRunnable(do_AddRef(this),
+                                                  Move(aEvent),
+                                                  aDelayMs);
+  nsresult rv = r->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+ThreadEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread)
+{
+  *aIsOnCurrentThread = IsOnCurrentThread();
+  return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+ThreadEventTarget::IsOnCurrentThreadInfallible()
+{
+  // Rely on mVirtualThread being correct.
+  MOZ_CRASH("IsOnCurrentThreadInfallible should never be called on nsIThread");
+}
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/ThreadEventTarget.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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/. */
+
+#ifndef mozilla_ThreadEventTarget_h
+#define mozilla_ThreadEventTarget_h
+
+#include "mozilla/Mutex.h"
+#include "mozilla/SynchronizedEventQueue.h" // for ThreadTargetSink
+#include "nsISerialEventTarget.h"
+
+namespace mozilla {
+
+// ThreadEventTarget handles the details of posting an event to a thread. It can
+// be used with any ThreadTargetSink implementation.
+class ThreadEventTarget final : public nsISerialEventTarget
+{
+public:
+  ThreadEventTarget(ThreadTargetSink* aSink,
+                    bool aIsMainThread);
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIEVENTTARGET_FULL
+
+  // Disconnects the target so that it can no longer post events.
+  void Disconnect(const MutexAutoLock& aProofOfLock) { mSink->Disconnect(aProofOfLock); }
+
+  // Sets the thread for which IsOnCurrentThread returns true to the current thread.
+  void SetCurrentThread();
+
+private:
+  ~ThreadEventTarget() {}
+
+  RefPtr<ThreadTargetSink> mSink;
+  bool mIsMainThread;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ThreadEventTarget_h
--- a/xpcom/threads/ThrottledEventQueue.cpp
+++ b/xpcom/threads/ThrottledEventQueue.cpp
@@ -3,19 +3,22 @@
 /* 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 "ThrottledEventQueue.h"
 
 #include "mozilla/Atomics.h"
 #include "mozilla/ClearOnShutdown.h"
+#include "mozilla/EventQueue.h"
 #include "mozilla/Mutex.h"
+#include "mozilla/Services.h"
 #include "mozilla/Unused.h"
-#include "nsEventQueue.h"
+#include "nsIObserverService.h"
+#include "nsThreadUtils.h"
 
 namespace mozilla {
 
 using mozilla::services::GetObserverService;
 
 namespace {
 
 static const char kShutdownTopic[] = "xpcom-shutdown";
@@ -81,35 +84,31 @@ class ThrottledEventQueue::Inner final :
     {
       return mInner->CurrentName(aName);
     }
   };
 
   mutable Mutex mMutex;
   mutable CondVar mIdleCondVar;
 
-  mozilla::CondVar mEventsAvailable;
-
   // any thread, protected by mutex
-  nsEventQueue mEventQueue;
+  EventQueue mEventQueue;
 
   // written on main thread, read on any thread
   nsCOMPtr<nsISerialEventTarget> mBaseTarget;
 
   // any thread, protected by mutex
   nsCOMPtr<nsIRunnable> mExecutor;
 
   // any thread, protected by mutex
   bool mShutdownStarted;
 
   explicit Inner(nsISerialEventTarget* aBaseTarget)
     : mMutex("ThrottledEventQueue")
     , mIdleCondVar(mMutex, "ThrottledEventQueue:Idle")
-    , mEventsAvailable(mMutex, "[ThrottledEventQueue::Inner.mEventsAvailable]")
-    , mEventQueue(mEventsAvailable, nsEventQueue::eNormalQueue)
     , mBaseTarget(aBaseTarget)
     , mShutdownStarted(false)
   {
   }
 
   ~Inner()
   {
     MOZ_ASSERT(!mExecutor);
@@ -127,17 +126,18 @@ class ThrottledEventQueue::Inner final :
     MOZ_ASSERT(currentThread);
 #endif
 
     {
       MutexAutoLock lock(mMutex);
 
       // We only check the name of an executor runnable when we know there is something
       // in the queue, so this should never fail.
-      MOZ_ALWAYS_TRUE(mEventQueue.PeekEvent(getter_AddRefs(event), lock));
+      event = mEventQueue.PeekEvent(lock);
+      MOZ_ALWAYS_TRUE(event);
     }
 
     if (nsCOMPtr<nsINamed> named = do_QueryInterface(event)) {
       nsresult rv = named->GetName(aName);
       return rv;
     }
 
     aName.AssignLiteral("non-nsINamed ThrottledEventQueue runnable");
@@ -157,17 +157,18 @@ class ThrottledEventQueue::Inner final :
     MOZ_ASSERT(currentThread);
 #endif
 
     {
       MutexAutoLock lock(mMutex);
 
       // We only dispatch an executor runnable when we know there is something
       // in the queue, so this should never fail.
-      MOZ_ALWAYS_TRUE(mEventQueue.GetPendingEvent(getter_AddRefs(event), lock));
+      event = mEventQueue.GetEvent(nullptr, lock);
+      MOZ_ASSERT(event);
 
       // If there are more events in the queue, then dispatch the next
       // executor.  We do this now, before running the event, because
       // the event might spin the event loop and we don't want to stall
       // the queue.
       if (mEventQueue.HasPendingEvent(lock)) {
         // Dispatch the next base target runnable to attempt to execute
         // the next throttled event.  We must do this before executing
@@ -347,17 +348,17 @@ public:
       if (NS_WARN_IF(NS_FAILED(rv))) {
         mExecutor = nullptr;
         return rv;
       }
     }
 
     // Only add the event to the underlying queue if are able to
     // dispatch to our base target.
-    mEventQueue.PutEvent(Move(aEvent), lock);
+    mEventQueue.PutEvent(Move(aEvent), EventPriority::Normal, lock);
     return NS_OK;
   }
 
   nsresult
   DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aDelay)
   {
     // The base target may implement this, but we don't.  Always fail
     // to provide consistent behavior.
--- a/xpcom/threads/moz.build
+++ b/xpcom/threads/moz.build
@@ -19,82 +19,89 @@ XPIDL_SOURCES += [
     'nsIThreadPool.idl',
     'nsITimer.idl',
 ]
 
 XPIDL_MODULE = 'xpcom_threads'
 
 EXPORTS += [
     'MainThreadUtils.h',
-    'nsEventQueue.h',
     'nsICancelableRunnable.h',
     'nsIIdleRunnable.h',
     'nsMemoryPressure.h',
     'nsProcess.h',
     'nsProxyRelease.h',
     'nsThread.h',
     'nsThreadUtils.h',
 ]
 
 EXPORTS.mozilla += [
+    'AbstractEventQueue.h',
     'AbstractThread.h',
     'BlockingResourceBase.h',
     'CondVar.h',
     'DeadlockDetector.h',
+    'EventQueue.h',
     'HangAnnotations.h',
     'HangMonitor.h',
     'IdleTaskRunner.h',
     'LazyIdleThread.h',
     'MainThreadIdlePeriod.h',
     'Monitor.h',
     'MozPromise.h',
     'Mutex.h',
+    'Queue.h',
     'RecursiveMutex.h',
     'ReentrantMonitor.h',
     'RWLock.h',
     'SchedulerGroup.h',
     'SharedThreadPool.h',
     'StateMirroring.h',
     'StateWatching.h',
+    'SynchronizedEventQueue.h',
     'SyncRunnable.h',
     'SystemGroup.h',
     'TaskCategory.h',
     'TaskDispatcher.h',
     'TaskQueue.h',
+    'ThreadEventQueue.h',
     'ThrottledEventQueue.h',
 ]
 
 SOURCES += [
     'IdleTaskRunner.cpp',
 ]
 
 UNIFIED_SOURCES += [
     'AbstractThread.cpp',
     'BlockingResourceBase.cpp',
+    'EventQueue.cpp',
     'HangAnnotations.cpp',
     'HangMonitor.cpp',
     'InputEventStatistics.cpp',
     'LazyIdleThread.cpp',
     'MainThreadIdlePeriod.cpp',
     'nsEnvironment.cpp',
-    'nsEventQueue.cpp',
     'nsMemoryPressure.cpp',
     'nsProcessCommon.cpp',
     'nsProxyRelease.cpp',
     'nsThread.cpp',
     'nsThreadManager.cpp',
     'nsThreadPool.cpp',
     'nsThreadUtils.cpp',
     'nsTimerImpl.cpp',
+    'PrioritizedEventQueue.cpp',
     'RecursiveMutex.cpp',
     'RWLock.cpp',
     'SchedulerGroup.cpp',
     'SharedThreadPool.cpp',
     'SystemGroup.cpp',
     'TaskQueue.cpp',
+    'ThreadEventQueue.cpp',
+    'ThreadEventTarget.cpp',
     'ThrottledEventQueue.cpp',
     'TimerThread.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '../build',
     '/caps',
     '/tools/profiler',
deleted file mode 100644
--- a/xpcom/threads/nsEventQueue.cpp
+++ /dev/null
@@ -1,174 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 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 "nsEventQueue.h"
-#include "nsAutoPtr.h"
-#include "mozilla/Logging.h"
-#include "nsThreadUtils.h"
-#include "prthread.h"
-#include "mozilla/ChaosMode.h"
-
-using namespace mozilla;
-
-static LazyLogModule sEventQueueLog("nsEventQueue");
-#ifdef LOG
-#undef LOG
-#endif
-#define LOG(args) MOZ_LOG(sEventQueueLog, mozilla::LogLevel::Debug, args)
-
-nsEventQueue::nsEventQueue(mozilla::CondVar& aCondVar, EventQueueType aType)
-  : mHead(nullptr)
-  , mTail(nullptr)
-  , mOffsetHead(0)
-  , mOffsetTail(0)
-  , mEventsAvailable(aCondVar)
-  , mType(aType)
-{
-}
-
-nsEventQueue::~nsEventQueue()
-{
-  // It'd be nice to be able to assert that no one else is holding the lock,
-  // but NSPR doesn't really expose APIs for it.
-  NS_ASSERTION(IsEmpty(),
-               "Non-empty event queue being destroyed; events being leaked.");
-
-  if (mHead) {
-    FreePage(mHead);
-  }
-}
-
-bool
-nsEventQueue::PeekEvent(nsIRunnable** aEvent, MutexAutoLock& aProofOfLock)
-{
-  MOZ_ASSERT(aEvent);
-  *aEvent = nullptr;
-
-  if (IsEmpty()) {
-    return false;
-  }
-
-  MOZ_ASSERT(mOffsetHead < EVENTS_PER_PAGE);
-  MOZ_ASSERT_IF(mHead == mTail, mOffsetHead <= mOffsetTail);
-  NS_ADDREF(*aEvent = mHead->mEvents[mOffsetHead]);
-
-  MOZ_ASSERT(*aEvent);
-
-  return true;
-}
-
-bool
-nsEventQueue::GetEvent(bool aMayWait, nsIRunnable** aResult,
-                       MutexAutoLock& aProofOfLock)
-{
-  if (aResult) {
-    *aResult = nullptr;
-  }
-
-  while (IsEmpty()) {
-    if (!aMayWait) {
-      return false;
-    }
-    LOG(("EVENTQ(%p): wait begin\n", this));
-    mEventsAvailable.Wait();
-    LOG(("EVENTQ(%p): wait end\n", this));
-
-    if (mType == eSharedCondVarQueue) {
-      if (IsEmpty()) {
-        return false;
-      }
-      break;
-    }
-  }
-
-  if (aResult) {
-    MOZ_ASSERT(mOffsetHead < EVENTS_PER_PAGE);
-    MOZ_ASSERT_IF(mHead == mTail, mOffsetHead <= mOffsetTail);
-    *aResult = mHead->mEvents[mOffsetHead++];
-
-    MOZ_ASSERT(*aResult);
-    MOZ_ASSERT(mOffsetHead <= EVENTS_PER_PAGE);
-
-    // Check if mHead points to empty Page
-    if (mOffsetHead == EVENTS_PER_PAGE) {
-      Page* dead = mHead;
-      mHead = mHead->mNext;
-      FreePage(dead);
-      mOffsetHead = 0;
-    }
-  }
-
-  return true;
-}
-
-void
-nsEventQueue::PutEvent(already_AddRefed<nsIRunnable>&& aRunnable,
-                       MutexAutoLock& aProofOfLock)
-{
-  if (!mHead) {
-    mHead = NewPage();
-    MOZ_ASSERT(mHead);
-
-    mTail = mHead;
-    mOffsetHead = 0;
-    mOffsetTail = 0;
-  } else if (mOffsetTail == EVENTS_PER_PAGE) {
-    Page* page = NewPage();
-    MOZ_ASSERT(page);
-
-    mTail->mNext = page;
-    mTail = page;
-    mOffsetTail = 0;
-  }
-
-  nsIRunnable*& queueLocation = mTail->mEvents[mOffsetTail];
-  MOZ_ASSERT(!queueLocation);
-  queueLocation = aRunnable.take();
-  ++mOffsetTail;
-  LOG(("EVENTQ(%p): notify\n", this));
-  mEventsAvailable.Notify();
-}
-
-void
-nsEventQueue::PutEvent(nsIRunnable* aRunnable, MutexAutoLock& aProofOfLock)
-{
-  nsCOMPtr<nsIRunnable> event(aRunnable);
-  PutEvent(event.forget(), aProofOfLock);
-}
-
-size_t
-nsEventQueue::Count(MutexAutoLock& aProofOfLock) const
-{
-  // It is obvious count is 0 when the queue is empty.
-  if (!mHead) {
-    return 0;
-  }
-
-  /* How we count the number of events in the queue:
-   * 1. Let pageCount(x, y) denote the number of pages excluding the tail page
-   *    where x is the index of head page and y is the index of the tail page.
-   * 2. Then we have pageCount(x, y) = y - x.
-   *
-   * Ex: pageCount(0, 0) = 0 where both head and tail pages point to page 0.
-   *     pageCount(0, 1) = 1 where head points to page 0 and tail points page 1.
-   *
-   * 3. number of events = (EVENTS_PER_PAGE * pageCount(x, y))
-   *      - (empty slots in head page) + (non-empty slots in tail page)
-   *      = (EVENTS_PER_PAGE * pageCount(x, y)) - mOffsetHead + mOffsetTail
-   */
-
-  int count = -mOffsetHead;
-
-  // Compute (EVENTS_PER_PAGE * pageCount(x, y))
-  for (Page* page = mHead; page != mTail; page = page->mNext) {
-    count += EVENTS_PER_PAGE;
-  }
-
-  count += mOffsetTail;
-  MOZ_ASSERT(count >= 0);
-
-  return count;
-}
deleted file mode 100644
--- a/xpcom/threads/nsEventQueue.h
+++ /dev/null
@@ -1,127 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 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/. */
-
-#ifndef nsEventQueue_h__
-#define nsEventQueue_h__
-
-#include <stdlib.h>
-#include "mozilla/CondVar.h"
-#include "mozilla/Mutex.h"
-#include "nsIRunnable.h"
-#include "nsCOMPtr.h"
-#include "mozilla/AlreadyAddRefed.h"
-#include "mozilla/UniquePtr.h"
-
-class nsThreadPool;
-
-// A threadsafe FIFO event queue...
-class nsEventQueue
-{
-public:
-  typedef mozilla::MutexAutoLock MutexAutoLock;
-
-  enum EventQueueType
-  {
-    eNormalQueue,
-    eSharedCondVarQueue
-  };
-
-  nsEventQueue(mozilla::CondVar& aCondVar, EventQueueType aType);
-  ~nsEventQueue();
-
-  // This method adds a new event to the pending event queue.  The queue holds
-  // a strong reference to the event after this method returns.  This method
-  // cannot fail.
-  void PutEvent(nsIRunnable* aEvent, MutexAutoLock& aProofOfLock);
-  void PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
-                MutexAutoLock& aProofOfLock);
-
-  // Return the first event in the queue without popping it. Returns whether the
-  // queue was empty or not. aEvent is set to null if the queue was empty.
-  bool PeekEvent(nsIRunnable** aEvent, MutexAutoLock& aProofOfLock);
-
-  // This method gets an event from the event queue.  If mayWait is true, then
-  // the method will block the calling thread until an event is available.  If
-  // the event is null, then the method returns immediately indicating whether
-  // or not an event is pending.  When the resulting event is non-null, the
-  // caller is responsible for releasing the event object.  This method does
-  // not alter the reference count of the resulting event.
-  bool GetEvent(bool aMayWait, nsIRunnable** aEvent,
-                MutexAutoLock& aProofOfLock);
-
-  // This method returns true if there is a pending event.
-  bool HasPendingEvent(MutexAutoLock& aProofOfLock)
-  {
-    return GetEvent(false, nullptr, aProofOfLock);
-  }
-
-  // This method returns the next pending event or null.
-  bool GetPendingEvent(nsIRunnable** aRunnable, MutexAutoLock& aProofOfLock)
-  {
-    return GetEvent(false, aRunnable, aProofOfLock);
-  }
-
-  size_t Count(MutexAutoLock&) const;
-
-private:
-  bool IsEmpty()
-  {
-    return !mHead || (mHead == mTail && mOffsetHead == mOffsetTail);
-  }
-
-  enum
-  {
-    EVENTS_PER_PAGE = 255
-  };
-
-  // Page objects are linked together to form a simple deque.
-
-  struct Page
-  {
-    struct Page* mNext;
-    nsIRunnable* mEvents[EVENTS_PER_PAGE];
-  };
-
-  static_assert((sizeof(Page) & (sizeof(Page) - 1)) == 0,
-                "sizeof(Page) should be a power of two to avoid heap slop.");
-
-  static Page* NewPage()
-  {
-    return static_cast<Page*>(moz_xcalloc(1, sizeof(Page)));
-  }
-
-  static void FreePage(Page* aPage)
-  {
-    free(aPage);
-  }
-
-  Page* mHead;
-  Page* mTail;
-
-  uint16_t mOffsetHead;  // offset into mHead where next item is removed
-  uint16_t mOffsetTail;  // offset into mTail where next item is added
-  mozilla::CondVar& mEventsAvailable;
-
-  EventQueueType mType;
-
-  // These methods are made available to nsThreadPool as a hack, since
-  // nsThreadPool needs to have its threads sleep for fixed amounts of
-  // time as well as being able to wake up all threads when thread
-  // limits change.
-  friend class nsThreadPool;
-  void Wait(PRIntervalTime aInterval)
-  {
-    MOZ_ASSERT(mType == eNormalQueue);
-    mEventsAvailable.Wait(aInterval);
-  }
-  void NotifyAll()
-  {
-    MOZ_ASSERT(mType == eNormalQueue);
-    mEventsAvailable.NotifyAll();
-  }
-};
-
-#endif  // nsEventQueue_h__
--- a/xpcom/threads/nsIThread.idl
+++ b/xpcom/threads/nsIThread.idl
@@ -1,24 +1,22 @@
 /* -*- 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 "nsISerialEventTarget.idl"
-#include "nsIIdlePeriod.idl"
 
 %{C++
 #include "mozilla/AlreadyAddRefed.h"
 %}
 
 [ptr] native PRThread(PRThread);
 
-native alreadyAddRefed_nsIIdlePeriod(already_AddRefed<nsIIdlePeriod>);
 native nsIEventTargetPtr(nsIEventTarget*);
 native nsISerialEventTargetPtr(nsISerialEventTarget*);
 
 /**
  * This interface provides a high-level abstraction for an operating system
  * thread.
  *
  * Threads have a built-in event queue, and a thread is an event target that
@@ -118,48 +116,31 @@ interface nsIThread : nsISerialEventTarg
    *   Indicates that this method was erroneously called when this thread was
    *   the current thread, that this thread was not created with a call to
    *   nsIThreadManager::NewThread, or if this method was called more than once
    *   on the thread object.
    */
   void asyncShutdown();
 
   /**
-   * Register an instance of nsIIdlePeriod which works as a facade of
-   * the abstract notion of a "current idle period". The
-   * nsIIdlePeriod should always represent the "current" idle period
-   * with regard to expected work for the thread. The thread is free
-   * to use this when there are no higher prioritized tasks to process
-   * to determine if it is reasonable to schedule tasks that could run
-   * when the thread is idle. The responsibility of the registered
-   * nsIIdlePeriod is to answer with an estimated deadline at which
-   * the thread should expect that it could receive new higher
-   * priority tasks.
-   */
-  [noscript] void registerIdlePeriod(in alreadyAddRefed_nsIIdlePeriod aIdlePeriod);
-
-  /**
    * Dispatch an event to the thread's idle queue.  This function may be called
    * from any thread, and it may be called re-entrantly.
    *
    * @param event
    *   The alreadyAddRefed<> event to dispatch.
    *   NOTE that the event will be leaked if it fails to dispatch.
    *
    * @throws NS_ERROR_INVALID_ARG
    *   Indicates that event is null.
    * @throws NS_ERROR_UNEXPECTED
    *   Indicates that the thread is shutting down and has finished processing
    * events, so this event would never run and has not been dispatched.
    */
   [noscript] void idleDispatch(in alreadyAddRefed_nsIRunnable event);
 
-  [noscript] void enableEventPrioritization();
-  [noscript] bool isEventPrioritizationEnabled();
-
   /**
    * Use this attribute to dispatch runnables to the thread. Eventually, the
    * eventTarget attribute will be the only way to dispatch events to a
    * thread--nsIThread will no longer inherit from nsIEventTarget.
    */
   readonly attribute nsIEventTarget eventTarget;
 
   /**
--- a/xpcom/threads/nsIThreadInternal.idl
+++ b/xpcom/threads/nsIThreadInternal.idl
@@ -35,38 +35,16 @@ interface nsIThreadInternal : nsIThread
    */
   void addObserver(in nsIThreadObserver observer);
 
   /**
    * Remove an observer added via the addObserver call. Once removed the
    * observer will never be called again by the thread.
    */
   void removeObserver(in nsIThreadObserver observer);
-
-  /**
-   * This method causes any events currently enqueued on the thread to be
-   * suppressed until PopEventQueue is called, and any event dispatched to this
-   * thread's nsIEventTarget will queue as well. Calls to PushEventQueue may be
-   * nested and must each be paired with a call to PopEventQueue in order to
-   * restore the original state of the thread. The returned nsIEventTarget may
-   * be used to push events onto the nested queue. Dispatching will be disabled
-   * once the event queue is popped. The thread will only ever process pending
-   * events for the innermost event queue. Must only be called on the target
-   * thread.
-   */
-  [noscript] nsIEventTarget pushEventQueue();
-
-  /**
-   * Revert a call to PushEventQueue. When an event queue is popped, any events
-   * remaining in the queue are appended to the elder queue. This also causes
-   * the nsIEventTarget returned from PushEventQueue to stop dispatching events.
-   * Must only be called on the target thread, and with the innermost event
-   * queue.
-   */
-  [noscript] void popEventQueue(in nsIEventTarget aInnermostTarget);
 };
 
 /**
  * This interface provides the observer with hooks to implement a layered
  * event queue.  For example, it is possible to overlay processing events
  * for a GUI toolkit on top of the events for a thread:
  *
  *   var NativeQueue;
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -31,22 +31,20 @@
 #include "mozilla/SchedulerGroup.h"
 #include "mozilla/Services.h"
 #include "nsXPCOMPrivate.h"
 #include "mozilla/ChaosMode.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/ScriptSettings.h"
-#include "nsIIdlePeriod.h"
-#include "nsIIdleRunnable.h"
 #include "nsThreadSyncDispatch.h"
-#include "LeakRefPtr.h"
 #include "GeckoProfiler.h"
 #include "InputEventStatistics.h"
+#include "ThreadEventTarget.h"
 
 #ifdef MOZ_CRASHREPORTER
 #include "nsServiceManagerUtils.h"
 #include "nsICrashReporter.h"
 #include "mozilla/dom/ContentChild.h"
 #endif
 
 #ifdef XP_LINUX
@@ -230,93 +228,16 @@ private:
     return NS_OK;
   }
 
   ReentrantMonitor mMon;
   bool mInitialized;
 };
 //-----------------------------------------------------------------------------
 
-namespace {
-class DelayedRunnable : public Runnable,
-                        public nsITimerCallback
-{
-public:
-  DelayedRunnable(already_AddRefed<nsIThread> aTargetThread,
-                  already_AddRefed<nsIRunnable> aRunnable,
-                  uint32_t aDelay)
-    : mozilla::Runnable("DelayedRunnable")
-    , mTargetThread(aTargetThread)
-    , mWrappedRunnable(aRunnable)
-    , mDelayedFrom(TimeStamp::NowLoRes())
-    , mDelay(aDelay)
-  { }
-
-  NS_DECL_ISUPPORTS_INHERITED
-
-  nsresult Init()
-  {
-    nsresult rv;
-    mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    MOZ_ASSERT(mTimer);
-    rv = mTimer->SetTarget(mTargetThread);
-
-    NS_ENSURE_SUCCESS(rv, rv);
-    return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT);
-  }
-
-  nsresult DoRun()
-  {
-    nsCOMPtr<nsIRunnable> r = mWrappedRunnable.forget();
-    return r->Run();
-  }
-
-  NS_IMETHOD Run() override
-  {
-    // Already ran?
-    if (!mWrappedRunnable) {
-      return NS_OK;
-    }
-
-    // Are we too early?
-    if ((TimeStamp::NowLoRes() - mDelayedFrom).ToMilliseconds() < mDelay) {
-      return NS_OK; // Let the nsITimer run us.
-    }
-
-    mTimer->Cancel();
-    return DoRun();
-  }
-
-  NS_IMETHOD Notify(nsITimer* aTimer) override
-  {
-    // If we already ran, the timer should have been canceled.
-    MOZ_ASSERT(mWrappedRunnable);
-    MOZ_ASSERT(aTimer == mTimer);
-
-    return DoRun();
-  }
-
-private:
-  ~DelayedRunnable() {}
-
-  nsCOMPtr<nsIThread> mTargetThread;
-  nsCOMPtr<nsIRunnable> mWrappedRunnable;
-  nsCOMPtr<nsITimer> mTimer;
-  TimeStamp mDelayedFrom;
-  uint32_t mDelay;
-};
-
-NS_IMPL_ISUPPORTS_INHERITED(DelayedRunnable, Runnable, nsITimerCallback)
-
-} // anonymous namespace
-
-//-----------------------------------------------------------------------------
-
 struct nsThreadShutdownContext
 {
   nsThreadShutdownContext(NotNull<nsThread*> aTerminatingThread,
                           NotNull<nsThread*> aJoiningThread,
                           bool      aAwaitingShutdownAck)
     : mTerminatingThread(aTerminatingThread)
     , mJoiningThread(aJoiningThread)
     , mAwaitingShutdownAck(aAwaitingShutdownAck)
@@ -460,16 +381,17 @@ nsThread::ThreadFunc(void* aArg)
 
   char stackTop;
 
   ThreadInitData* initData = static_cast<ThreadInitData*>(aArg);
   nsThread* self = initData->thread;  // strong reference
 
   self->mThread = PR_GetCurrentThread();
   self->mVirtualThread = GetCurrentVirtualThread();
+  self->mEventTarget->SetCurrentThread();
   SetupCurrentThreadForChaosMode();
 
   if (!initData->name.IsEmpty()) {
     NS_SetCurrentThreadName(initData->name.BeginReading());
   }
 
   // Inform the ThreadManager
   nsThreadManager::get().RegisterCurrentThread(*self);
@@ -479,24 +401,18 @@ nsThread::ThreadFunc(void* aArg)
   // This must come after the call to nsThreadManager::RegisterCurrentThread(),
   // because that call is needed to properly set up this thread as an nsThread,
   // which profiler_register_thread() requires. See bug 1347007.
   if (!initData->name.IsEmpty()) {
     profiler_register_thread(initData->name.BeginReading(), &stackTop);
   }
 
   // Wait for and process startup event
-  nsCOMPtr<nsIRunnable> event;
-  {
-    MutexAutoLock lock(self->mLock);
-    if (!self->mEvents->GetEvent(true, getter_AddRefs(event), nullptr, lock)) {
-      NS_WARNING("failed waiting for thread startup event");
-      return;
-    }
-  }
+  nsCOMPtr<nsIRunnable> event = self->mEvents->GetEvent(true, nullptr);
+  MOZ_ASSERT(event);
 
   initData = nullptr; // clear before unblocking nsThread::Init
 
   event->Run();  // unblocks nsThread::Init
   event = nullptr;
 
   {
     // Scope for MessageLoop.
@@ -516,26 +432,18 @@ nsThread::ThreadFunc(void* aArg)
     // invariant here is that we will never permit PutEvent to succeed if the
     // event would be left in the queue after our final call to
     // NS_ProcessPendingEvents. We also have to keep processing events as long
     // as we have outstanding mRequestedShutdownContexts.
     while (true) {
       // Check and see if we're waiting on any threads.
       self->WaitForAllAsynchronousShutdowns();
 
-      {
-        MutexAutoLock lock(self->mLock);
-        if (!self->mEvents->HasPendingEvent(lock)) {
-          // No events in the queue, so we will stop now. Don't let any more
-          // events be added, since they won't be processed. It is critical
-          // that no PutEvent can occur between testing that the event queue is
-          // empty and setting mEventsAreDoomed!
-          self->mEventsAreDoomed = true;
-          break;
-        }
+      if (self->mEvents->ShutdownIfNoPendingEvents()) {
+        break;
       }
       NS_ProcessPendingEvents(self);
     }
   }
 
   mozilla::IOInterposer::UnregisterCurrentThread();
 
   // Inform the threadmanager that this thread is going away
@@ -628,34 +536,31 @@ nsThread::SaveMemoryReportNearOOM(Should
   return recentlySavedReport;
 }
 #endif
 
 #ifdef MOZ_CANARY
 int sCanaryOutputFD = -1;
 #endif
 
-nsThread::nsThread(MainThreadFlag aMainThread, uint32_t aStackSize)
-  : mLock("nsThread.mLock")
+nsThread::nsThread(NotNull<SynchronizedEventQueue*> aQueue,
+                   MainThreadFlag aMainThread,
+                   uint32_t aStackSize)
+  : mEvents(aQueue.get())
+  , mEventTarget(new ThreadEventTarget(mEvents.get(), aMainThread == MAIN_THREAD))
   , mScriptObserver(nullptr)
-  , mEvents(WrapNotNull(&mEventsRoot))
-  , mEventsRoot(mLock)
-  , mIdleEventsAvailable(mLock, "[nsThread.mEventsAvailable]")
-  , mIdleEvents(mIdleEventsAvailable, nsEventQueue::eNormalQueue)
   , mPriority(PRIORITY_NORMAL)
   , mThread(nullptr)
   , mNestedEventLoopDepth(0)
   , mStackSize(aStackSize)
   , mShutdownContext(nullptr)
   , mShutdownRequired(false)
-  , mEventsAreDoomed(false)
   , mIsMainThread(aMainThread)
   , mLastUnlabeledRunnable(TimeStamp::Now())
   , mCanInvokeJS(false)
-  , mHasPendingEventsPromisedIdleEvent(false)
 {
 }
 
 nsThread::~nsThread()
 {
   NS_ASSERTION(mRequestedShutdownContexts.IsEmpty(),
                "shouldn't be waiting on other threads to shutdown");
 #ifdef DEBUG
@@ -674,332 +579,80 @@ nsThread::~nsThread()
 nsresult
 nsThread::Init(const nsACString& aName)
 {
   // spawn thread and wait until it is fully setup
   RefPtr<nsThreadStartupEvent> startup = new nsThreadStartupEvent();
 
   NS_ADDREF_THIS();
 
-  mIdlePeriod = new IdlePeriod();
-
   mShutdownRequired = true;
 
   ThreadInitData initData = { this, aName };
 
   // ThreadFunc is responsible for setting mThread
   if (!PR_CreateThread(PR_USER_THREAD, ThreadFunc, &initData,
                        PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
                        PR_JOINABLE_THREAD, mStackSize)) {
     NS_RELEASE_THIS();
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   // ThreadFunc will wait for this event to be run before it tries to access
   // mThread.  By delaying insertion of this event into the queue, we ensure
   // that mThread is set properly.
   {
-    MutexAutoLock lock(mLock);
-    mEventsRoot.PutEvent(startup, lock); // retain a reference
+    mEvents->PutEvent(do_AddRef(startup), EventPriority::Normal); // retain a reference
   }
 
   // Wait for thread to call ThreadManager::SetupCurrentThread, which completes
   // initialization of ThreadFunc.
   startup->Wait();
   return NS_OK;
 }
 
 nsresult
 nsThread::InitCurrentThread()
 {
   mThread = PR_GetCurrentThread();
   mVirtualThread = GetCurrentVirtualThread();
   SetupCurrentThreadForChaosMode();
 
-  mIdlePeriod = new IdlePeriod();
-
   nsThreadManager::get().RegisterCurrentThread(*this);
   return NS_OK;
 }
 
-nsresult
-nsThread::PutEvent(nsIRunnable* aEvent, nsNestedEventTarget* aTarget)
-{
-  nsCOMPtr<nsIRunnable> event(aEvent);
-  return PutEvent(event.forget(), aTarget);
-}
-
-nsresult
-nsThread::PutEvent(already_AddRefed<nsIRunnable> aEvent, nsNestedEventTarget* aTarget)
-{
-  // We want to leak the reference when we fail to dispatch it, so that
-  // we won't release the event in a wrong thread.
-  LeakRefPtr<nsIRunnable> event(Move(aEvent));
-  nsCOMPtr<nsIThreadObserver> obs;
-
-  {
-    MutexAutoLock lock(mLock);
-    nsChainedEventQueue* queue = aTarget ? aTarget->mQueue : &mEventsRoot;
-    if (!queue || (queue == &mEventsRoot && mEventsAreDoomed)) {
-      NS_WARNING("An event was posted to a thread that will never run it (rejected)");
-      return NS_ERROR_UNEXPECTED;
-    }
-    queue->PutEvent(event.take(), lock);
-
-    // Make sure to grab the observer before dropping the lock, otherwise the
-    // event that we just placed into the queue could run and eventually delete
-    // this nsThread before the calling thread is scheduled again. We would then
-    // crash while trying to access a dead nsThread.
-    obs = mObserver;
-  }
-
-  if (obs) {
-    obs->OnDispatchedEvent();
-  }
-
-  return NS_OK;
-}
-
-nsresult
-nsThread::DispatchInternal(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags,
-                           nsNestedEventTarget* aTarget)
-{
-  // We want to leak the reference when we fail to dispatch it, so that
-  // we won't release the event in a wrong thread.
-  LeakRefPtr<nsIRunnable> event(Move(aEvent));
-  if (NS_WARN_IF(!event)) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  if (gXPCOMThreadsShutDown && MAIN_THREAD != mIsMainThread && !aTarget) {
-    NS_ASSERTION(false, "Failed Dispatch after xpcom-shutdown-threads");
-    return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
-  }
-
-#ifdef MOZ_TASK_TRACER
-  nsCOMPtr<nsIRunnable> tracedRunnable = CreateTracedRunnable(event.take());
-  (static_cast<TracedRunnable*>(tracedRunnable.get()))->DispatchTask();
-  // XXX tracedRunnable will always leaked when we fail to disptch.
-  event = tracedRunnable.forget();
-#endif
-
-  if (aFlags & DISPATCH_SYNC) {
-    nsThread* thread = nsThreadManager::get().GetCurrentThread();
-    if (NS_WARN_IF(!thread)) {
-      return NS_ERROR_NOT_AVAILABLE;
-    }
-
-    // XXX we should be able to do something better here... we should
-    //     be able to monitor the slot occupied by this event and use
-    //     that to tell us when the event has been processed.
-
-    RefPtr<nsThreadSyncDispatch> wrapper =
-      new nsThreadSyncDispatch(thread, event.take());
-    nsresult rv = PutEvent(wrapper, aTarget); // hold a ref
-    // Don't wait for the event to finish if we didn't dispatch it...
-    if (NS_FAILED(rv)) {
-      // PutEvent leaked the wrapper runnable object on failure, so we
-      // explicitly release this object once for that. Note that this
-      // object will be released again soon because it exits the scope.
-      wrapper.get()->Release();
-      return rv;
-    }
-
-    // Allows waiting; ensure no locks are held that would deadlock us!
-    SpinEventLoopUntil([&, wrapper]() -> bool {
-        return !wrapper->IsPending();
-      }, thread);
-
-    return NS_OK;
-  }
-
-  NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL ||
-               aFlags == NS_DISPATCH_AT_END, "unexpected dispatch flags");
-  return PutEvent(event.take(), aTarget);
-}
-
-NS_IMPL_ISUPPORTS(nsThread::nsChainedEventQueue::EnablePrioritizationRunnable,
-                  nsIRunnable)
-
-void
-nsThread::nsChainedEventQueue::EnablePrioritization(MutexAutoLock& aProofOfLock)
-{
-  MOZ_ASSERT(!mIsInputPrioritizationEnabled);
-  // When enabling event prioritization, there may be some pending events with
-  // different priorities in the normal queue. Create an event in the normal
-  // queue to consume all pending events in the time order to make sure we won't
-  // preempt a pending event (e.g. input) in the normal queue by another newly
-  // created event with the same priority.
-  mNormalQueue->PutEvent(new EnablePrioritizationRunnable(this), aProofOfLock);
-  mInputHandlingStartTime = TimeStamp();
-  mIsInputPrioritizationEnabled = true;
-}
-
-bool
-nsThread::nsChainedEventQueue::
-GetNormalOrInputOrHighPriorityEvent(bool aMayWait, nsIRunnable** aEvent,
-                                    unsigned short* aPriority,
-                                    MutexAutoLock& aProofOfLock)
-{
-  bool retVal = false;
-  do {
-    // Use mProcessHighPriorityQueueRunnable to prevent the high priority events
-    // from consuming all cpu time and causing starvation.
-    if (mProcessHighPriorityQueueRunnable) {
-      MOZ_ASSERT(mHighQueue->HasPendingEvent(aProofOfLock));
-      retVal = mHighQueue->GetEvent(false, aEvent, aProofOfLock);
-      MOZ_ASSERT(*aEvent);
-      SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_HIGH);
-      mInputHandlingStartTime = TimeStamp();
-      mProcessHighPriorityQueueRunnable = false;
-      return retVal;
-    }
-    mProcessHighPriorityQueueRunnable =
-      mHighQueue->HasPendingEvent(aProofOfLock);
-
-    uint32_t pendingInputCount = mInputQueue->Count(aProofOfLock);
-    if (pendingInputCount > 0) {
-      if (mInputHandlingStartTime.IsNull()) {
-        mInputHandlingStartTime =
-          InputEventStatistics::Get()
-            .GetInputHandlingStartTime(mInputQueue->Count(aProofOfLock));
-      }
-      if (TimeStamp::Now() > mInputHandlingStartTime) {
-        retVal = mInputQueue->GetEvent(false, aEvent, aProofOfLock);
-        MOZ_ASSERT(*aEvent);
-        SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_INPUT);
-        return retVal;
-      }
-    }
-
-    // We don't want to wait if there are some high priority events or input
-    // events in the queues.
-    bool reallyMayWait = aMayWait && !mProcessHighPriorityQueueRunnable &&
-                         pendingInputCount == 0;
-
-    retVal = mNormalQueue->GetEvent(reallyMayWait, aEvent, aProofOfLock);
-    if (*aEvent) {
-      // We got an event, return early.
-      SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_NORMAL);
-      return retVal;
-    }
-    if (pendingInputCount > 0 && !mProcessHighPriorityQueueRunnable) {
-      // Handle input events if we have time for them.
-      MOZ_ASSERT(mInputQueue->HasPendingEvent(aProofOfLock));
-      retVal = mInputQueue->GetEvent(false, aEvent, aProofOfLock);
-      MOZ_ASSERT(*aEvent);
-      SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_INPUT);
-      return retVal;
-    }
-  } while (aMayWait || mProcessHighPriorityQueueRunnable);
-  return retVal;
-}
-
-bool
-nsThread::nsChainedEventQueue::
-GetNormalOrHighPriorityEvent(bool aMayWait, nsIRunnable** aEvent,
-                             unsigned short* aPriority,
-                             MutexAutoLock& aProofOfLock)
-{
-  bool retVal = false;
-  do {
-    // Use mProcessHighPriorityQueueRunnable to prevent the high priority events
-    // from consuming all cpu time and causing starvation.
-    if (mProcessHighPriorityQueueRunnable) {
-      MOZ_ASSERT(mHighQueue->HasPendingEvent(aProofOfLock));
-      retVal = mHighQueue->GetEvent(false, aEvent, aProofOfLock);
-      MOZ_ASSERT(*aEvent);
-      SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_HIGH);
-      mProcessHighPriorityQueueRunnable = false;
-      return retVal;
-    }
-    mProcessHighPriorityQueueRunnable =
-      mHighQueue->HasPendingEvent(aProofOfLock);
-
-    // We don't want to wait if there are some events in the high priority
-    // queue.
-    bool reallyMayWait = aMayWait && !mProcessHighPriorityQueueRunnable;
-
-    retVal = mNormalQueue->GetEvent(reallyMayWait, aEvent, aProofOfLock);
-    if (*aEvent) {
-      // We got an event, return early.
-      SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_NORMAL);
-      return retVal;
-    }
-  } while (aMayWait || mProcessHighPriorityQueueRunnable);
-  return retVal;
-}
-
-void
-nsThread::nsChainedEventQueue::PutEvent(already_AddRefed<nsIRunnable> aEvent,
-                                        MutexAutoLock& aProofOfLock)
-{
-  RefPtr<nsIRunnable> event(aEvent);
-  nsCOMPtr<nsIRunnablePriority> runnablePrio(do_QueryInterface(event));
-  uint32_t prio = nsIRunnablePriority::PRIORITY_NORMAL;
-  if (runnablePrio) {
-    runnablePrio->GetPriority(&prio);
-  }
-  switch (prio) {
-  case nsIRunnablePriority::PRIORITY_NORMAL:
-    mNormalQueue->PutEvent(event.forget(), aProofOfLock);
-    break;
-  case nsIRunnablePriority::PRIORITY_INPUT:
-    if (mIsInputPrioritizationEnabled) {
-      mInputQueue->PutEvent(event.forget(), aProofOfLock);
-    } else {
-      mNormalQueue->PutEvent(event.forget(), aProofOfLock);
-    }
-    break;
-  case nsIRunnablePriority::PRIORITY_HIGH:
-    mHighQueue->PutEvent(event.forget(), aProofOfLock);
-    break;
-  default:
-    MOZ_ASSERT(false);
-    break;
-  }
-}
-
 //-----------------------------------------------------------------------------
 // nsIEventTarget
 
 NS_IMETHODIMP
 nsThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
 {
   nsCOMPtr<nsIRunnable> event(aEvent);
-  return Dispatch(event.forget(), aFlags);
+  return mEventTarget->Dispatch(event.forget(), aFlags);
 }
 
 NS_IMETHODIMP
 nsThread::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
 {
   LOG(("THRD(%p) Dispatch [%p %x]\n", this, /* XXX aEvent */nullptr, aFlags));
 
-  return DispatchInternal(Move(aEvent), aFlags, nullptr);
+  return mEventTarget->Dispatch(Move(aEvent), aFlags);
 }
 
 NS_IMETHODIMP
 nsThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aDelayMs)
 {
-  NS_ENSURE_TRUE(!!aDelayMs, NS_ERROR_UNEXPECTED);
-
-  RefPtr<DelayedRunnable> r = new DelayedRunnable(Move(do_AddRef(this)),
-                                                  Move(aEvent),
-                                                  aDelayMs);
-  nsresult rv = r->Init();
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return DispatchInternal(r.forget(), 0, nullptr);
+  return mEventTarget->DelayedDispatch(Move(aEvent), aDelayMs);
 }
 
 NS_IMETHODIMP
 nsThread::IsOnCurrentThread(bool* aResult)
 {
-  *aResult = (PR_GetCurrentThread() == mThread);
-  return NS_OK;
+  return mEventTarget->IsOnCurrentThread(aResult);
 }
 
 NS_IMETHODIMP_(bool)
 nsThread::IsOnCurrentThreadInfallible()
 {
   // Rely on mVirtualThread being correct.
   MOZ_CRASH("IsOnCurrentThreadInfallible should never be called on nsIThread");
 }
@@ -1048,37 +701,33 @@ nsThread::ShutdownInternal(bool aSync)
 {
   MOZ_ASSERT(mThread);
   MOZ_ASSERT(mThread != PR_GetCurrentThread());
   if (NS_WARN_IF(mThread == PR_GetCurrentThread())) {
     return nullptr;
   }
 
   // Prevent multiple calls to this method
-  {
-    MutexAutoLock lock(mLock);
-    if (!mShutdownRequired) {
-      return nullptr;
-    }
-    mShutdownRequired = false;
+  if (!mShutdownRequired.compareExchange(true, false)) {
+    return nullptr;
   }
 
   NotNull<nsThread*> currentThread =
     WrapNotNull(nsThreadManager::get().GetCurrentThread());
 
   nsAutoPtr<nsThreadShutdownContext>& context =
     *currentThread->mRequestedShutdownContexts.AppendElement();
   context = new nsThreadShutdownContext(WrapNotNull(this), currentThread, aSync);
 
   // Set mShutdownContext and wake up the thread in case it is waiting for
   // events to process.
   nsCOMPtr<nsIRunnable> event =
     new nsThreadShutdownEvent(WrapNotNull(this), WrapNotNull(context.get()));
   // XXXroc What if posting the event fails due to OOM?
-  PutEvent(event.forget(), nullptr);
+  mEvents->PutEvent(event.forget(), EventPriority::Normal);
 
   // We could still end up with other events being added after the shutdown
   // task, but that's okay because we process pending events in ThreadFunc
   // after setting mShutdownContext just before exiting.
   return context;
 }
 
 void
@@ -1100,20 +749,18 @@ nsThread::ShutdownComplete(NotNull<nsThr
   mThread = nullptr;
 
   // We hold strong references to our event observers, and once the thread is
   // shut down the observers can't easily unregister themselves. Do it here
   // to avoid leaking.
   ClearObservers();
 
 #ifdef DEBUG
-  {
-    MutexAutoLock lock(mLock);
-    MOZ_ASSERT(!mObserver, "Should have been cleared at shutdown!");
-  }
+  nsCOMPtr<nsIThreadObserver> obs = mEvents->GetObserver();
+  MOZ_ASSERT(!obs, "Should have been cleared at shutdown!");
 #endif
 
   // Delete aContext.
   MOZ_ALWAYS_TRUE(
     aContext->mJoiningThread->mRequestedShutdownContexts.RemoveElement(aContext));
 }
 
 void
@@ -1148,145 +795,47 @@ nsThread::Shutdown()
       return !context->mAwaitingShutdownAck;
     }, context->mJoiningThread);
 
   ShutdownComplete(context);
 
   return NS_OK;
 }
 
-TimeStamp
-nsThread::GetIdleDeadline()
-{
-  // If we are shutting down, we won't honor the idle period, and we will
-  // always process idle runnables.  This will ensure that the idle queue
-  // gets exhausted at shutdown time to prevent intermittently leaking
-  // some runnables inside that queue and even worse potentially leaving
-  // some important cleanup work unfinished.
-  // Note that we need to check both of these conditions since ShuttingDown()
-  // will never return true on the main thread, where gXPCOMThreadsShutDown
-  // performs a similar function.
-  if (gXPCOMThreadsShutDown || ShuttingDown()) {
-    return TimeStamp::Now();
-  }
-
-  TimeStamp idleDeadline;
-  {
-    // Releasing the lock temporarily since getting the idle period
-    // might need to lock the timer thread. Unlocking here might make
-    // us receive an event on the main queue, but we've committed to
-    // run an idle event anyhow.
-    MutexAutoUnlock unlock(mLock);
-    mIdlePeriod->GetIdlePeriodHint(&idleDeadline);
-  }
-
-  // If HasPendingEvents() has been called and it has returned true because of
-  // pending idle events, there is a risk that we may decide here that we aren't
-  // idle and return null, in which case HasPendingEvents() has effectively
-  // lied.  Since we can't go back and fix the past, we have to adjust what we
-  // do here and forcefully pick the idle queue task here.  Note that this means
-  // that we are choosing to run a task from the idle queue when we would
-  // normally decide that we aren't in an idle period, but this can only happen
-  // if we fall out of the idle period in between the call to HasPendingEvents()
-  // and here, which should hopefully be quite rare.  We are effectively
-  // choosing to prioritize the sanity of our API semantics over the optimal
-  // scheduling.
-  if (!mHasPendingEventsPromisedIdleEvent &&
-      (!idleDeadline || idleDeadline < TimeStamp::Now())) {
-    return TimeStamp();
-  }
-  if (mHasPendingEventsPromisedIdleEvent && !idleDeadline) {
-    // If HasPendingEvents() has been called and it has returned true, but we're no
-    // longer in the idle period, we must return a valid timestamp to pretend that
-    // we are still in the idle period.
-    return TimeStamp::Now();
-  }
-  return idleDeadline;
-}
-
 NS_IMETHODIMP
 nsThread::HasPendingEvents(bool* aResult)
 {
   if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
     return NS_ERROR_NOT_SAME_THREAD;
   }
 
-  {
-    MutexAutoLock lock(mLock);
-    mHasPendingEventsPromisedIdleEvent = false;
-    bool hasPendingEvent = mEvents->HasPendingEvent(lock);
-    bool hasPendingIdleEvent = false;
-    if (!hasPendingEvent) {
-      // Note that GetIdleDeadline() checks mHasPendingEventsPromisedIdleEvent,
-      // but that's OK since we set it to false in the beginning of this method!
-      TimeStamp idleDeadline = GetIdleDeadline();
-
-      // Only examine the idle queue if we are in an idle period.
-      if (idleDeadline) {
-        hasPendingIdleEvent = mIdleEvents.HasPendingEvent(lock);
-        mHasPendingEventsPromisedIdleEvent = hasPendingIdleEvent;
-      }
-    }
-    *aResult = hasPendingEvent || hasPendingIdleEvent;
-  }
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsThread::RegisterIdlePeriod(already_AddRefed<nsIIdlePeriod> aIdlePeriod)
-{
-  if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
-    return NS_ERROR_NOT_SAME_THREAD;
-  }
-
-  MutexAutoLock lock(mLock);
-  mIdlePeriod = aIdlePeriod;
+  *aResult = mEvents->HasPendingEvent();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsThread::IdleDispatch(already_AddRefed<nsIRunnable> aEvent)
 {
   // Currently the only supported idle dispatch is from the same
   // thread. To support idle dispatch from another thread we need to
   // support waking threads that are waiting for an event queue that
   // isn't mIdleEvents.
   MOZ_ASSERT(PR_GetCurrentThread() == mThread);
 
-  MutexAutoLock lock(mLock);
-  LeakRefPtr<nsIRunnable> event(Move(aEvent));
+  nsCOMPtr<nsIRunnable> event = aEvent;
 
   if (NS_WARN_IF(!event)) {
     return NS_ERROR_INVALID_ARG;
   }
 
-  if (mEventsAreDoomed) {
+  if (!mEvents->PutEvent(event.forget(), EventPriority::Idle)) {
     NS_WARNING("An idle event was posted to a thread that will never run it (rejected)");
     return NS_ERROR_UNEXPECTED;
   }
 
-  mIdleEvents.PutEvent(event.take(), lock);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsThread::EnableEventPrioritization()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MutexAutoLock lock(mLock);
-  // Only support event prioritization for main event queue.
-  mEventsRoot.EnablePrioritization(lock);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsThread::IsEventPrioritizationEnabled(bool* aResult)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  *aResult = mEventsRoot.IsPrioritizationEnabled();
   return NS_OK;
 }
 
 #ifdef MOZ_CANARY
 void canary_alarm_handler(int signum);
 
 class Canary
 {
@@ -1332,97 +881,16 @@ void canary_alarm_handler(int signum)
       nsCOMPtr<nsIThreadObserver> obs_;                                        \
       while (iter_.HasMore()) {                                                \
         obs_ = iter_.GetNext();                                                \
         obs_ -> func_ params_ ;                                                \
       }                                                                        \
     }                                                                          \
   } while(0)
 
-void
-nsThread::GetIdleEvent(nsIRunnable** aEvent, MutexAutoLock& aProofOfLock)
-{
-  MOZ_ASSERT(PR_GetCurrentThread() == mThread);
-  MOZ_ASSERT(aEvent);
-
-  if (!mIdleEvents.HasPendingEvent(aProofOfLock)) {
-    MOZ_ASSERT(!mHasPendingEventsPromisedIdleEvent);
-    aEvent = nullptr;
-    return;
-  }
-
-  TimeStamp idleDeadline = GetIdleDeadline();
-  if (!idleDeadline) {
-    aEvent = nullptr;
-    return;
-  }
-
-  mIdleEvents.GetEvent(false, aEvent, aProofOfLock);
-
-  if (*aEvent) {
-    nsCOMPtr<nsIIdleRunnable> idleEvent(do_QueryInterface(*aEvent));
-    if (idleEvent) {
-      idleEvent->SetDeadline(idleDeadline);
-    }
-
-#ifndef RELEASE_OR_BETA
-    // Store the next idle deadline to be able to determine budget use
-    // in ProcessNextEvent.
-    mNextIdleDeadline = idleDeadline;
-#endif
-  }
-}
-
-void
-nsThread::GetEvent(bool aWait, nsIRunnable** aEvent,
-                   unsigned short* aPriority,
-                   MutexAutoLock& aProofOfLock)
-{
-  MOZ_ASSERT(PR_GetCurrentThread() == mThread);
-  MOZ_ASSERT(aEvent);
-
-  MakeScopeExit([&] {
-    mHasPendingEventsPromisedIdleEvent = false;
-  });
-
-#ifndef RELEASE_OR_BETA
-  // Clear mNextIdleDeadline so that it is possible to determine that
-  // we're running an idle runnable in ProcessNextEvent.
-  mNextIdleDeadline = TimeStamp();
-#endif
-
-  // We'll try to get an event to execute in three stages.
-  // [1] First we just try to get it from the regular queue without waiting.
-  mEvents->GetEvent(false, aEvent, aPriority, aProofOfLock);
-
-  // [2] If we didn't get an event from the regular queue, try to
-  // get one from the idle queue
-  if (!*aEvent) {
-    // Since events in mEvents have higher priority than idle
-    // events, we will only consider idle events when there are no
-    // pending events in mEvents. We will for the same reason never
-    // wait for an idle event, since a higher priority event might
-    // appear at any time.
-    GetIdleEvent(aEvent, aProofOfLock);
-
-    if (*aEvent && aPriority) {
-      // Idle events count as normal priority.
-      *aPriority = nsIRunnablePriority::PRIORITY_NORMAL;
-    }
-  }
-
-  // [3] If we neither got an event from the regular queue nor the
-  // idle queue, then if we should wait for events we block on the
-  // main queue until an event is available.
-  // If we are shutting down, then do not wait for new events.
-  if (!*aEvent && aWait) {
-    mEvents->GetEvent(aWait, aEvent, aPriority, aProofOfLock);
-  }
-}
-
 #ifndef RELEASE_OR_BETA
 static bool
 GetLabeledRunnableName(nsIRunnable* aEvent, nsACString& aName)
 {
   bool labeled = false;
   if (RefPtr<SchedulerGroup::Runnable> groupRunnable = do_QueryObject(aEvent)) {
     labeled = true;
     MOZ_ALWAYS_TRUE(NS_SUCCEEDED(groupRunnable->GetName(aName)));
@@ -1472,38 +940,34 @@ nsThread::ProcessNextEvent(bool aMayWait
   // mScriptObserver.
   Maybe<dom::AutoNoJSAPI> noJSAPI;
   bool callScriptObserver = !!mScriptObserver;
   if (callScriptObserver) {
     noJSAPI.emplace();
     mScriptObserver->BeforeProcessTask(reallyWait);
   }
 
-  nsCOMPtr<nsIThreadObserver> obs = mObserver;
+  nsCOMPtr<nsIThreadObserver> obs = mEvents->GetObserverOnThread();
   if (obs) {
     obs->OnProcessNextEvent(this, reallyWait);
   }
 
   NOTIFY_EVENT_OBSERVERS(OnProcessNextEvent, (this, reallyWait));
 
 #ifdef MOZ_CANARY
   Canary canary;
 #endif
   nsresult rv = NS_OK;
 
   {
     // Scope for |event| to make sure that its destructor fires while
     // mNestedEventLoopDepth has been incremented, since that destructor can
     // also do work.
-    nsCOMPtr<nsIRunnable> event;
-    unsigned short priority;
-    {
-      MutexAutoLock lock(mLock);
-      GetEvent(reallyWait, getter_AddRefs(event), &priority, lock);
-    }
+    EventPriority priority;
+    nsCOMPtr<nsIRunnable> event = mEvents->GetEvent(reallyWait, &priority);
 
     *aResult = (event.get() != nullptr);
 
     if (event) {
       LOG(("THRD(%p) running [%p]\n", this, event.get()));
 
       if (MAIN_THREAD == mIsMainThread) {
         HangMonitor::NotifyActivity();
@@ -1517,17 +981,17 @@ nsThread::ProcessNextEvent(bool aMayWait
       if ((MAIN_THREAD == mIsMainThread) || mNextIdleDeadline) {
         bool labeled = GetLabeledRunnableName(event, name);
 
         if (MAIN_THREAD == mIsMainThread) {
           timer.emplace(name);
 
           // High-priority runnables are ignored here since they'll run right away
           // even with the cooperative scheduler.
-          if (!labeled && priority == nsIRunnablePriority::PRIORITY_NORMAL) {
+          if (!labeled && priority == EventPriority::Normal) {
             TimeStamp now = TimeStamp::Now();
             double diff = (now - mLastUnlabeledRunnable).ToMilliseconds();
             Telemetry::Accumulate(Telemetry::TIME_BETWEEN_UNLABELED_RUNNABLES_MS, diff);
             mLastUnlabeledRunnable = now;
           }
         }
 
         if (mNextIdleDeadline) {
@@ -1558,17 +1022,17 @@ nsThread::ProcessNextEvent(bool aMayWait
         // terminating null.
         uint32_t length = std::min((uint32_t) kRunnableNameBufSize - 1,
                                    (uint32_t) name.Length());
         memcpy(sMainThreadRunnableName.begin(), name.BeginReading(), length);
         sMainThreadRunnableName[length] = '\0';
       }
 #endif
       Maybe<AutoTimeDurationHelper> timeDurationHelper;
-      if (priority == nsIRunnablePriority::PRIORITY_INPUT) {
+      if (priority == EventPriority::Input) {
         timeDurationHelper.emplace();
       }
       event->Run();
     } else if (aMayWait) {
       MOZ_ASSERT(ShuttingDown(),
                  "This should only happen when shutting down");
       rv = NS_ERROR_UNEXPECTED;
     }
@@ -1643,30 +1107,29 @@ nsThread::AdjustPriority(int32_t aDelta)
 }
 
 //-----------------------------------------------------------------------------
 // nsIThreadInternal
 
 NS_IMETHODIMP
 nsThread::GetObserver(nsIThreadObserver** aObs)
 {
-  MutexAutoLock lock(mLock);
-  NS_IF_ADDREF(*aObs = mObserver);
+  nsCOMPtr<nsIThreadObserver> obs = mEvents->GetObserver();
+  obs.forget(aObs);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsThread::SetObserver(nsIThreadObserver* aObs)
 {
   if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
     return NS_ERROR_NOT_SAME_THREAD;
   }
 
-  MutexAutoLock lock(mLock);
-  mObserver = aObs;
+  mEvents->SetObserver(aObs);
   return NS_OK;
 }
 
 uint32_t
 nsThread::RecursionDepth() const
 {
   MOZ_ASSERT(PR_GetCurrentThread() == mThread);
   return mNestedEventLoopDepth;
@@ -1702,78 +1165,16 @@ nsThread::RemoveObserver(nsIThreadObserv
 
   if (aObserver && !mEventObservers.RemoveElement(aObserver)) {
     NS_WARNING("Removing an observer that was never added!");
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsThread::PushEventQueue(nsIEventTarget** aResult)
-{
-  if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
-    return NS_ERROR_NOT_SAME_THREAD;
-  }
-
-  NotNull<nsChainedEventQueue*> queue =
-    WrapNotNull(new nsChainedEventQueue(mLock));
-  queue->mEventTarget = new nsNestedEventTarget(WrapNotNull(this), queue);
-
-  {
-    MutexAutoLock lock(mLock);
-    queue->mNext = mEvents;
-    mEvents = queue;
-  }
-
-  NS_ADDREF(*aResult = queue->mEventTarget);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsThread::PopEventQueue(nsIEventTarget* aInnermostTarget)
-{
-  if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
-    return NS_ERROR_NOT_SAME_THREAD;
-  }
-
-  if (NS_WARN_IF(!aInnermostTarget)) {
-    return NS_ERROR_NULL_POINTER;
-  }
-
-  // Don't delete or release anything while holding the lock.
-  nsAutoPtr<nsChainedEventQueue> queue;
-  RefPtr<nsNestedEventTarget> target;
-
-  {
-    MutexAutoLock lock(mLock);
-
-    // Make sure we're popping the innermost event target.
-    if (NS_WARN_IF(mEvents->mEventTarget != aInnermostTarget)) {
-      return NS_ERROR_UNEXPECTED;
-    }
-
-    MOZ_ASSERT(mEvents != &mEventsRoot);
-
-    queue = mEvents;
-    mEvents = WrapNotNull(mEvents->mNext);
-
-    nsCOMPtr<nsIRunnable> event;
-    while (queue->GetEvent(false, getter_AddRefs(event), nullptr, lock)) {
-      mEvents->PutEvent(event.forget(), lock);
-    }
-
-    // Don't let the event target post any more events.
-    queue->mEventTarget.swap(target);
-    target->mQueue = nullptr;
-  }
-
-  return NS_OK;
-}
-
 void
 nsThread::SetScriptObserver(mozilla::CycleCollectedJSContext* aScriptObserver)
 {
   if (!aScriptObserver) {
     mScriptObserver = nullptr;
     return;
   }
 
@@ -1831,46 +1232,8 @@ nsThread::EventTarget()
   return this;
 }
 
 nsISerialEventTarget*
 nsThread::SerialEventTarget()
 {
   return this;
 }
-
-//-----------------------------------------------------------------------------
-
-NS_IMPL_ISUPPORTS(nsThread::nsNestedEventTarget, nsIEventTarget)
-
-NS_IMETHODIMP
-nsThread::nsNestedEventTarget::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
-{
-  nsCOMPtr<nsIRunnable> event(aEvent);
-  return Dispatch(event.forget(), aFlags);
-}
-
-NS_IMETHODIMP
-nsThread::nsNestedEventTarget::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
-{
-  LOG(("THRD(%p) Dispatch [%p %x] to nested loop %p\n", mThread.get().get(),
-       /*XXX aEvent*/ nullptr, aFlags, this));
-
-  return mThread->DispatchInternal(Move(aEvent), aFlags, this);
-}
-
-NS_IMETHODIMP
-nsThread::nsNestedEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-NS_IMETHODIMP
-nsThread::nsNestedEventTarget::IsOnCurrentThread(bool* aResult)
-{
-  return mThread->IsOnCurrentThread(aResult);
-}
-
-NS_IMETHODIMP_(bool)
-nsThread::nsNestedEventTarget::IsOnCurrentThreadInfallible()
-{
-  return mThread->IsOnCurrentThread();
-}
--- a/xpcom/threads/nsThread.h
+++ b/xpcom/threads/nsThread.h
@@ -6,30 +6,31 @@
 
 #ifndef nsThread_h__
 #define nsThread_h__
 
 #include "mozilla/Mutex.h"
 #include "nsIIdlePeriod.h"
 #include "nsIThreadInternal.h"
 #include "nsISupportsPriority.h"
-#include "nsEventQueue.h"
 #include "nsThreadUtils.h"
 #include "nsString.h"
 #include "nsTObserverArray.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/SynchronizedEventQueue.h"
 #include "mozilla/NotNull.h"
 #include "mozilla/TimeStamp.h"
 #include "nsAutoPtr.h"
 #include "mozilla/AlreadyAddRefed.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/Array.h"
 
 namespace mozilla {
 class CycleCollectedJSContext;
+class ThreadEventTarget;
 }
 
 using mozilla::NotNull;
 
 // A native thread
 class nsThread
   : public nsIThreadInternal
   , public nsISupportsPriority
@@ -42,17 +43,19 @@ public:
   NS_DECL_NSISUPPORTSPRIORITY
 
   enum MainThreadFlag
   {
     MAIN_THREAD,
     NOT_MAIN_THREAD
   };
 
-  nsThread(MainThreadFlag aMainThread, uint32_t aStackSize);
+  nsThread(NotNull<mozilla::SynchronizedEventQueue*> aQueue,
+           MainThreadFlag aMainThread,
+           uint32_t aStackSize);
 
   // Initialize this as a wrapper for a new PRThread, and optionally give it a name.
   nsresult Init(const nsACString& aName = NS_LITERAL_CSTRING(""));
 
   // Initialize this as a wrapper for the current PRThread.
   nsresult InitCurrentThread();
 
   // The PRThread corresponding to this thread.
@@ -92,272 +95,77 @@ public:
   };
 
   static bool SaveMemoryReportNearOOM(ShouldSaveMemoryReport aShouldSave);
 #endif
 
   static const uint32_t kRunnableNameBufSize = 1000;
   static mozilla::Array<char, kRunnableNameBufSize> sMainThreadRunnableName;
 
-  // Query whether there are some pending input events in the queue. This method
-  // is supposed to be called on main thread with input event prioritization
-  // enabled.
-  bool HasPendingInputEvents()
+  void EnableInputEventPrioritization()
   {
-    MOZ_ASSERT(NS_IsMainThread());
-    mozilla::MutexAutoLock lock(mLock);
-    return mEventsRoot.HasPendingEventsInInputQueue(lock);
+    EventQueue()->EnableInputEventPrioritization();
+  }
+
+  mozilla::TimeStamp& NextIdleDeadlineRef() { return mNextIdleDeadline; }
+
+  mozilla::SynchronizedEventQueue* EventQueue() { return mEvents.get(); }
+
+  bool ShuttingDown()
+  {
+    return mShutdownContext != nullptr;
   }
 
 private:
   void DoMainThreadSpecificProcessing(bool aReallyWait);
 
-  // Returns a null TimeStamp if we're not in the idle period.
-  mozilla::TimeStamp GetIdleDeadline();
-  void GetIdleEvent(nsIRunnable** aEvent, mozilla::MutexAutoLock& aProofOfLock);
-  void GetEvent(bool aWait, nsIRunnable** aEvent,
-                unsigned short* aPriority,
-                mozilla::MutexAutoLock& aProofOfLock);
-
 protected:
-  class nsChainedEventQueue;
-
-  class nsNestedEventTarget;
-  friend class nsNestedEventTarget;
-
   friend class nsThreadShutdownEvent;
 
   virtual ~nsThread();
 
-  bool ShuttingDown()
-  {
-    return mShutdownContext != nullptr;
-  }
-
   static void ThreadFunc(void* aArg);
 
   // Helper
   already_AddRefed<nsIThreadObserver> GetObserver()
   {
     nsIThreadObserver* obs;
     nsThread::GetObserver(&obs);
     return already_AddRefed<nsIThreadObserver>(obs);
   }
 
-  // Wrappers for event queue methods:
-  nsresult PutEvent(nsIRunnable* aEvent, nsNestedEventTarget* aTarget);
-  nsresult PutEvent(already_AddRefed<nsIRunnable> aEvent,
-                    nsNestedEventTarget* aTarget);
-
-  nsresult DispatchInternal(already_AddRefed<nsIRunnable> aEvent,
-                            uint32_t aFlags, nsNestedEventTarget* aTarget);
-
   struct nsThreadShutdownContext* ShutdownInternal(bool aSync);
 
-  // Wrapper for nsEventQueue that supports chaining and prioritization.
-  class nsChainedEventQueue
-  {
-  public:
-    explicit nsChainedEventQueue(mozilla::Mutex& aLock)
-      : mNext(nullptr)
-      , mEventsAvailable(aLock, "[nsChainedEventQueue.mEventsAvailable]")
-      , mIsInputPrioritizationEnabled(false)
-      , mIsReadyToPrioritizeEvents(false)
-      , mProcessHighPriorityQueueRunnable(false)
-    {
-      mNormalQueue =
-        mozilla::MakeUnique<nsEventQueue>(mEventsAvailable,
-                                          nsEventQueue::eSharedCondVarQueue);
-      // All queues need to use the same CondVar!
-      mInputQueue =
-        mozilla::MakeUnique<nsEventQueue>(mEventsAvailable,
-                                          nsEventQueue::eSharedCondVarQueue);
-      mHighQueue =
-        mozilla::MakeUnique<nsEventQueue>(mEventsAvailable,
-                                          nsEventQueue::eSharedCondVarQueue);
-    }
-
-    void EnablePrioritization(mozilla::MutexAutoLock& aProofOfLock);
-
-    bool IsPrioritizationEnabled()
-    {
-      return mIsInputPrioritizationEnabled;
-    }
-
-    bool GetEvent(bool aMayWait, nsIRunnable** aEvent,
-                  unsigned short* aPriority,
-                  mozilla::MutexAutoLock& aProofOfLock) {
-      return mIsReadyToPrioritizeEvents
-        ? GetNormalOrInputOrHighPriorityEvent(aMayWait, aEvent, aPriority, aProofOfLock)
-        : GetNormalOrHighPriorityEvent(aMayWait, aEvent, aPriority, aProofOfLock);
-    }
-
-    void PutEvent(nsIRunnable* aEvent, mozilla::MutexAutoLock& aProofOfLock)
-    {
-      RefPtr<nsIRunnable> event(aEvent);
-      PutEvent(event.forget(), aProofOfLock);
-    }
-
-    void PutEvent(already_AddRefed<nsIRunnable> aEvent,
-                  mozilla::MutexAutoLock& aProofOfLock);
-
-    bool HasPendingEvent(mozilla::MutexAutoLock& aProofOfLock)
-    {
-      return mNormalQueue->HasPendingEvent(aProofOfLock) ||
-             mInputQueue->HasPendingEvent(aProofOfLock) ||
-             mHighQueue->HasPendingEvent(aProofOfLock);
-    }
-
-    bool HasPendingEventsInInputQueue(mozilla::MutexAutoLock& aProofOfLock)
-    {
-      MOZ_ASSERT(mIsInputPrioritizationEnabled);
-      return mInputQueue->HasPendingEvent(aProofOfLock);
-    }
-
-    nsChainedEventQueue* mNext;
-    RefPtr<nsNestedEventTarget> mEventTarget;
-
-  private:
-    bool GetNormalOrInputOrHighPriorityEvent(bool aMayWait,
-                                             nsIRunnable** aEvent,
-                                             unsigned short* aPriority,
-                                             mozilla::MutexAutoLock& aProofOfLock);
-
-    bool GetNormalOrHighPriorityEvent(bool aMayWait, nsIRunnable** aEvent,
-                                      unsigned short* aPriority,
-                                      mozilla::MutexAutoLock& aProofOfLock);
-
-    // This is used to flush pending events in nsChainedEventQueue::mNormalQueue
-    // before starting event prioritization.
-    class EnablePrioritizationRunnable final : public nsIRunnable
-    {
-      nsChainedEventQueue* mEventQueue;
+  RefPtr<mozilla::SynchronizedEventQueue> mEvents;
+  RefPtr<mozilla::ThreadEventTarget> mEventTarget;
 
-    public:
-      NS_DECL_ISUPPORTS
-
-      explicit EnablePrioritizationRunnable(nsChainedEventQueue* aQueue)
-        : mEventQueue(aQueue)
-      {
-      }
-
-      NS_IMETHOD Run() override
-      {
-        mEventQueue->mIsReadyToPrioritizeEvents = true;
-        return NS_OK;
-      }
-    private:
-      ~EnablePrioritizationRunnable()
-      {
-      }
-    };
-
-    static void SetPriorityIfNotNull(unsigned short* aPriority, short aValue)
-    {
-      if (aPriority) {
-        *aPriority = aValue;
-      }
-    }
-    mozilla::CondVar mEventsAvailable;
-    mozilla::TimeStamp mInputHandlingStartTime;
-    mozilla::UniquePtr<nsEventQueue> mNormalQueue;
-    mozilla::UniquePtr<nsEventQueue> mInputQueue;
-    mozilla::UniquePtr<nsEventQueue> mHighQueue;
-    bool mIsInputPrioritizationEnabled;
-
-    // When enabling input event prioritization, there may be some events in the
-    // queue. We have to process all of them before the new coming events to
-    // prevent the queued events are preempted by the newly ones with the same
-    // priority.
-    bool mIsReadyToPrioritizeEvents;
-    // Try to process one high priority runnable after each normal
-    // priority runnable. This gives the processing model HTML spec has for
-    // 'Update the rendering' in the case only vsync messages are in the
-    // secondary queue and prevents starving the normal queue.
-    bool mProcessHighPriorityQueueRunnable;
-  };
-
-  class nsNestedEventTarget final : public nsIEventTarget
-  {
-  public:
-    NS_DECL_THREADSAFE_ISUPPORTS
-    NS_DECL_NSIEVENTTARGET_FULL
-
-    nsNestedEventTarget(NotNull<nsThread*> aThread,
-                        NotNull<nsChainedEventQueue*> aQueue)
-      : mThread(aThread)
-      , mQueue(aQueue)
-
-
-
-    {
-    }
-
-    NotNull<RefPtr<nsThread>> mThread;
-
-    // This is protected by mThread->mLock.
-    nsChainedEventQueue* mQueue;
-
-  private:
-    ~nsNestedEventTarget()
-    {
-    }
-  };
-
-  // This lock protects access to mObserver, mEvents, mIdleEvents,
-  // mIdlePeriod and mEventsAreDoomed.  All of those fields are only
-  // modified on the thread itself (never from another thread).  This
-  // means that we can avoid holding the lock while using mObserver
-  // and mEvents on the thread itself.  When calling PutEvent on
-  // mEvents, we have to hold the lock to synchronize with
-  // PopEventQueue.
-  mozilla::Mutex mLock;
-
-  nsCOMPtr<nsIThreadObserver> mObserver;
   mozilla::CycleCollectedJSContext* mScriptObserver;
 
   // Only accessed on the target thread.
   nsAutoTObserverArray<NotNull<nsCOMPtr<nsIThreadObserver>>, 2> mEventObservers;
 
-  NotNull<nsChainedEventQueue*> mEvents;  // never null
-  nsChainedEventQueue mEventsRoot;
-
-  // mIdlePeriod keeps track of the current idle period. If at any
-  // time the main event queue is empty, calling
-  // mIdlePeriod->GetIdlePeriodHint() will give an estimate of when
-  // the current idle period will end.
-  nsCOMPtr<nsIIdlePeriod> mIdlePeriod;
-  mozilla::CondVar mIdleEventsAvailable;
-  nsEventQueue mIdleEvents;
-
   int32_t   mPriority;
   PRThread* mThread;
   uint32_t  mNestedEventLoopDepth;
   uint32_t  mStackSize;
 
   // The shutdown context for ourselves.
   struct nsThreadShutdownContext* mShutdownContext;
   // The shutdown contexts for any other threads we've asked to shut down.
   nsTArray<nsAutoPtr<struct nsThreadShutdownContext>> mRequestedShutdownContexts;
 
-  bool mShutdownRequired;
-  // Set to true when events posted to this thread will never run.
-  bool mEventsAreDoomed;
+  mozilla::Atomic<bool> mShutdownRequired;
   MainThreadFlag mIsMainThread;
 
   // The time when we last ran an unlabeled runnable (one not associated with a
   // SchedulerGroup).
   mozilla::TimeStamp mLastUnlabeledRunnable;
 
   // Set to true if this thread creates a JSRuntime.
   bool mCanInvokeJS;
-  // Set to true if HasPendingEvents() has been called and returned true because
-  // of a pending idle event.  This is used to remember to return that idle
-  // event from GetIdleEvent() to ensure that HasPendingEvents() never lies.
-  bool mHasPendingEventsPromisedIdleEvent;
 
 #ifndef RELEASE_OR_BETA
   mozilla::TimeStamp mNextIdleDeadline;
 #endif
 };
 
 #if defined(XP_UNIX) && !defined(ANDROID) && !defined(DEBUG) && HAVE_UALARM \
   && defined(_GNU_SOURCE)
--- a/xpcom/threads/nsThreadManager.cpp
+++ b/xpcom/threads/nsThreadManager.cpp
@@ -6,19 +6,22 @@
 
 #include "nsThreadManager.h"
 #include "nsThread.h"
 #include "nsThreadUtils.h"
 #include "nsIClassInfoImpl.h"
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
 #include "mozilla/AbstractThread.h"
+#include "mozilla/EventQueue.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/SystemGroup.h"
+#include "mozilla/ThreadEventQueue.h"
 #include "mozilla/ThreadLocal.h"
+#include "PrioritizedEventQueue.h"
 #ifdef MOZ_CANARY
 #include <fcntl.h>
 #include <unistd.h>
 #endif
 
 #include "MainThreadIdlePeriod.h"
 #include "InputEventStatistics.h"
 
@@ -91,30 +94,42 @@ nsThreadManager::Init()
   const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
   char* env_var_flag = getenv("MOZ_KILL_CANARIES");
   sCanaryOutputFD =
     env_var_flag ? (env_var_flag[0] ? open(env_var_flag, flags, mode) :
                                       STDERR_FILENO) :
                    0;
 #endif
 
+  using MainThreadQueueT = PrioritizedEventQueue<EventQueue>;
+
+  nsCOMPtr<nsIIdlePeriod> idlePeriod = new MainThreadIdlePeriod();
+  auto prioritized = MakeUnique<MainThreadQueueT>(MakeUnique<EventQueue>(),
+                                                  MakeUnique<EventQueue>(),
+                                                  MakeUnique<EventQueue>(),
+                                                  MakeUnique<EventQueue>(),
+                                                  idlePeriod.forget());
+
+  // Save a reference temporarily so we can set some state on it.
+  MainThreadQueueT* prioritizedRef = prioritized.get();
+  RefPtr<ThreadEventQueue<MainThreadQueueT>> queue =
+    new ThreadEventQueue<MainThreadQueueT>(Move(prioritized));
+
   // Setup "main" thread
-  mMainThread = new nsThread(nsThread::MAIN_THREAD, 0);
+  mMainThread = new nsThread(WrapNotNull(queue), nsThread::MAIN_THREAD, 0);
+
+  prioritizedRef->SetMutexRef(queue->MutexRef());
+  prioritizedRef->SetNextIdleDeadlineRef(mMainThread->NextIdleDeadlineRef());
 
   nsresult rv = mMainThread->InitCurrentThread();
   if (NS_FAILED(rv)) {
     mMainThread = nullptr;
     return rv;
   }
 
-  {
-    nsCOMPtr<nsIIdlePeriod> idlePeriod = new MainThreadIdlePeriod();
-    mMainThread->RegisterIdlePeriod(idlePeriod.forget());
-  }
-
   // We need to keep a pointer to the current thread, so we can satisfy
   // GetIsMainThread calls that occur post-Shutdown.
   mMainThread->GetPRThread(&mMainPRThread);
 
   // Init AbstractThread.
   AbstractThread::InitTLS();
   AbstractThread::InitMainThread();
 
@@ -239,17 +254,19 @@ nsThreadManager::GetCurrentThread()
     return static_cast<nsThread*>(data);
   }
 
   if (!mInitialized) {
     return nullptr;
   }
 
   // OK, that's fine.  We'll dynamically create one :-)
-  RefPtr<nsThread> thread = new nsThread(nsThread::NOT_MAIN_THREAD, 0);
+  RefPtr<ThreadEventQueue<EventQueue>> queue =
+    new ThreadEventQueue<EventQueue>(MakeUnique<EventQueue>());
+  RefPtr<nsThread> thread = new nsThread(WrapNotNull(queue), nsThread::NOT_MAIN_THREAD, 0);
   if (!thread || NS_FAILED(thread->InitCurrentThread())) {
     return nullptr;
   }
 
   return thread.get();  // reference held in TLS
 }
 
 NS_IMETHODIMP
@@ -267,17 +284,19 @@ nsThreadManager::NewNamedThread(const ns
 {
   // Note: can be called from arbitrary threads
 
   // No new threads during Shutdown
   if (NS_WARN_IF(!mInitialized)) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
-  RefPtr<nsThread> thr = new nsThread(nsThread::NOT_MAIN_THREAD, aStackSize);
+  RefPtr<ThreadEventQueue<EventQueue>> queue =
+    new ThreadEventQueue<EventQueue>(MakeUnique<EventQueue>());
+  RefPtr<nsThread> thr = new nsThread(WrapNotNull(queue), nsThread::NOT_MAIN_THREAD, aStackSize);
   nsresult rv = thr->Init(aName);  // Note: blocks until the new thread has been set up
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   // At this point, we expect that the thread has been registered in mThreadByPRThread;
   // however, it is possible that it could have also been replaced by now, so
   // we cannot really assert that it was added.  Instead, kill it if we entered
@@ -423,17 +442,17 @@ nsThreadManager::EnableMainThreadEventPr
   MOZ_ASSERT(Preferences::IsServiceAvailable());
   bool enable =
     Preferences::GetBool("prioritized_input_events.enabled", false);
 
   if (!enable) {
     return;
   }
   InputEventStatistics::Get().SetEnable(true);
-  mMainThread->EnableEventPrioritization();
+  mMainThread->EnableInputEventPrioritization();
 }
 
 NS_IMETHODIMP
 nsThreadManager::IdleDispatchToMainThread(nsIRunnable *aEvent, uint32_t aTimeout)
 {
   // Note: C++ callers should instead use NS_IdleDispatchToThread or
   // NS_IdleDispatchToCurrentThread.
   MOZ_ASSERT(NS_IsMainThread());
--- a/xpcom/threads/nsThreadManager.h
+++ b/xpcom/threads/nsThreadManager.h
@@ -48,16 +48,17 @@ public:
   // simultaneously during the execution of the thread manager.
   uint32_t GetHighestNumberOfThreads();
 
   // This needs to be public in order to support static instantiation of this
   // class with older compilers (e.g., egcs-2.91.66).
   ~nsThreadManager()
   {
   }
+
   void EnableMainThreadEventPrioritization();
 
 private:
   nsThreadManager()
     : mCurThreadIndex(0)
     , mMainPRThread(nullptr)
     , mLock("nsThreadManager.mLock")
     , mInitialized(false)
--- a/xpcom/threads/nsThreadPool.cpp
+++ b/xpcom/threads/nsThreadPool.cpp
@@ -38,17 +38,16 @@ NS_IMPL_CLASSINFO(nsThreadPool, nullptr,
                   NS_THREADPOOL_CID)
 NS_IMPL_QUERY_INTERFACE_CI(nsThreadPool, nsIThreadPool, nsIEventTarget,
                            nsIRunnable)
 NS_IMPL_CI_INTERFACE_GETTER(nsThreadPool, nsIThreadPool, nsIEventTarget)
 
 nsThreadPool::nsThreadPool()
   : mMutex("[nsThreadPool.mMutex]")
   , mEventsAvailable(mMutex, "[nsThreadPool.mEventsAvailable]")
-  , mEvents(mEventsAvailable, nsEventQueue::eNormalQueue)
   , mThreadLimit(DEFAULT_THREAD_LIMIT)
   , mIdleThreadLimit(DEFAULT_IDLE_THREAD_LIMIT)
   , mIdleThreadTimeout(DEFAULT_IDLE_THREAD_TIMEOUT)
   , mIdleCount(0)
   , mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE)
   , mShutdown(false)
 {
   LOG(("THRD-P(%p) constructor!!!\n", this));
@@ -89,17 +88,18 @@ nsThreadPool::PutEvent(already_AddRefed<
     if (mThreads.Count() < (int32_t)mThreadLimit &&
         !(aFlags & NS_DISPATCH_AT_END) &&
         // Spawn a new thread if we don't have enough idle threads to serve
         // pending events immediately.
         mEvents.Count(lock) >= mIdleCount) {
       spawnThread = true;
     }
 
-    mEvents.PutEvent(Move(aEvent), lock);
+    mEvents.PutEvent(Move(aEvent), EventPriority::Normal, lock);
+    mEventsAvailable.Notify();
     stackSize = mStackSize;
   }
 
   LOG(("THRD-P(%p) put [spawn=%d]\n", this, spawnThread));
   if (!spawnThread) {
     return NS_OK;
   }
 
@@ -173,17 +173,18 @@ nsThreadPool::Run()
     listener->OnThreadCreated();
   }
 
   do {
     nsCOMPtr<nsIRunnable> event;
     {
       MutexAutoLock lock(mMutex);
 
-      if (!mEvents.GetPendingEvent(getter_AddRefs(event), lock)) {
+      event = mEvents.GetEvent(nullptr, lock);
+      if (!event) {
         PRIntervalTime now     = PR_IntervalNow();
         PRIntervalTime timeout = PR_MillisecondsToInterval(mIdleThreadTimeout);
 
         // If we are shutting down, then don't keep any idle threads
         if (mShutdown) {
           exitThread = true;
         } else {
           if (wasIdle) {
@@ -207,17 +208,17 @@ nsThreadPool::Run()
         if (exitThread) {
           if (wasIdle) {
             --mIdleCount;
           }
           shutdownThreadOnExit = mThreads.RemoveObject(current);
         } else {
           PRIntervalTime delta = timeout - (now - idleSince);
           LOG(("THRD-P(%p) %s waiting [%d]\n", this, mName.BeginReading(), delta));
-          mEvents.Wait(delta);
+          mEventsAvailable.Wait(delta);
           LOG(("THRD-P(%p) done waiting\n", this));
         }
       } else if (wasIdle) {
         wasIdle = false;
         --mIdleCount;
       }
     }
     if (event) {
@@ -257,22 +258,22 @@ nsThreadPool::Dispatch(already_AddRefed<
   if (aFlags & DISPATCH_SYNC) {
     nsCOMPtr<nsIThread> thread;
     nsThreadManager::get().GetCurrentThread(getter_AddRefs(thread));
     if (NS_WARN_IF(!thread)) {
       return NS_ERROR_NOT_AVAILABLE;
     }
 
     RefPtr<nsThreadSyncDispatch> wrapper =
-      new nsThreadSyncDispatch(thread, Move(aEvent));
+      new nsThreadSyncDispatch(thread.forget(), Move(aEvent));
     PutEvent(wrapper);
 
     SpinEventLoopUntil([&, wrapper]() -> bool {
         return !wrapper->IsPending();
-      }, thread);
+      });
   } else {
     NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL ||
                  aFlags == NS_DISPATCH_AT_END, "unexpected dispatch flags");
     PutEvent(Move(aEvent), aFlags);
   }
   return NS_OK;
 }
 
@@ -318,17 +319,17 @@ nsThreadPool::IsOnCurrentThread(bool* aR
 NS_IMETHODIMP
 nsThreadPool::Shutdown()
 {
   nsCOMArray<nsIThread> threads;
   nsCOMPtr<nsIThreadPoolListener> listener;
   {
     MutexAutoLock lock(mMutex);
     mShutdown = true;
-    mEvents.NotifyAll();
+    mEventsAvailable.NotifyAll();
 
     threads.AppendObjects(mThreads);
     mThreads.Clear();
 
     // Swap in a null listener so that we release the listener at the end of
     // this method. The listener will be kept alive as long as the other threads
     // that were created when it was set.
     mListener.swap(listener);
@@ -357,17 +358,17 @@ nsThreadPool::SetThreadLimit(uint32_t aV
   MutexAutoLock lock(mMutex);
   LOG(("THRD-P(%p) thread limit [%u]\n", this, aValue));
   mThreadLimit = aValue;
   if (mIdleThreadLimit > mThreadLimit) {
     mIdleThreadLimit = mThreadLimit;
   }
 
   if (static_cast<uint32_t>(mThreads.Count()) > mThreadLimit) {
-    mEvents.NotifyAll();  // wake up threads so they observe this change
+    mEventsAvailable.NotifyAll();  // wake up threads so they observe this change
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsThreadPool::GetIdleThreadLimit(uint32_t* aValue)
 {
   *aValue = mIdleThreadLimit;
@@ -381,17 +382,17 @@ nsThreadPool::SetIdleThreadLimit(uint32_
   LOG(("THRD-P(%p) idle thread limit [%u]\n", this, aValue));
   mIdleThreadLimit = aValue;
   if (mIdleThreadLimit > mThreadLimit) {
     mIdleThreadLimit = mThreadLimit;
   }
 
   // Do we need to kill some idle threads?
   if (mIdleCount > mIdleThreadLimit) {
-    mEvents.NotifyAll();  // wake up threads so they observe this change
+    mEventsAvailable.NotifyAll();  // wake up threads so they observe this change
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsThreadPool::GetIdleThreadTimeout(uint32_t* aValue)
 {
   *aValue = mIdleThreadTimeout;
@@ -402,17 +403,17 @@ NS_IMETHODIMP
 nsThreadPool::SetIdleThreadTimeout(uint32_t aValue)
 {
   MutexAutoLock lock(mMutex);
   uint32_t oldTimeout = mIdleThreadTimeout;
   mIdleThreadTimeout = aValue;
 
   // Do we need to notify any idle threads that their sleep time has shortened?
   if (mIdleThreadTimeout < oldTimeout && mIdleCount > 0) {
-    mEvents.NotifyAll();  // wake up threads so they observe this change
+    mEventsAvailable.NotifyAll();  // wake up threads so they observe this change
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsThreadPool::GetThreadStackSize(uint32_t* aValue)
 {
   MutexAutoLock lock(mMutex);
--- a/xpcom/threads/nsThreadPool.h
+++ b/xpcom/threads/nsThreadPool.h
@@ -5,22 +5,22 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsThreadPool_h__
 #define nsThreadPool_h__
 
 #include "nsIThreadPool.h"
 #include "nsIThread.h"
 #include "nsIRunnable.h"
-#include "nsEventQueue.h"
 #include "nsCOMArray.h"
 #include "nsCOMPtr.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/EventQueue.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/Monitor.h"
 
 class nsThreadPool final
   : public nsIThreadPool
   , public nsIRunnable
 {
 public:
@@ -36,17 +36,17 @@ private:
 
   void ShutdownThread(nsIThread* aThread);
   nsresult PutEvent(nsIRunnable* aEvent);
   nsresult PutEvent(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags);
 
   nsCOMArray<nsIThread> mThreads;
   mozilla::Mutex        mMutex;
   mozilla::CondVar      mEventsAvailable;
-  nsEventQueue          mEvents;
+  mozilla::EventQueue   mEvents;
   uint32_t              mThreadLimit;
   uint32_t              mIdleThreadLimit;
   uint32_t              mIdleThreadTimeout;
   uint32_t              mIdleCount;
   uint32_t              mStackSize;
   nsCOMPtr<nsIThreadPoolListener> mListener;
   bool                  mShutdown;
   nsCString             mName;
--- a/xpcom/threads/nsThreadSyncDispatch.h
+++ b/xpcom/threads/nsThreadSyncDispatch.h
@@ -11,17 +11,17 @@
 #include "mozilla/DebugOnly.h"
 
 #include "nsThreadUtils.h"
 #include "LeakRefPtr.h"
 
 class nsThreadSyncDispatch : public mozilla::Runnable
 {
 public:
-  nsThreadSyncDispatch(nsIThread* aOrigin, already_AddRefed<nsIRunnable>&& aTask)
+  nsThreadSyncDispatch(already_AddRefed<nsIEventTarget> aOrigin, already_AddRefed<nsIRunnable>&& aTask)
     : Runnable("nsThreadSyncDispatch")
     , mOrigin(aOrigin)
     , mSyncTask(mozilla::Move(aTask))
     , mIsPending(true)
   {
   }
 
   bool IsPending()
@@ -49,16 +49,16 @@ private:
 
       // unblock the origin thread
       mOrigin->Dispatch(this, NS_DISPATCH_NORMAL);
     }
 
     return NS_OK;
   }
 
-  nsCOMPtr<nsIThread> mOrigin;
+  nsCOMPtr<nsIEventTarget> mOrigin;
   // The task is leaked by default when Run() is not called, because
   // otherwise we may release it in an incorrect thread.
   mozilla::LeakRefPtr<nsIRunnable> mSyncTask;
   mozilla::Atomic<bool, mozilla::ReleaseAcquire> mIsPending;
 };
 
 #endif // nsThreadSyncDispatch_h_
--- a/xpcom/threads/nsTimerImpl.cpp
+++ b/xpcom/threads/nsTimerImpl.cpp
@@ -9,32 +9,36 @@
 #include "nsAutoPtr.h"
 #include "nsThreadManager.h"
 #include "nsThreadUtils.h"
 #include "pratom.h"
 #include "GeckoProfiler.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/Logging.h"
+#include "mozilla/Move.h"
+#include "mozilla/Mutex.h"
 #ifdef MOZ_TASK_TRACER
 #include "GeckoTaskTracerImpl.h"
 using namespace mozilla::tasktracer;
 #endif
 
 #ifdef XP_WIN
 #include <process.h>
 #ifndef getpid
 #define getpid _getpid
 #endif
 #else
 #include <unistd.h>
 #endif
 
 using mozilla::Atomic;
 using mozilla::LogLevel;
+using mozilla::Move;
+using mozilla::MutexAutoLock;
 using mozilla::TimeDuration;
 using mozilla::TimeStamp;
 
 static TimerThread*     gThread = nullptr;
 
 // This module prints info about the precision of timers.
 static mozilla::LazyLogModule sTimerLog("nsTimerImpl");
 
@@ -148,17 +152,17 @@ nsTimer::Release(void)
 
 nsTimerImpl::nsTimerImpl(nsITimer* aTimer) :
   mHolder(nullptr),
   mGeneration(0),
   mITimer(aTimer),
   mMutex("nsTimerImpl::mMutex")
 {
   // XXXbsmedberg: shouldn't this be in Init()?
-  mEventTarget = GetCurrentThreadEventTarget();
+  mEventTarget = mozilla::GetCurrentThreadEventTarget();
 }
 
 //static
 nsresult
 nsTimerImpl::Startup()
 {
   nsresult rv;
 
@@ -438,17 +442,17 @@ nsTimerImpl::SetTarget(nsIEventTarget* a
   MutexAutoLock lock(mMutex);
   if (NS_WARN_IF(mCallback.mType != Callback::Type::Unknown)) {
     return NS_ERROR_ALREADY_INITIALIZED;
   }
 
   if (aTarget) {
     mEventTarget = aTarget;
   } else {
-    mEventTarget = GetCurrentThreadEventTarget();
+    mEventTarget = mozilla::GetCurrentThreadEventTarget();
   }
   return NS_OK;
 }
 
 nsresult
 nsTimerImpl::GetAllowedEarlyFiringMicroseconds(uint32_t* aValueOut)
 {
   *aValueOut = gThread ? gThread->AllowedEarlyFiringMicroseconds() : 0;