Bug 1522150: Add a DeferredTimers queue ahead of the normal Idle EventQueue r=froyd
authorRandell Jesup <rjesup@jesup.org>
Sat, 26 Jan 2019 12:18:05 -0500
changeset 515566 65cf08e33fe2b12a90a505462f3246df204c64ad
parent 515565 0f5c896960f5147c3600c847209fb786dc6c1481
child 515567 7f8594d21c51ba9966c85b1bacbf0d245e45395f
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroyd
bugs1522150
milestone66.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 1522150: Add a DeferredTimers queue ahead of the normal Idle EventQueue r=froyd * * * Bug 1522150: Rename NS_IdleDispatch* functions since they take queue identifiers r=froyd
dom/base/ChromeUtils.cpp
dom/base/Document.cpp
dom/base/FragmentOrElement.cpp
dom/base/WindowDestroyedEvent.cpp
dom/base/nsContentUtils.cpp
dom/base/nsGlobalWindowInner.cpp
dom/ipc/ContentChild.cpp
dom/ipc/PreallocatedProcessManager.cpp
dom/ipc/SharedMap.cpp
dom/script/ScriptLoader.cpp
editor/spellchecker/EditorSpellCheck.cpp
extensions/spellcheck/src/mozInlineSpellChecker.cpp
gfx/thebes/gfxPlatformFontList.cpp
intl/strres/nsStringBundle.cpp
js/xpconnect/src/XPCJSRuntime.cpp
layout/base/nsRefreshDriver.cpp
toolkit/components/antitracking/AntiTrackingCommon.cpp
widget/nsBaseWidget.cpp
xpcom/base/CycleCollectedJSContext.cpp
xpcom/base/CycleCollectedJSRuntime.cpp
xpcom/base/MemoryTelemetry.cpp
xpcom/tests/gtest/TestEventPriorities.cpp
xpcom/tests/gtest/TestThreadUtils.cpp
xpcom/threads/AbstractEventQueue.h
xpcom/threads/IdleTaskRunner.cpp
xpcom/threads/LazyIdleThread.cpp
xpcom/threads/MainThreadQueue.h
xpcom/threads/PrioritizedEventQueue.cpp
xpcom/threads/PrioritizedEventQueue.h
xpcom/threads/nsIThread.idl
xpcom/threads/nsThread.cpp
xpcom/threads/nsThreadManager.cpp
xpcom/threads/nsThreadUtils.cpp
xpcom/threads/nsThreadUtils.h
--- a/dom/base/ChromeUtils.cpp
+++ b/dom/base/ChromeUtils.cpp
@@ -365,20 +365,21 @@ NS_IMPL_ISUPPORTS_INHERITED(IdleDispatch
                                             const IdleRequestOptions& aOptions,
                                             ErrorResult& aRv) {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
   MOZ_ASSERT(global);
 
   auto runnable = MakeRefPtr<IdleDispatchRunnable>(global, aCallback);
 
   if (aOptions.mTimeout.WasPassed()) {
-    aRv = NS_IdleDispatchToCurrentThread(runnable.forget(),
-                                         aOptions.mTimeout.Value());
+    aRv = NS_DispatchToCurrentThreadQueue(
+        runnable.forget(), aOptions.mTimeout.Value(), EventQueuePriority::Idle);
   } else {
-    aRv = NS_IdleDispatchToCurrentThread(runnable.forget());
+    aRv = NS_DispatchToCurrentThreadQueue(runnable.forget(),
+                                          EventQueuePriority::Idle);
   }
 }
 
 /* static */ void ChromeUtils::Import(
     const GlobalObject& aGlobal, const nsAString& aResourceURI,
     const Optional<JS::Handle<JSObject*>>& aTargetObj,
     JS::MutableHandle<JSObject*> aRetval, ErrorResult& aRv) {
   RefPtr<mozJSComponentLoader> moduleloader = mozJSComponentLoader::Get();
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -8783,17 +8783,18 @@ void Document::RegisterPendingLinkUpdate
 
   aLink->SetHasPendingLinkUpdate();
 
   if (!mHasLinksToUpdateRunnable && !mFlushingPendingLinkUpdates) {
     nsCOMPtr<nsIRunnable> event =
         NewRunnableMethod("Document::FlushPendingLinkUpdatesFromRunnable", this,
                           &Document::FlushPendingLinkUpdatesFromRunnable);
     // Do this work in a second in the worst case.
-    nsresult rv = NS_IdleDispatchToCurrentThread(event.forget(), 1000);
+    nsresult rv = NS_DispatchToCurrentThreadQueue(event.forget(), 1000,
+                                                  EventQueuePriority::Idle);
     if (NS_FAILED(rv)) {
       // If during shutdown posting a runnable doesn't succeed, we probably
       // don't need to update link states.
       return;
     }
     mHasLinksToUpdateRunnable = true;
   }
 
@@ -11971,17 +11972,18 @@ void Document::MaybeStoreUserInteraction
     return;
   }
 
   if (mHasUserInteractionTimerScheduled) {
     return;
   }
 
   nsCOMPtr<nsIRunnable> task = new UserIntractionTimer(this);
-  nsresult rv = NS_IdleDispatchToCurrentThread(task.forget(), 2500);
+  nsresult rv = NS_DispatchToCurrentThreadQueue(task.forget(), 2500,
+                                                EventQueuePriority::Idle);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
   // This value will be reset by the timer.
   mHasUserInteractionTimerScheduled = true;
 }
 
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -1254,17 +1254,18 @@ class ContentUnbinder : public Runnable 
     if (this == sContentUnbinder) {
       sContentUnbinder = nullptr;
       if (mNext) {
         RefPtr<ContentUnbinder> next;
         next.swap(mNext);
         sContentUnbinder = next;
         next->mLast = mLast;
         mLast = nullptr;
-        NS_IdleDispatchToCurrentThread(next.forget());
+        NS_DispatchToCurrentThreadQueue(next.forget(),
+                                        EventQueuePriority::Idle);
       }
     }
     return NS_OK;
   }
 
   static void UnbindAll() {
     RefPtr<ContentUnbinder> ub = sContentUnbinder;
     sContentUnbinder = nullptr;
@@ -1273,17 +1274,17 @@ class ContentUnbinder : public Runnable 
       ub = ub->mNext;
     }
   }
 
   static void Append(nsIContent* aSubtreeRoot) {
     if (!sContentUnbinder) {
       sContentUnbinder = new ContentUnbinder();
       nsCOMPtr<nsIRunnable> e = sContentUnbinder;
-      NS_IdleDispatchToCurrentThread(e.forget());
+      NS_DispatchToCurrentThreadQueue(e.forget(), EventQueuePriority::Idle);
     }
 
     if (sContentUnbinder->mLast->mSubtreeRoots.Length() >=
         SUBTREE_UNBINDINGS_PER_RUNNABLE) {
       sContentUnbinder->mLast->mNext = new ContentUnbinder();
       sContentUnbinder->mLast = sContentUnbinder->mLast->mNext;
     }
     sContentUnbinder->mLast->mSubtreeRoots.AppendElement(aSubtreeRoot);
--- a/dom/base/WindowDestroyedEvent.cpp
+++ b/dom/base/WindowDestroyedEvent.cpp
@@ -83,17 +83,18 @@ WindowDestroyedEvent::Run() {
         if (mTopic.EqualsLiteral("inner-window-destroyed")) {
           mTopic.AssignLiteral("inner-window-nuked");
         } else if (mTopic.EqualsLiteral("outer-window-destroyed")) {
           mTopic.AssignLiteral("outer-window-nuked");
         }
         mPhase = Phase::Nuking;
 
         nsCOMPtr<nsIRunnable> copy(this);
-        NS_IdleDispatchToCurrentThread(copy.forget(), 1000);
+        NS_DispatchToCurrentThreadQueue(copy.forget(), 1000,
+                                        EventQueuePriority::Idle);
       }
     } break;
 
     case Phase::Nuking: {
       nsCOMPtr<nsISupports> window = do_QueryReferent(mWindow);
       if (window) {
         nsGlobalWindowInner* currentInner;
         if (mIsInnerWindow) {
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -3669,23 +3669,26 @@ void nsContentUtils::AsyncPrecreateStrin
   // If we attempt to create a bundle in the child before its memory region is
   // available, we need to create a temporary non-shared bundle, and later
   // replace that with the shared memory copy. So attempting to pre-load in the
   // child is wasteful and unnecessary.
   MOZ_ASSERT(XRE_IsParentProcess());
 
   for (uint32_t bundleIndex = 0; bundleIndex < PropertiesFile_COUNT;
        ++bundleIndex) {
-    nsresult rv = NS_IdleDispatchToCurrentThread(
-        NS_NewRunnableFunction("AsyncPrecreateStringBundles", [bundleIndex]() {
-          PropertiesFile file = static_cast<PropertiesFile>(bundleIndex);
-          EnsureStringBundle(file);
-          nsIStringBundle* bundle = sStringBundles[file];
-          bundle->AsyncPreload();
-        }));
+    nsresult rv = NS_DispatchToCurrentThreadQueue(
+        NS_NewRunnableFunction("AsyncPrecreateStringBundles",
+                               [bundleIndex]() {
+                                 PropertiesFile file =
+                                     static_cast<PropertiesFile>(bundleIndex);
+                                 EnsureStringBundle(file);
+                                 nsIStringBundle* bundle = sStringBundles[file];
+                                 bundle->AsyncPreload();
+                               }),
+        EventQueuePriority::Idle);
     Unused << NS_WARN_IF(NS_FAILED(rv));
   }
 }
 
 /* static */
 nsresult nsContentUtils::GetLocalizedString(PropertiesFile aFile,
                                             const char* aKey,
                                             nsAString& aResult) {
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -612,17 +612,17 @@ void IdleRequestExecutor::MaybeDispatch(
   TimeDuration delay = aDelayUntil - now;
   DelayedDispatch(static_cast<uint32_t>(delay.ToMilliseconds()));
 }
 
 void IdleRequestExecutor::ScheduleDispatch() {
   MOZ_ASSERT(mWindow);
   mDelayedExecutorHandle = Nothing();
   RefPtr<IdleRequestExecutor> request = this;
-  NS_IdleDispatchToCurrentThread(request.forget());
+  NS_DispatchToCurrentThreadQueue(request.forget(), EventQueuePriority::Idle);
 }
 
 void IdleRequestExecutor::DelayedDispatch(uint32_t aDelay) {
   MOZ_ASSERT(mWindow);
   MOZ_ASSERT(mDelayedExecutorHandle.isNothing());
   int32_t handle;
   mWindow->TimeoutManager().SetTimeout(
       mDelayedExecutorDispatcher, aDelay, false,
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1713,17 +1713,18 @@ mozilla::ipc::IPCResult ContentChild::Re
 
   static bool hasRunOnce = false;
   if (!hasRunOnce) {
     hasRunOnce = true;
     MOZ_ASSERT(!gFirstIdleTask);
     RefPtr<CancelableRunnable> firstIdleTask =
         NewCancelableRunnableFunction("FirstIdleRunnable", FirstIdle);
     gFirstIdleTask = firstIdleTask;
-    if (NS_FAILED(NS_IdleDispatchToCurrentThread(firstIdleTask.forget()))) {
+    if (NS_FAILED(NS_DispatchToCurrentThreadQueue(firstIdleTask.forget(),
+                                                  EventQueuePriority::Idle))) {
       gFirstIdleTask = nullptr;
       hasRunOnce = false;
     }
   }
 
   return nsIContentChild::RecvPBrowserConstructor(
       aActor, aTabId, aSameTabGroupAs, aContext, aChromeFlags, aCpID,
       aIsForBrowser);
--- a/dom/ipc/PreallocatedProcessManager.cpp
+++ b/dom/ipc/PreallocatedProcessManager.cpp
@@ -240,19 +240,20 @@ void PreallocatedProcessManagerImpl::All
       sPrelaunchDelayMS);
 }
 
 void PreallocatedProcessManagerImpl::AllocateOnIdle() {
   if (!mEnabled) {
     return;
   }
 
-  NS_IdleDispatchToCurrentThread(
+  NS_DispatchToCurrentThreadQueue(
       NewRunnableMethod("PreallocatedProcessManagerImpl::AllocateNow", this,
-                        &PreallocatedProcessManagerImpl::AllocateNow));
+                        &PreallocatedProcessManagerImpl::AllocateNow),
+      EventQueuePriority::Idle);
 }
 
 void PreallocatedProcessManagerImpl::AllocateNow() {
   if (!CanAllocate()) {
     if (mEnabled && !mShutdown && IsEmpty() && !mBlockers.IsEmpty()) {
       // If it's too early to allocate a process let's retry later.
       AllocateAfterDelay();
     }
--- a/dom/ipc/SharedMap.cpp
+++ b/dom/ipc/SharedMap.cpp
@@ -410,18 +410,20 @@ void WritableSharedMap::IdleFlush() {
 
 nsresult WritableSharedMap::KeyChanged(const nsACString& aName) {
   if (!mChangedKeys.ContainsSorted(aName)) {
     mChangedKeys.InsertElementSorted(aName);
   }
   mEntryArray.reset();
 
   if (!mPendingFlush) {
-    MOZ_TRY(NS_IdleDispatchToCurrentThread(NewRunnableMethod(
-        "WritableSharedMap::IdleFlush", this, &WritableSharedMap::IdleFlush)));
+    MOZ_TRY(NS_DispatchToCurrentThreadQueue(
+        NewRunnableMethod("WritableSharedMap::IdleFlush", this,
+                          &WritableSharedMap::IdleFlush),
+        EventQueuePriority::Idle));
     mPendingFlush = true;
   }
   return NS_OK;
 }
 
 JSObject* SharedMap::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto) {
   return MozSharedMap_Binding::Wrap(aCx, this, aGivenProto);
 }
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -2750,17 +2750,18 @@ void ScriptLoader::MaybeTriggerBytecodeE
     return;
   }
 
   // Create a new runnable dedicated to encoding the content of the bytecode of
   // all enqueued scripts when the document is idle. In case of failure, we
   // give-up on encoding the bytecode.
   nsCOMPtr<nsIRunnable> encoder = NewRunnableMethod(
       "ScriptLoader::EncodeBytecode", this, &ScriptLoader::EncodeBytecode);
-  if (NS_FAILED(NS_IdleDispatchToCurrentThread(encoder.forget()))) {
+  if (NS_FAILED(NS_DispatchToCurrentThreadQueue(encoder.forget(),
+                                                EventQueuePriority::Idle))) {
     GiveUpBytecodeEncoding();
     return;
   }
 
   LOG(("ScriptLoader (%p): Schedule bytecode encoding.", this));
 }
 
 void ScriptLoader::EncodeBytecode() {
--- a/editor/spellchecker/EditorSpellCheck.cpp
+++ b/editor/spellchecker/EditorSpellCheck.cpp
@@ -183,17 +183,18 @@ class ContentPrefInitializerRunnable fin
 };
 
 NS_IMETHODIMP
 DictionaryFetcher::Fetch(nsIEditor* aEditor) {
   NS_ENSURE_ARG_POINTER(aEditor);
 
   nsCOMPtr<nsIRunnable> runnable =
       new ContentPrefInitializerRunnable(aEditor, this);
-  NS_IdleDispatchToCurrentThread(runnable.forget(), 1000);
+  NS_DispatchToCurrentThreadQueue(runnable.forget(), 1000,
+                                  EventQueuePriority::Idle);
 
   return NS_OK;
 }
 
 /**
  * Stores the current dictionary for aEditor's document URL.
  */
 static nsresult StoreCurrentDictionary(EditorBase* aEditorBase,
--- a/extensions/spellcheck/src/mozInlineSpellChecker.cpp
+++ b/extensions/spellcheck/src/mozInlineSpellChecker.cpp
@@ -450,17 +450,18 @@ class mozInlineSpellResume : public Runn
   mozInlineSpellResume(UniquePtr<mozInlineSpellStatus>&& aStatus,
                        uint32_t aDisabledAsyncToken)
       : Runnable("mozInlineSpellResume"),
         mDisabledAsyncToken(aDisabledAsyncToken),
         mStatus(std::move(aStatus)) {}
 
   nsresult Post() {
     nsCOMPtr<nsIRunnable> runnable(this);
-    return NS_IdleDispatchToCurrentThread(runnable.forget(), 1000);
+    return NS_DispatchToCurrentThreadQueue(runnable.forget(), 1000,
+                                           EventQueuePriority::Idle);
   }
 
   NS_IMETHOD Run() override {
     // Discard the resumption if the spell checker was disabled after the
     // resumption was scheduled.
     if (mDisabledAsyncToken == mStatus->mSpellChecker->mDisabledAsyncToken) {
       mStatus->mSpellChecker->ResumeCheck(std::move(mStatus));
     }
--- a/gfx/thebes/gfxPlatformFontList.cpp
+++ b/gfx/thebes/gfxPlatformFontList.cpp
@@ -396,17 +396,17 @@ void gfxPlatformFontList::InitOtherFamil
   // (This is used so we can reliably run reftests that depend on localized
   // font-family names being available.)
   if (aDeferOtherFamilyNamesLoading &&
       Preferences::GetUint(FONT_LOADER_DELAY_PREF) > 0) {
     if (!mPendingOtherFamilyNameTask) {
       RefPtr<mozilla::CancelableRunnable> task =
           new InitOtherFamilyNamesRunnable();
       mPendingOtherFamilyNameTask = task;
-      NS_IdleDispatchToMainThread(task.forget());
+      NS_DispatchToMainThreadQueue(task.forget(), EventQueuePriority::Idle);
     }
   } else {
     InitOtherFamilyNamesInternal(false);
   }
 }
 
 // time limit for loading facename lists (ms)
 #define NAMELIST_TIMEOUT 200
--- a/intl/strres/nsStringBundle.cpp
+++ b/intl/strres/nsStringBundle.cpp
@@ -312,19 +312,20 @@ template <typename T, typename... Args>
 
 nsStringBundle::nsStringBundle(const char* aURLSpec)
     : nsStringBundleBase(aURLSpec) {}
 
 nsStringBundle::~nsStringBundle() {}
 
 NS_IMETHODIMP
 nsStringBundleBase::AsyncPreload() {
-  return NS_IdleDispatchToCurrentThread(
+  return NS_DispatchToCurrentThreadQueue(
       NewIdleRunnableMethod("nsStringBundleBase::LoadProperties", this,
-                            &nsStringBundleBase::LoadProperties));
+                            &nsStringBundleBase::LoadProperties),
+      EventQueuePriority::Idle);
 }
 
 size_t nsStringBundle::SizeOfIncludingThis(
     mozilla::MallocSizeOf aMallocSizeOf) const {
   size_t n = 0;
   if (mProps) {
     n += mProps->SizeOfIncludingThis(aMallocSizeOf);
   }
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -151,17 +151,18 @@ class AsyncFreeSnowWhite : public Runnab
     } else {
       mActive = false;
     }
     return NS_OK;
   }
 
   nsresult Dispatch() {
     nsCOMPtr<nsIRunnable> self(this);
-    return NS_IdleDispatchToCurrentThread(self.forget(), 500);
+    return NS_DispatchToCurrentThreadQueue(self.forget(), 500,
+                                           EventQueuePriority::Idle);
   }
 
   void Start(bool aContinuation = false, bool aPurge = false) {
     if (mContinuation) {
       mContinuation = aContinuation;
     }
     mPurge = aPurge;
     if (!mActive && NS_SUCCEEDED(Dispatch())) {
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -2001,18 +2001,19 @@ void nsRefreshDriver::Tick(VsyncId aId, 
       gfxPrefs::AlwaysPaint()) {
     ScheduleViewManagerFlush();
   }
 
   if (dispatchRunnablesAfterTick && sPendingIdleRunnables) {
     AutoTArray<RunnableWithDelay, 8>* runnables = sPendingIdleRunnables;
     sPendingIdleRunnables = nullptr;
     for (RunnableWithDelay& runnableWithDelay : *runnables) {
-      NS_IdleDispatchToCurrentThread(runnableWithDelay.mRunnable.forget(),
-                                     runnableWithDelay.mDelay);
+      NS_DispatchToCurrentThreadQueue(runnableWithDelay.mRunnable.forget(),
+                                      runnableWithDelay.mDelay,
+                                      EventQueuePriority::Idle);
     }
     delete runnables;
   }
 }
 
 void nsRefreshDriver::BeginRefreshingImages(RequestTable& aEntries,
                                             mozilla::TimeStamp aDesired) {
   for (auto iter = aEntries.Iter(); !iter.Done(); iter.Next()) {
--- a/toolkit/components/antitracking/AntiTrackingCommon.cpp
+++ b/toolkit/components/antitracking/AntiTrackingCommon.cpp
@@ -282,17 +282,17 @@ void ReportBlockingToConsole(nsPIDOMWind
   uint32_t lineNumber = 0, columnNumber = 0;
   JSContext* cx = nsContentUtils::GetCurrentJSContext();
   if (cx) {
     nsJSUtils::GetCallingLocation(cx, sourceLine, &lineNumber, &columnNumber);
   }
 
   nsCOMPtr<nsIURI> uri(aURI);
 
-  nsresult rv = NS_IdleDispatchToCurrentThread(
+  nsresult rv = NS_DispatchToCurrentThreadQueue(
       NS_NewRunnableFunction(
           "ReportBlockingToConsoleDelayed",
           [doc, sourceLine, lineNumber, columnNumber, uri, aRejectedReason]() {
             const char* message = nullptr;
             nsAutoCString category;
             switch (aRejectedReason) {
               case nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION:
                 message = "CookieBlockedByPermission";
@@ -333,17 +333,17 @@ void ReportBlockingToConsole(nsPIDOMWind
             const char16_t* params[] = {spec.get()};
 
             nsContentUtils::ReportToConsole(
                 nsIScriptError::warningFlag, category, doc,
                 nsContentUtils::eNECKO_PROPERTIES, message, params,
                 ArrayLength(params), nullptr, sourceLine, lineNumber,
                 columnNumber);
           }),
-      kMaxConsoleOutputDelayMs);
+      kMaxConsoleOutputDelayMs, EventQueuePriority::Idle);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 }
 
 void ReportUnblockingToConsole(
     nsPIDOMWindowInner* aWindow, const nsAString& aTrackingOrigin,
     const nsAString& aGrantedOrigin,
@@ -369,17 +369,17 @@ void ReportUnblockingToConsole(
 
   nsAutoString sourceLine;
   uint32_t lineNumber = 0, columnNumber = 0;
   JSContext* cx = nsContentUtils::GetCurrentJSContext();
   if (cx) {
     nsJSUtils::GetCallingLocation(cx, sourceLine, &lineNumber, &columnNumber);
   }
 
-  nsresult rv = NS_IdleDispatchToCurrentThread(
+  nsresult rv = NS_DispatchToCurrentThreadQueue(
       NS_NewRunnableFunction(
           "ReportUnblockingToConsoleDelayed",
           [doc, principal, trackingOrigin, grantedOrigin, sourceLine,
            lineNumber, columnNumber, aReason]() {
             nsAutoString origin;
             nsresult rv = nsContentUtils::GetUTFOrigin(principal, origin);
             if (NS_WARN_IF(NS_FAILED(rv))) {
               return;
@@ -417,17 +417,17 @@ void ReportUnblockingToConsole(
             } else {
               nsContentUtils::ReportToConsole(
                   nsIScriptError::warningFlag,
                   NS_LITERAL_CSTRING("Content Blocking"), doc,
                   nsContentUtils::eNECKO_PROPERTIES, messageWithDifferentOrigin,
                   params, 3, nullptr, sourceLine, lineNumber, columnNumber);
             }
           }),
-      kMaxConsoleOutputDelayMs);
+      kMaxConsoleOutputDelayMs, EventQueuePriority::Idle);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 }
 
 already_AddRefed<nsPIDOMWindowOuter> GetTopWindow(nsPIDOMWindowInner* aWindow) {
   Document* document = aWindow->GetExtantDoc();
   if (!document) {
--- a/widget/nsBaseWidget.cpp
+++ b/widget/nsBaseWidget.cpp
@@ -2075,21 +2075,21 @@ void nsBaseWidget::UnregisterPluginWindo
   }
   MOZ_ASSERT(sPluginWidgetList);
   sPluginWidgetList->Remove(id);
 #endif
 }
 
 nsresult nsBaseWidget::AsyncEnableDragDrop(bool aEnable) {
   RefPtr<nsBaseWidget> kungFuDeathGrip = this;
-  return NS_IdleDispatchToCurrentThread(
+  return NS_DispatchToCurrentThreadQueue(
       NS_NewRunnableFunction(
           "AsyncEnableDragDropFn",
           [this, aEnable, kungFuDeathGrip]() { EnableDragDrop(aEnable); }),
-      kAsyncDragDropTimeout);
+      kAsyncDragDropTimeout, EventQueuePriority::Idle);
 }
 
 // static
 nsIWidget* nsIWidget::LookupRegisteredPluginWindow(uintptr_t aWindowID) {
 #if !defined(XP_WIN) && !defined(MOZ_WIDGET_GTK)
   MOZ_ASSERT_UNREACHABLE(
       "nsBaseWidget::LookupRegisteredPluginWindow "
       "not implemented!");
--- a/xpcom/base/CycleCollectedJSContext.cpp
+++ b/xpcom/base/CycleCollectedJSContext.cpp
@@ -422,17 +422,17 @@ void CycleCollectedJSContext::IsIdleGCTa
       return NS_OK;
     }
 
     nsresult Cancel() override { return NS_OK; }
   };
 
   if (Runtime()->IsIdleGCTaskNeeded()) {
     nsCOMPtr<nsIRunnable> gc_task = new IdleTimeGCTaskRunnable();
-    NS_IdleDispatchToCurrentThread(gc_task.forget());
+    NS_DispatchToCurrentThreadQueue(gc_task.forget(), EventQueuePriority::Idle);
     Runtime()->SetPendingIdleGCTask();
   }
 }
 
 uint32_t CycleCollectedJSContext::RecursionDepth() const {
   return mOwningThread->RecursionDepth();
 }
 
--- a/xpcom/base/CycleCollectedJSRuntime.cpp
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -1321,17 +1321,18 @@ void CycleCollectedJSRuntime::FinalizeDe
 
   mFinalizeRunnable =
       new IncrementalFinalizeRunnable(this, mDeferredFinalizerTable);
 
   // Everything should be gone now.
   MOZ_ASSERT(mDeferredFinalizerTable.Count() == 0);
 
   if (aType == CycleCollectedJSContext::FinalizeIncrementally) {
-    NS_IdleDispatchToCurrentThread(do_AddRef(mFinalizeRunnable), 2500);
+    NS_DispatchToCurrentThreadQueue(do_AddRef(mFinalizeRunnable), 2500,
+                                    EventQueuePriority::Idle);
   } else {
     mFinalizeRunnable->ReleaseNow(false);
     MOZ_ASSERT(!mFinalizeRunnable);
   }
 }
 
 const char* CycleCollectedJSRuntime::OOMStateToString(
     const OOMState aOomState) const {
--- a/xpcom/base/MemoryTelemetry.cpp
+++ b/xpcom/base/MemoryTelemetry.cpp
@@ -520,19 +520,21 @@ nsresult MemoryTelemetry::Observe(nsISup
     auto now = TimeStamp::Now();
     if (!mLastPoll.IsNull() &&
         (now - mLastPoll).ToMilliseconds() < kTelemetryInterval) {
       return NS_OK;
     }
 
     mLastPoll = now;
 
-    NS_IdleDispatchToCurrentThread(NewRunnableMethod<std::function<void()>>(
-        "MemoryTelemetry::GatherReports", this, &MemoryTelemetry::GatherReports,
-        nullptr));
+    NS_DispatchToCurrentThreadQueue(
+        NewRunnableMethod<std::function<void()>>(
+            "MemoryTelemetry::GatherReports", this,
+            &MemoryTelemetry::GatherReports, nullptr),
+        EventQueuePriority::Idle);
   } else if (strcmp(aTopic, "content-child-shutdown") == 0) {
     if (nsCOMPtr<nsITelemetry> telemetry =
             do_GetService("@mozilla.org/base/telemetry;1")) {
       telemetry->FlushBatchedChildTelemetry();
     }
   }
   return NS_OK;
 }
--- a/xpcom/tests/gtest/TestEventPriorities.cpp
+++ b/xpcom/tests/gtest/TestEventPriorities.cpp
@@ -50,19 +50,19 @@ NS_IMPL_ISUPPORTS_INHERITED(TestEvent, R
 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_DispatchToCurrentThreadQueue(do_AddRef(evIdle), EventQueuePriority::Idle);
+  NS_DispatchToCurrentThreadQueue(do_AddRef(evIdle), EventQueuePriority::Idle);
+  NS_DispatchToCurrentThreadQueue(do_AddRef(evIdle), EventQueuePriority::Idle);
   NS_DispatchToMainThread(evNormal);
   NS_DispatchToMainThread(evNormal);
   NS_DispatchToMainThread(evNormal);
 
   MOZ_ALWAYS_TRUE(
       SpinEventLoopUntil([&]() { return normalRan == 3 && idleRan == 3; }));
 }
 
--- a/xpcom/tests/gtest/TestThreadUtils.cpp
+++ b/xpcom/tests/gtest/TestThreadUtils.cpp
@@ -602,23 +602,23 @@ class IdleObject final {
         NewRunnableMethod("IdleObject::Method3", this, &IdleObject::Method3));
   }
 
   void Method3() {
     CheckExecutedMethods("Method3", 3);
 
     NS_NewTimerWithFuncCallback(getter_AddRefs(mTimer), Method4, this, 10,
                                 nsITimer::TYPE_ONE_SHOT, "IdleObject::Method3");
-    NS_IdleDispatchToCurrentThread(
+    NS_DispatchToCurrentThreadQueue(
         NewIdleRunnableMethodWithTimer("IdleObject::Method5", this,
                                        &IdleObject::Method5),
-        50);
-    NS_IdleDispatchToCurrentThread(
+        50, EventQueuePriority::Idle);
+    NS_DispatchToCurrentThreadQueue(
         NewRunnableMethod("IdleObject::Method6", this, &IdleObject::Method6),
-        100);
+        100, EventQueuePriority::Idle);
 
     PR_Sleep(PR_MillisecondsToInterval(200));
     mRunnableExecuted[3] = true;
     mSetIdleDeadlineCalled = false;
   }
 
   static void Method4(nsITimer* aTimer, void* aClosure) {
     RefPtr<IdleObject> self = static_cast<IdleObject*>(aClosure);
@@ -659,33 +659,43 @@ TEST(ThreadUtils, IdleRunnableMethod) {
     RefPtr<IdleObject> idle = new IdleObject();
     RefPtr<IdleObjectWithoutSetDeadline> idleNoSetDeadline =
         new IdleObjectWithoutSetDeadline();
     RefPtr<IdleObjectInheritedSetDeadline> idleInheritedSetDeadline =
         new IdleObjectInheritedSetDeadline();
 
     NS_DispatchToCurrentThread(
         NewRunnableMethod("IdleObject::Method0", idle, &IdleObject::Method0));
-    NS_IdleDispatchToCurrentThread(NewIdleRunnableMethod(
-        "IdleObject::Method1", idle, &IdleObject::Method1));
-    NS_IdleDispatchToCurrentThread(
+    NS_DispatchToCurrentThreadQueue(
+        NewIdleRunnableMethod("IdleObject::Method1", idle,
+                              &IdleObject::Method1),
+        EventQueuePriority::Idle);
+    NS_DispatchToCurrentThreadQueue(
         NewIdleRunnableMethodWithTimer("IdleObject::Method2", idle,
                                        &IdleObject::Method2),
-        60000);
-    NS_IdleDispatchToCurrentThread(NewIdleRunnableMethod(
-        "IdleObject::Method7", idle, &IdleObject::Method7));
-    NS_IdleDispatchToCurrentThread(NewIdleRunnableMethod<const char*, uint32_t>(
-        "IdleObject::CheckExecutedMethods", idle,
-        &IdleObject::CheckExecutedMethods, "final", 8));
-    NS_IdleDispatchToCurrentThread(NewIdleRunnableMethod(
-        "IdleObjectWithoutSetDeadline::Method", idleNoSetDeadline,
-        &IdleObjectWithoutSetDeadline::Method));
-    NS_IdleDispatchToCurrentThread(NewIdleRunnableMethod(
-        "IdleObjectInheritedSetDeadline::Method", idleInheritedSetDeadline,
-        &IdleObjectInheritedSetDeadline::Method));
+        60000, EventQueuePriority::Idle);
+    NS_DispatchToCurrentThreadQueue(
+        NewIdleRunnableMethod("IdleObject::Method7", idle,
+                              &IdleObject::Method7),
+        EventQueuePriority::Idle);
+    NS_DispatchToCurrentThreadQueue(
+        NewIdleRunnableMethod<const char*, uint32_t>(
+            "IdleObject::CheckExecutedMethods", idle,
+            &IdleObject::CheckExecutedMethods, "final", 8),
+        EventQueuePriority::Idle);
+    NS_DispatchToCurrentThreadQueue(
+        NewIdleRunnableMethod("IdleObjectWithoutSetDeadline::Method",
+                              idleNoSetDeadline,
+                              &IdleObjectWithoutSetDeadline::Method),
+        EventQueuePriority::Idle);
+    NS_DispatchToCurrentThreadQueue(
+        NewIdleRunnableMethod("IdleObjectInheritedSetDeadline::Method",
+                              idleInheritedSetDeadline,
+                              &IdleObjectInheritedSetDeadline::Method),
+        EventQueuePriority::Idle);
 
     NS_ProcessPendingEvents(nullptr);
 
     ASSERT_TRUE(idleNoSetDeadline->mRunnableExecuted);
     ASSERT_TRUE(idleInheritedSetDeadline->mRunnableExecuted);
     ASSERT_TRUE(idleInheritedSetDeadline->mSetDeadlineCalled);
   }
 }
--- a/xpcom/threads/AbstractEventQueue.h
+++ b/xpcom/threads/AbstractEventQueue.h
@@ -14,16 +14,17 @@
 class nsIRunnable;
 
 namespace mozilla {
 
 enum class EventQueuePriority {
   High,
   Input,
   Normal,
+  DeferredTimers,
   Idle,
 
   Count
 };
 
 // AbstractEventQueue is an abstract base class for all our unsynchronized event
 // queue implementations:
 // - EventQueue: A queue of runnables. Used for non-main threads.
--- a/xpcom/threads/IdleTaskRunner.cpp
+++ b/xpcom/threads/IdleTaskRunner.cpp
@@ -80,17 +80,17 @@ static void TimedOut(nsITimer* aTimer, v
 
 void IdleTaskRunner::SetDeadline(mozilla::TimeStamp aDeadline) {
   mDeadline = aDeadline;
 };
 
 void IdleTaskRunner::SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) {
   MOZ_ASSERT(NS_IsMainThread());
   // aTarget is always the main thread event target provided from
-  // NS_IdleDispatchToCurrentThread(). We ignore aTarget here to ensure that
+  // NS_DispatchToCurrentThreadQueue(). We ignore aTarget here to ensure that
   // CollectorRunner always run specifically on SystemGroup::EventTargetFor(
   // TaskCategory::GarbageCollection) of the main thread.
   SetTimerInternal(aDelay);
 }
 
 nsresult IdleTaskRunner::Cancel() {
   CancelTimer();
   mTimer = nullptr;
@@ -122,17 +122,18 @@ void IdleTaskRunner::Schedule(bool aAllo
     nsRefreshDriver::DispatchIdleRunnableAfterTick(this, mDelay);
     // Ensure we get called at some point, even if RefreshDriver is stopped.
     SetTimerInternal(mDelay);
   } else {
     // RefreshDriver doesn't seem to be running.
     if (aAllowIdleDispatch) {
       nsCOMPtr<nsIRunnable> runnable = this;
       SetTimerInternal(mDelay);
-      NS_IdleDispatchToCurrentThread(runnable.forget());
+      NS_DispatchToCurrentThreadQueue(runnable.forget(),
+                                      EventQueuePriority::Idle);
     } else {
       if (!mScheduleTimer) {
         nsIEventTarget* target = nullptr;
         if (TaskCategory::Count != mTaskCategory) {
           target = SystemGroup::EventTargetFor(mTaskCategory);
         }
         mScheduleTimer = NS_NewTimer(target);
         if (!mScheduleTimer) {
--- a/xpcom/threads/LazyIdleThread.cpp
+++ b/xpcom/threads/LazyIdleThread.cpp
@@ -481,17 +481,18 @@ NS_IMETHODIMP
 LazyIdleThread::HasPendingHighPriorityEvents(bool* aHasPendingEvents) {
   // This is only supposed to be called from the thread itself so it's not
   // implemented here.
   MOZ_ASSERT_UNREACHABLE("Shouldn't ever call this!");
   return NS_ERROR_UNEXPECTED;
 }
 
 NS_IMETHODIMP
-LazyIdleThread::IdleDispatch(already_AddRefed<nsIRunnable> aEvent) {
+LazyIdleThread::DispatchToQueue(already_AddRefed<nsIRunnable> aEvent,
+                                EventQueuePriority aQueue) {
   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.
   MOZ_ASSERT_UNREACHABLE("Shouldn't ever call this!");
--- a/xpcom/threads/MainThreadQueue.h
+++ b/xpcom/threads/MainThreadQueue.h
@@ -21,16 +21,17 @@ inline already_AddRefed<nsThread> Create
     nsIIdlePeriod* aIdlePeriod,
     SynchronizedQueueT** aSynchronizedQueue = nullptr) {
   using MainThreadQueueT = PrioritizedEventQueue<InnerQueueT>;
 
   auto queue = MakeUnique<MainThreadQueueT>(
       MakeUnique<InnerQueueT>(EventQueuePriority::High),
       MakeUnique<InnerQueueT>(EventQueuePriority::Input),
       MakeUnique<InnerQueueT>(EventQueuePriority::Normal),
+      MakeUnique<InnerQueueT>(EventQueuePriority::DeferredTimers),
       MakeUnique<InnerQueueT>(EventQueuePriority::Idle),
       do_AddRef(aIdlePeriod));
 
   MainThreadQueueT* prioritized = queue.get();
 
   RefPtr<SynchronizedQueueT> synchronizedQueue =
       new SynchronizedQueueT(std::move(queue));
 
--- a/xpcom/threads/PrioritizedEventQueue.cpp
+++ b/xpcom/threads/PrioritizedEventQueue.cpp
@@ -11,21 +11,24 @@
 #include "nsXPCOMPrivate.h"  // for gXPCOMThreadsShutDown
 #include "InputEventStatistics.h"
 
 using namespace mozilla;
 
 template <class InnerQueueT>
 PrioritizedEventQueue<InnerQueueT>::PrioritizedEventQueue(
     UniquePtr<InnerQueueT> aHighQueue, UniquePtr<InnerQueueT> aInputQueue,
-    UniquePtr<InnerQueueT> aNormalQueue, UniquePtr<InnerQueueT> aIdleQueue,
+    UniquePtr<InnerQueueT> aNormalQueue,
+    UniquePtr<InnerQueueT> aDeferredTimersQueue,
+    UniquePtr<InnerQueueT> aIdleQueue,
     already_AddRefed<nsIIdlePeriod> aIdlePeriod)
     : mHighQueue(std::move(aHighQueue)),
       mInputQueue(std::move(aInputQueue)),
       mNormalQueue(std::move(aNormalQueue)),
+      mDeferredTimersQueue(std::move(aDeferredTimersQueue)),
       mIdleQueue(std::move(aIdleQueue)),
       mIdlePeriod(aIdlePeriod) {
   static_assert(IsBaseOf<AbstractEventQueue, InnerQueueT>::value,
                 "InnerQueueT must be an AbstractEventQueue subclass");
 }
 
 template <class InnerQueueT>
 void PrioritizedEventQueue<InnerQueueT>::PutEvent(
@@ -45,16 +48,19 @@ void PrioritizedEventQueue<InnerQueueT>:
       mHighQueue->PutEvent(event.forget(), priority, aProofOfLock);
       break;
     case EventQueuePriority::Input:
       mInputQueue->PutEvent(event.forget(), priority, aProofOfLock);
       break;
     case EventQueuePriority::Normal:
       mNormalQueue->PutEvent(event.forget(), priority, aProofOfLock);
       break;
+    case EventQueuePriority::DeferredTimers:
+      mDeferredTimersQueue->PutEvent(event.forget(), priority, aProofOfLock);
+      break;
     case EventQueuePriority::Idle:
       mIdleQueue->PutEvent(event.forget(), priority, aProofOfLock);
       break;
     case EventQueuePriority::Count:
       MOZ_CRASH("EventQueuePriority::Count isn't a valid priority");
       break;
   }
 }
@@ -103,18 +109,16 @@ TimeStamp PrioritizedEventQueue<InnerQue
     return TimeStamp::Now();
   }
   return idleDeadline;
 }
 
 template <class InnerQueueT>
 EventQueuePriority PrioritizedEventQueue<InnerQueueT>::SelectQueue(
     bool aUpdateState, const MutexAutoLock& aProofOfLock) {
-  bool highPending = !mHighQueue->IsEmpty(aProofOfLock);
-  bool normalPending = !mNormalQueue->IsEmpty(aProofOfLock);
   size_t inputCount = mInputQueue->Count(aProofOfLock);
 
   if (aUpdateState && mInputQueueState == STATE_ENABLED &&
       mInputHandlingStartTime.IsNull() && inputCount > 0) {
     mInputHandlingStartTime =
         InputEventStatistics::Get().GetInputHandlingStartTime(inputCount);
   }
 
@@ -126,42 +130,47 @@ EventQueuePriority PrioritizedEventQueue
   // 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
+  // DEFERREDTIMERS: if GetIdleDeadline()
   // 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.
   EventQueuePriority queue;
+  bool highPending = !mHighQueue->IsEmpty(aProofOfLock);
 
   if (mProcessHighPriorityQueue) {
     queue = EventQueuePriority::High;
   } else if (inputCount > 0 && (mInputQueueState == STATE_FLUSHING ||
                                 (mInputQueueState == STATE_ENABLED &&
                                  !mInputHandlingStartTime.IsNull() &&
                                  TimeStamp::Now() > mInputHandlingStartTime))) {
     queue = EventQueuePriority::Input;
-  } else if (normalPending) {
+  } else if (!mNormalQueue->IsEmpty(aProofOfLock)) {
     MOZ_ASSERT(mInputQueueState != STATE_FLUSHING,
-               "Shouldn't consume normal event when flusing input events");
+               "Shouldn't consume normal event when flushing input events");
     queue = EventQueuePriority::Normal;
   } else if (highPending) {
     queue = EventQueuePriority::High;
   } else if (inputCount > 0 && mInputQueueState != STATE_SUSPEND) {
     MOZ_ASSERT(
         mInputQueueState != STATE_DISABLED,
         "Shouldn't consume input events when the input queue is disabled");
     queue = EventQueuePriority::Input;
+  } else if (!mDeferredTimersQueue->IsEmpty(aProofOfLock)) {
+    // We may not actually return an idle event in this case.
+    queue = EventQueuePriority::DeferredTimers;
   } else {
     // We may not actually return an idle event in this case.
     queue = EventQueuePriority::Idle;
   }
 
   MOZ_ASSERT_IF(
       queue == EventQueuePriority::Input,
       mInputQueueState != STATE_DISABLED && mInputQueueState != STATE_SUSPEND);
@@ -207,30 +216,36 @@ already_AddRefed<nsIRunnable> Prioritize
   }
 
   if (queue == EventQueuePriority::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 == EventQueuePriority::Idle);
+  // If we get here, then all queues except deferredtimers and idle are empty.
+  MOZ_ASSERT(queue == EventQueuePriority::Idle ||
+             queue == EventQueuePriority::DeferredTimers);
 
-  if (mIdleQueue->IsEmpty(aProofOfLock)) {
+  if (mIdleQueue->IsEmpty(aProofOfLock) &&
+      mDeferredTimersQueue->IsEmpty(aProofOfLock)) {
     MOZ_ASSERT(!mHasPendingEventsPromisedIdleEvent);
     return nullptr;
   }
 
   TimeStamp idleDeadline = GetIdleDeadline();
   if (!idleDeadline) {
     return nullptr;
   }
 
-  nsCOMPtr<nsIRunnable> event = mIdleQueue->GetEvent(aPriority, aProofOfLock);
+  nsCOMPtr<nsIRunnable> event =
+      mDeferredTimersQueue->GetEvent(aPriority, aProofOfLock);
+  if (!event) {
+    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
@@ -245,16 +260,17 @@ already_AddRefed<nsIRunnable> Prioritize
 template <class InnerQueueT>
 bool PrioritizedEventQueue<InnerQueueT>::IsEmpty(
     const MutexAutoLock& aProofOfLock) {
   // Just check IsEmpty() on the sub-queues. Don't bother checking the idle
   // deadline since that only determines whether an idle event is ready or not.
   return mHighQueue->IsEmpty(aProofOfLock) &&
          mInputQueue->IsEmpty(aProofOfLock) &&
          mNormalQueue->IsEmpty(aProofOfLock) &&
+         mDeferredTimersQueue->IsEmpty(aProofOfLock) &&
          mIdleQueue->IsEmpty(aProofOfLock);
 }
 
 template <class InnerQueueT>
 bool PrioritizedEventQueue<InnerQueueT>::HasReadyEvent(
     const MutexAutoLock& aProofOfLock) {
   mHasPendingEventsPromisedIdleEvent = false;
 
@@ -263,26 +279,29 @@ bool PrioritizedEventQueue<InnerQueueT>:
   if (queue == EventQueuePriority::High) {
     return mHighQueue->HasReadyEvent(aProofOfLock);
   } else if (queue == EventQueuePriority::Input) {
     return mInputQueue->HasReadyEvent(aProofOfLock);
   } else if (queue == EventQueuePriority::Normal) {
     return mNormalQueue->HasReadyEvent(aProofOfLock);
   }
 
-  MOZ_ASSERT(queue == EventQueuePriority::Idle);
+  MOZ_ASSERT(queue == EventQueuePriority::Idle ||
+             queue == EventQueuePriority::DeferredTimers);
 
   // If we get here, then both the high and normal queues are empty.
 
-  if (mIdleQueue->IsEmpty(aProofOfLock)) {
+  if (mDeferredTimersQueue->IsEmpty(aProofOfLock) &&
+      mIdleQueue->IsEmpty(aProofOfLock)) {
     return false;
   }
 
   TimeStamp idleDeadline = GetIdleDeadline();
-  if (idleDeadline && mIdleQueue->HasReadyEvent(aProofOfLock)) {
+  if (idleDeadline && (mDeferredTimersQueue->HasReadyEvent(aProofOfLock) ||
+                       mIdleQueue->HasReadyEvent(aProofOfLock))) {
     mHasPendingEventsPromisedIdleEvent = true;
     return true;
   }
 
   return false;
 }
 
 template <class InnerQueueT>
--- a/xpcom/threads/PrioritizedEventQueue.h
+++ b/xpcom/threads/PrioritizedEventQueue.h
@@ -38,16 +38,17 @@ namespace mozilla {
 template <class InnerQueueT>
 class PrioritizedEventQueue final : public AbstractEventQueue {
  public:
   static const bool SupportsPrioritization = true;
 
   PrioritizedEventQueue(UniquePtr<InnerQueueT> aHighQueue,
                         UniquePtr<InnerQueueT> aInputQueue,
                         UniquePtr<InnerQueueT> aNormalQueue,
+                        UniquePtr<InnerQueueT> aDeferredTimersQueue,
                         UniquePtr<InnerQueueT> aIdleQueue,
                         already_AddRefed<nsIIdlePeriod> aIdlePeriod);
 
   void PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
                 EventQueuePriority aPriority,
                 const MutexAutoLock& aProofOfLock) final;
   already_AddRefed<nsIRunnable> GetEvent(
       EventQueuePriority* aPriority, const MutexAutoLock& aProofOfLock) final;
@@ -79,16 +80,17 @@ class PrioritizedEventQueue final : publ
 
   size_t SizeOfExcludingThis(
       mozilla::MallocSizeOf aMallocSizeOf) const override {
     size_t n = 0;
 
     n += mHighQueue->SizeOfIncludingThis(aMallocSizeOf);
     n += mInputQueue->SizeOfIncludingThis(aMallocSizeOf);
     n += mNormalQueue->SizeOfIncludingThis(aMallocSizeOf);
+    n += mDeferredTimersQueue->SizeOfIncludingThis(aMallocSizeOf);
     n += mIdleQueue->SizeOfIncludingThis(aMallocSizeOf);
 
     if (mIdlePeriod) {
       n += aMallocSizeOf(mIdlePeriod);
     }
 
     return n;
   }
@@ -98,16 +100,17 @@ class PrioritizedEventQueue final : publ
                                  const MutexAutoLock& aProofOfLock);
 
   // 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> mDeferredTimersQueue;
   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;
 
 #ifndef RELEASE_OR_BETA
   // Pointer to a place where the most recently computed idle deadline is
--- a/xpcom/threads/nsIThread.idl
+++ b/xpcom/threads/nsIThread.idl
@@ -5,20 +5,22 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISerialEventTarget.idl"
 
 %{C++
 #include "mozilla/AlreadyAddRefed.h"
 namespace mozilla {
 class TimeStamp;
+enum class EventQueuePriority;
 }
 %}
 
 [ptr] native PRThread(PRThread);
+native EventQueuePriority(mozilla::EventQueuePriority);
 
 native nsIEventTargetPtr(nsIEventTarget*);
 native nsISerialEventTargetPtr(nsISerialEventTarget*);
 native TimeStamp(mozilla::TimeStamp);
 
 /**
  * This interface provides a high-level abstraction for an operating system
  * thread.
@@ -125,30 +127,35 @@ 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();
 
   /**
-   * Dispatch an event to the thread's idle queue.  This function may be called
-   * from any thread, and it may be called re-entrantly.
+   * Dispatch an event to a specified queue for the thread.  This function
+   * may be called from any thread, and it may be called re-entrantly.
+   * Most users should use the NS_Dispatch*() functions in nsThreadUtils instead
+   * of calling this directly.
    *
    * @param event
    *   The alreadyAddRefed<> event to dispatch.
    *   NOTE that the event will be leaked if it fails to dispatch.
+   * @param queue
+   *   Which event priority queue this should be added to
    *
    * @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 dispatchToQueue(in alreadyAddRefed_nsIRunnable event,
+                                  in EventQueuePriority queue);
 
   /**
    * 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/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -895,24 +895,25 @@ nsThread::HasPendingHighPriorityEvents(b
     return NS_ERROR_NOT_SAME_THREAD;
   }
 
   *aResult = mEvents->HasPendingHighPriorityEvents();
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsThread::IdleDispatch(already_AddRefed<nsIRunnable> aEvent) {
+nsThread::DispatchToQueue(already_AddRefed<nsIRunnable> aEvent,
+                          EventQueuePriority aQueue) {
   nsCOMPtr<nsIRunnable> event = aEvent;
 
   if (NS_WARN_IF(!event)) {
     return NS_ERROR_INVALID_ARG;
   }
 
-  if (!mEvents->PutEvent(event.forget(), EventQueuePriority::Idle)) {
+  if (!mEvents->PutEvent(event.forget(), aQueue)) {
     NS_WARNING(
         "An idle event was posted to a thread that will never run it "
         "(rejected)");
     return NS_ERROR_UNEXPECTED;
   }
 
   return NS_OK;
 }
--- a/xpcom/threads/nsThreadManager.cpp
+++ b/xpcom/threads/nsThreadManager.cpp
@@ -575,26 +575,28 @@ bool nsThreadManager::MainThreadHasPendi
     get().mMainThread->HasPendingHighPriorityEvents(&retVal);
   }
   return retVal;
 }
 
 NS_IMETHODIMP
 nsThreadManager::IdleDispatchToMainThread(nsIRunnable* aEvent,
                                           uint32_t aTimeout) {
-  // Note: C++ callers should instead use NS_IdleDispatchToThread or
-  // NS_IdleDispatchToCurrentThread.
+  // Note: C++ callers should instead use NS_DispatchToThreadQueue or
+  // NS_DispatchToCurrentThreadQueue.
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIRunnable> event(aEvent);
   if (aTimeout) {
-    return NS_IdleDispatchToThread(event.forget(), aTimeout, mMainThread);
+    return NS_DispatchToThreadQueue(event.forget(), aTimeout, mMainThread,
+                                    EventQueuePriority::Idle);
   }
 
-  return NS_IdleDispatchToThread(event.forget(), mMainThread);
+  return NS_DispatchToThreadQueue(event.forget(), mMainThread,
+                                  EventQueuePriority::Idle);
 }
 
 namespace mozilla {
 
 PRThread* GetCurrentVirtualThread() {
   // We call GetCurrentVirtualThread very early in startup, before the TLS is
   // initialized. Make sure we don't assert in that case.
   if (gTlsCurrentVirtualThread.initialized()) {
--- a/xpcom/threads/nsThreadUtils.cpp
+++ b/xpcom/threads/nsThreadUtils.cpp
@@ -261,49 +261,51 @@ nsresult NS_DelayedDispatchToCurrentThre
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 #endif
 
   return thread->DelayedDispatch(event.forget(), aDelayMs);
 }
 
-nsresult NS_IdleDispatchToThread(already_AddRefed<nsIRunnable>&& aEvent,
-                                 nsIThread* aThread) {
+nsresult NS_DispatchToThreadQueue(already_AddRefed<nsIRunnable>&& aEvent,
+                                  nsIThread* aThread,
+                                  EventQueuePriority aQueue) {
   nsresult rv;
   nsCOMPtr<nsIRunnable> event(aEvent);
   NS_ENSURE_TRUE(event, NS_ERROR_INVALID_ARG);
   if (!aThread) {
     return NS_ERROR_UNEXPECTED;
   }
   // To keep us from leaking the runnable if dispatch method fails,
   // we grab the reference on failures and release it.
   nsIRunnable* temp = event.get();
-  rv = aThread->IdleDispatch(event.forget());
+  rv = aThread->DispatchToQueue(event.forget(), aQueue);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     // Dispatch() leaked the reference to the event, but due to caller's
     // assumptions, we shouldn't leak here. And given we are on the same
     // thread as the dispatch target, it's mostly safe to do it here.
     NS_RELEASE(temp);
   }
 
   return rv;
 }
 
-nsresult NS_IdleDispatchToCurrentThread(
-    already_AddRefed<nsIRunnable>&& aEvent) {
-  return NS_IdleDispatchToThread(std::move(aEvent), NS_GetCurrentThread());
+nsresult NS_DispatchToCurrentThreadQueue(already_AddRefed<nsIRunnable>&& aEvent,
+                                         EventQueuePriority aQueue) {
+  return NS_DispatchToThreadQueue(std::move(aEvent), NS_GetCurrentThread(),
+                                  aQueue);
 }
 
-extern nsresult NS_IdleDispatchToMainThread(
-    already_AddRefed<nsIRunnable>&& aEvent) {
+extern nsresult NS_DispatchToMainThreadQueue(
+    already_AddRefed<nsIRunnable>&& aEvent, EventQueuePriority aQueue) {
   nsCOMPtr<nsIThread> mainThread;
   nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread));
   if (NS_SUCCEEDED(rv)) {
-    return NS_IdleDispatchToThread(std::move(aEvent), mainThread);
+    return NS_DispatchToThreadQueue(std::move(aEvent), mainThread, aQueue);
   }
   return rv;
 }
 
 class IdleRunnableWrapper final : public IdleRunnable {
  public:
   explicit IdleRunnableWrapper(already_AddRefed<nsIRunnable>&& aEvent)
       : mRunnable(std::move(aEvent)) {}
@@ -354,43 +356,47 @@ class IdleRunnableWrapper final : public
       mTimer->Cancel();
     }
   }
 
   nsCOMPtr<nsITimer> mTimer;
   nsCOMPtr<nsIRunnable> mRunnable;
 };
 
-extern nsresult NS_IdleDispatchToThread(already_AddRefed<nsIRunnable>&& aEvent,
-                                        uint32_t aTimeout, nsIThread* aThread) {
+extern nsresult NS_DispatchToThreadQueue(already_AddRefed<nsIRunnable>&& aEvent,
+                                         uint32_t aTimeout, nsIThread* aThread,
+                                         EventQueuePriority aQueue) {
   nsCOMPtr<nsIRunnable> event(std::move(aEvent));
   NS_ENSURE_TRUE(event, NS_ERROR_INVALID_ARG);
+  MOZ_ASSERT(aQueue == EventQueuePriority::Idle ||
+             aQueue == EventQueuePriority::DeferredTimers);
 
   // XXX Using current thread for now as the nsIEventTarget.
   nsIEventTarget* target = mozilla::GetCurrentThreadEventTarget();
   if (!target) {
     return NS_ERROR_UNEXPECTED;
   }
 
   nsCOMPtr<nsIIdleRunnable> idleEvent = do_QueryInterface(event);
 
   if (!idleEvent) {
     idleEvent = new IdleRunnableWrapper(event.forget());
     event = do_QueryInterface(idleEvent);
     MOZ_DIAGNOSTIC_ASSERT(event);
   }
   idleEvent->SetTimer(aTimeout, target);
 
-  return NS_IdleDispatchToThread(event.forget(), aThread);
+  return NS_DispatchToThreadQueue(event.forget(), aThread, aQueue);
 }
 
-extern nsresult NS_IdleDispatchToCurrentThread(
-    already_AddRefed<nsIRunnable>&& aEvent, uint32_t aTimeout) {
-  return NS_IdleDispatchToThread(std::move(aEvent), aTimeout,
-                                 NS_GetCurrentThread());
+extern nsresult NS_DispatchToCurrentThreadQueue(
+    already_AddRefed<nsIRunnable>&& aEvent, uint32_t aTimeout,
+    EventQueuePriority aQueue) {
+  return NS_DispatchToThreadQueue(std::move(aEvent), aTimeout,
+                                  NS_GetCurrentThread(), aQueue);
 }
 
 #ifndef XPCOM_GLUE_AVOID_NSPR
 nsresult NS_ProcessPendingEvents(nsIThread* aThread, PRIntervalTime aTimeout) {
   nsresult rv = NS_OK;
 
 #  ifdef MOZILLA_INTERNAL_API
   if (!aThread) {
--- a/xpcom/threads/nsThreadUtils.h
+++ b/xpcom/threads/nsThreadUtils.h
@@ -17,16 +17,17 @@
 #include "nsIRunnable.h"
 #include "nsIThreadManager.h"
 #include "nsITimer.h"
 #include "nsIThread.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "xpcpublic.h"
+#include "mozilla/AbstractEventQueue.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Likely.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Move.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Tuple.h"
 #include "mozilla/TypeTraits.h"
 
@@ -108,102 +109,114 @@ extern nsresult NS_DispatchToMainThread(
 extern nsresult NS_DispatchToMainThread(
     already_AddRefed<nsIRunnable>&& aEvent,
     uint32_t aDispatchFlags = NS_DISPATCH_NORMAL);
 
 extern nsresult NS_DelayedDispatchToCurrentThread(
     already_AddRefed<nsIRunnable>&& aEvent, uint32_t aDelayMs);
 
 /**
- * Dispatch the given event to the idle queue of the current thread.
+ * Dispatch the given event to the specified queue of the current thread.
  *
- * @param aEvent
- *   The event to dispatch.
+ * @param aEvent The event to dispatch.
+ * @param aQueue The event queue for the thread to use
  *
  * @returns NS_ERROR_INVALID_ARG
  *   If event is null.
  * @returns NS_ERROR_UNEXPECTED
  *   If the thread is shutting down.
  */
-extern nsresult NS_IdleDispatchToCurrentThread(
-    already_AddRefed<nsIRunnable>&& aEvent);
+extern nsresult NS_DispatchToCurrentThreadQueue(
+    already_AddRefed<nsIRunnable>&& aEvent, mozilla::EventQueuePriority aQueue);
 
 /**
- * Dispatch the given event to the idle queue of the main thread.
+ * Dispatch the given event to the specified queue of the main thread.
  *
  * @param aEvent The event to dispatch.
+ * @param aQueue The event queue for the thread to use
  *
  * @returns NS_ERROR_INVALID_ARG
  *   If event is null.
  * @returns NS_ERROR_UNEXPECTED
  *   If the thread is shutting down.
  */
-extern nsresult NS_IdleDispatchToMainThread(
-    already_AddRefed<nsIRunnable>&& aEvent);
+extern nsresult NS_DispatchToMainThreadQueue(
+    already_AddRefed<nsIRunnable>&& aEvent, mozilla::EventQueuePriority aQueue);
 
 /**
- * Dispatch the given event to the idle queue of the current thread.
+ * Dispatch the given event to an idle queue of the current thread.
  *
  * @param aEvent The event to dispatch. If the event implements
  *   nsIIdleRunnable, it will receive a call on
  *   nsIIdleRunnable::SetTimer when dispatched, with the value of
  *   aTimeout.
  *
  * @param aTimeout The time in milliseconds until the event should be
- *   moved from the idle queue to the regular queue, if it hasn't been
+ *   moved from an idle queue to the regular queue, if it hasn't been
  *   executed. If aEvent is also an nsIIdleRunnable, it is expected
  *   that it should handle the timeout itself, after a call to
  *   nsIIdleRunnable::SetTimer.
  *
+ * @param aQueue
+ *   The event queue for the thread to use.  Must be an idle queue
+ *   (Idle or DeferredTimers)
+ *
  * @returns NS_ERROR_INVALID_ARG
  *   If event is null.
  * @returns NS_ERROR_UNEXPECTED
  *   If the thread is shutting down.
  */
-extern nsresult NS_IdleDispatchToCurrentThread(
-    already_AddRefed<nsIRunnable>&& aEvent, uint32_t aTimeout);
+extern nsresult NS_DispatchToCurrentThreadQueue(
+    already_AddRefed<nsIRunnable>&& aEvent, uint32_t aTimeout,
+    mozilla::EventQueuePriority aQueue);
 
 /**
- * Dispatch the given event to the idle queue of a thread.
+ * Dispatch the given event to a queue of a thread.
  *
  * @param aEvent The event to dispatch.
- *
  * @param aThread The target thread for the dispatch.
+ * @param aQueue The event queue for the thread to use.
  *
  * @returns NS_ERROR_INVALID_ARG
  *   If event is null.
  * @returns NS_ERROR_UNEXPECTED
  *   If the thread is shutting down.
  */
-extern nsresult NS_IdleDispatchToThread(already_AddRefed<nsIRunnable>&& aEvent,
-                                        nsIThread* aThread);
+extern nsresult NS_DispatchToThreadQueue(already_AddRefed<nsIRunnable>&& aEvent,
+                                         nsIThread* aThread,
+                                         mozilla::EventQueuePriority aQueue);
 
 /**
- * Dispatch the given event to the idle queue of a thread.
+ * Dispatch the given event to an idle queue of a thread.
  *
  * @param aEvent The event to dispatch. If the event implements
  *   nsIIdleRunnable, it will receive a call on
  *   nsIIdleRunnable::SetTimer when dispatched, with the value of
  *   aTimeout.
  *
  * @param aTimeout The time in milliseconds until the event should be
- *   moved from the idle queue to the regular queue, if it hasn't been
+ *   moved from an idle queue to the regular queue, if it hasn't been
  *   executed. If aEvent is also an nsIIdleRunnable, it is expected
  *   that it should handle the timeout itself, after a call to
  *   nsIIdleRunnable::SetTimer.
  *
  * @param aThread The target thread for the dispatch.
  *
+ * @param aQueue
+ *   The event queue for the thread to use.  Must be an idle queue
+ *   (Idle or DeferredTimers)
+ *
  * @returns NS_ERROR_INVALID_ARG
  *   If event is null.
  * @returns NS_ERROR_UNEXPECTED
  *   If the thread is shutting down.
  */
-extern nsresult NS_IdleDispatchToThread(already_AddRefed<nsIRunnable>&& aEvent,
-                                        uint32_t aTimeout, nsIThread* aThread);
+extern nsresult NS_DispatchToThreadQueue(already_AddRefed<nsIRunnable>&& aEvent,
+                                         uint32_t aTimeout, nsIThread* aThread,
+                                         mozilla::EventQueuePriority aQueue);
 
 #ifndef XPCOM_GLUE_AVOID_NSPR
 /**
  * Process all pending events for the given thread before returning.  This
  * method simply calls ProcessNextEvent on the thread while HasPendingEvents
  * continues to return true and the time spent in NS_ProcessPendingEvents
  * does not exceed the given timeout value.
  *