Merge inbound to mozilla-central. a=merge
authorBrindusan Cristian <cbrindusan@mozilla.com>
Sun, 27 Jan 2019 11:35:28 +0200
changeset 455564 2013099b2c13997ffe1362e6e1e9cf09cd4cd4ac
parent 455563 11f88edba10fcab0a5e508b1fba9b58203a51fa4 (current diff)
parent 455550 ed3d84fc13910adbc9bc27ca3d59e3ee03b9eaf6 (diff)
child 455565 579d66b2e57dcaf66fab49df4be13ceb84ca150b
child 455570 d743672a092cac271f2a0ca0a7976161fb2c19f7
push id111488
push usercbrindusan@mozilla.com
push dateSun, 27 Jan 2019 09:39:53 +0000
treeherdermozilla-inbound@2013099b2c13 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone66.0a1
first release with
nightly linux32
2013099b2c13 / 66.0a1 / 20190127093605 / files
nightly linux64
2013099b2c13 / 66.0a1 / 20190127093605 / files
nightly mac
2013099b2c13 / 66.0a1 / 20190127093605 / files
nightly win32
2013099b2c13 / 66.0a1 / 20190127093605 / files
nightly win64
2013099b2c13 / 66.0a1 / 20190127093605 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
dom/svg/SVGAngle.cpp
dom/svg/SVGAngle.h
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1328,32 +1328,24 @@
         }
 
         if (event.target.localName == "tab") {
           gBrowser.removeTab(event.target, {
             animate: true,
             byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE,
           });
         } else if (event.originalTarget.localName == "scrollbox") {
-          // The user middleclicked an open space on the tabstrip. This could
-          // be because they intend to open a new tab, but it could also be
-          // because they just removed a tab and they now middleclicked on the
-          // resulting space while that tab is closing. In that case, we don't
-          // want to open a tab. So if we're removing one or more tabs, and
-          // the tab click is before the end of the last visible tab, we do
-          // nothing.
-          if (gBrowser._removingTabs.length) {
-            let visibleTabs = this._getVisibleTabs();
-            let lastTab = visibleTabs[visibleTabs.length - 1];
-            let endOfTab = lastTab.getBoundingClientRect()[RTL_UI ? "left" : "right"];
-            if ((!RTL_UI && event.clientX > endOfTab) ||
-                (RTL_UI && event.clientX < endOfTab)) {
-              BrowserOpenTab();
-            }
-          } else {
+          // The user middleclicked on the tabstrip. Check whether the click
+          // was dispatched on the open space of it.
+          let visibleTabs = this._getVisibleTabs();
+          let lastTab = visibleTabs[visibleTabs.length - 1];
+          let winUtils = window.windowUtils;
+          let endOfTab = winUtils.getBoundsWithoutFlushing(lastTab)[RTL_UI ? "left" : "right"];
+          if ((!RTL_UI && event.clientX > endOfTab) ||
+              (RTL_UI && event.clientX < endOfTab)) {
             BrowserOpenTab();
           }
         } else {
           return;
         }
 
         event.stopPropagation();
       ]]></handler>
--- 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
@@ -1290,16 +1290,17 @@ Document::Document(const char* aContentT
 #endif
       mHasDelayedRefreshEvent(false),
       mPendingFullscreenRequests(0),
       mXMLDeclarationBits(0),
       mOnloadBlockCount(0),
       mAsyncOnloadBlockCount(0),
       mCompatMode(eCompatibility_FullStandards),
       mReadyState(ReadyState::READYSTATE_UNINITIALIZED),
+      mAncestorIsLoading(false),
 #ifdef MOZILLA_INTERNAL_API
       mVisibilityState(dom::VisibilityState::Hidden),
 #else
       mDummy(0),
 #endif
       mType(eUnknown),
       mDefaultElementType(0),
       mAllowXULXBL(eTriUnset),
@@ -8111,24 +8112,62 @@ nsresult Document::CloneDocHelper(Docume
   // State from Document
   clone->mType = mType;
   clone->mXMLDeclarationBits = mXMLDeclarationBits;
   clone->mBaseTarget = mBaseTarget;
 
   return NS_OK;
 }
 
+static bool SetLoadingInSubDocument(Document* aDocument, void* aData) {
+  aDocument->SetAncestorLoading(*(static_cast<bool*>(aData)));
+  return true;
+}
+
+void Document::SetAncestorLoading(bool aAncestorIsLoading) {
+  NotifyLoading(mAncestorIsLoading, aAncestorIsLoading, mReadyState,
+                mReadyState);
+  mAncestorIsLoading = aAncestorIsLoading;
+}
+
+void Document::NotifyLoading(const bool& aCurrentParentIsLoading,
+                             bool aNewParentIsLoading,
+                             const ReadyState& aCurrentState,
+                             ReadyState aNewState) {
+  // Mirror the top-level loading state down to all subdocuments
+  bool was_loading = aCurrentParentIsLoading ||
+                     aCurrentState == READYSTATE_LOADING ||
+                     aCurrentState == READYSTATE_INTERACTIVE;
+  bool is_loading = aNewParentIsLoading || aNewState == READYSTATE_LOADING ||
+                    aNewState == READYSTATE_INTERACTIVE;  // new value for state
+  bool set_load_state = was_loading != is_loading;
+
+  if (set_load_state && StaticPrefs::dom_timeout_defer_during_load()) {
+    nsPIDOMWindowInner* inner = GetInnerWindow();
+    if (inner) {
+      inner->SetActiveLoadingState(is_loading);
+    }
+    EnumerateSubDocuments(SetLoadingInSubDocument, &is_loading);
+  }
+}
+
 void Document::SetReadyStateInternal(ReadyState rs) {
-  mReadyState = rs;
   if (rs == READYSTATE_UNINITIALIZED) {
     // Transition back to uninitialized happens only to keep assertions happy
     // right before readyState transitions to something else. Make this
     // transition undetectable by Web content.
-    return;
-  }
+    mReadyState = rs;
+    return;
+  }
+
+  if (READYSTATE_LOADING == rs) {
+    mLoadingTimeStamp = mozilla::TimeStamp::Now();
+  }
+  NotifyLoading(mAncestorIsLoading, mAncestorIsLoading, mReadyState, rs);
+  mReadyState = rs;
   if (mTiming) {
     switch (rs) {
       case READYSTATE_LOADING:
         mTiming->NotifyDOMLoading(Document::GetDocumentURI());
         break;
       case READYSTATE_INTERACTIVE:
         mTiming->NotifyDOMInteractive(Document::GetDocumentURI());
         break;
@@ -8136,19 +8175,16 @@ void Document::SetReadyStateInternal(Rea
         mTiming->NotifyDOMComplete(Document::GetDocumentURI());
         break;
       default:
         NS_WARNING("Unexpected ReadyState value");
         break;
     }
   }
   // At the time of loading start, we don't have timing object, record time.
-  if (READYSTATE_LOADING == rs) {
-    mLoadingTimeStamp = mozilla::TimeStamp::Now();
-  }
 
   if (READYSTATE_INTERACTIVE == rs) {
     if (nsContentUtils::IsSystemPrincipal(NodePrincipal())) {
       Element* root = GetRootElement();
       if (root && root->HasAttr(kNameSpaceID_None, nsGkAtoms::mozpersist)) {
         mXULPersist = new XULPersist(this);
         mXULPersist->Init();
       }
@@ -8783,17 +8819,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 +12008,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/Document.h
+++ b/dom/base/Document.h
@@ -1898,16 +1898,21 @@ class Document : public nsINode,
     READYSTATE_UNINITIALIZED = 0,
     READYSTATE_LOADING = 1,
     READYSTATE_INTERACTIVE = 3,
     READYSTATE_COMPLETE = 4
   };
   void SetReadyStateInternal(ReadyState rs);
   ReadyState GetReadyStateEnum() { return mReadyState; }
 
+  void SetAncestorLoading(bool aAncestorIsLoading);
+  void NotifyLoading(const bool& aCurrentParentIsLoading,
+                     bool aNewParentIsLoading, const ReadyState& aCurrentState,
+                     ReadyState aNewState);
+
   // notify that a content node changed state.  This must happen under
   // a scriptblocker but NOT within a begin/end update.
   void ContentStateChanged(nsIContent* aContent,
                            mozilla::EventStates aStateMask);
 
   // Notify that a document state has changed.
   // This should only be called by callers whose state is also reflected in the
   // implementation of Document::GetDocumentState.
@@ -4111,16 +4116,19 @@ class Document : public nsINode,
   uint32_t mAsyncOnloadBlockCount;
 
   // Compatibility mode
   nsCompatibility mCompatMode;
 
   // Our readyState
   ReadyState mReadyState;
 
+  // Ancestor's loading state
+  bool mAncestorIsLoading;
+
 #ifdef MOZILLA_INTERNAL_API
   // Our visibility state
   mozilla::dom::VisibilityState mVisibilityState;
   static_assert(sizeof(mozilla::dom::VisibilityState) == sizeof(uint8_t),
                 "Error size of mVisibilityState and mDummy");
 #else
   uint8_t mDummy;
 #endif
--- 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/Timeout.cpp
+++ b/dom/base/Timeout.cpp
@@ -38,16 +38,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Timeout, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Timeout, Release)
 
 void Timeout::SetWhenOrTimeRemaining(const TimeStamp& aBaseTime,
                                      const TimeDuration& aDelay) {
   MOZ_DIAGNOSTIC_ASSERT(mWindow);
+  mSubmitTime = aBaseTime;
 
   // If we are frozen simply set mTimeRemaining to be the "time remaining" in
   // the timeout (i.e., the interval itself).  This will be used to create a
   // new mWhen time when the window is thawed.  The end effect is that time does
   // not appear to pass for frozen windows.
   if (mWindow->IsFrozen()) {
     mWhen = TimeStamp();
     mTimeRemaining = aDelay;
@@ -63,16 +64,18 @@ void Timeout::SetWhenOrTimeRemaining(con
 
 const TimeStamp& Timeout::When() const {
   MOZ_DIAGNOSTIC_ASSERT(!mWhen.IsNull());
   // Note, mWindow->IsFrozen() can be true here.  The Freeze() method calls
   // When() to calculate the delay to populate mTimeRemaining.
   return mWhen;
 }
 
+const TimeStamp& Timeout::SubmitTime() const { return mSubmitTime; }
+
 const TimeDuration& Timeout::TimeRemaining() const {
   MOZ_DIAGNOSTIC_ASSERT(mWhen.IsNull());
   // Note, mWindow->IsFrozen() can be false here.  The Thaw() method calls
   // TimeRemaining() to calculate the new When() value.
   return mTimeRemaining;
 }
 
 }  // namespace dom
--- a/dom/base/Timeout.h
+++ b/dom/base/Timeout.h
@@ -40,29 +40,36 @@ class Timeout final : public LinkedListE
   };
 
   void SetWhenOrTimeRemaining(const TimeStamp& aBaseTime,
                               const TimeDuration& aDelay);
 
   // Can only be called when not frozen.
   const TimeStamp& When() const;
 
+  const TimeStamp& SubmitTime() const;
+
   // Can only be called when frozen.
   const TimeDuration& TimeRemaining() const;
 
  private:
   // mWhen and mTimeRemaining can't be in a union, sadly, because they
   // have constructors.
   // Nominal time to run this timeout.  Use only when timeouts are not
   // frozen.
   TimeStamp mWhen;
 
   // Remaining time to wait.  Used only when timeouts are frozen.
   TimeDuration mTimeRemaining;
 
+  // Time that the timeout started, restarted, or was frozen.  Useful for
+  // logging time from (virtual) start of a timer until the time it fires
+  // (or is cancelled, etc)
+  TimeStamp mSubmitTime;
+
   ~Timeout() = default;
 
  public:
   // Public member variables in this section.  Please don't add to this list
   // or mix methods with these.  The interleaving public/private sections
   // is necessary as we migrate members to private while still trying to
   // keep decent binary packing.
 
--- a/dom/base/TimeoutExecutor.cpp
+++ b/dom/base/TimeoutExecutor.cpp
@@ -6,16 +6,18 @@
 
 #include "TimeoutExecutor.h"
 
 #include "mozilla/dom/TimeoutManager.h"
 #include "nsComponentManagerUtils.h"
 #include "nsIEventTarget.h"
 #include "nsString.h"
 
+extern mozilla::LazyLogModule gTimeoutLog;
+
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_ISUPPORTS(TimeoutExecutor, nsIRunnable, nsITimerCallback, nsINamed)
 
 TimeoutExecutor::~TimeoutExecutor() {
   // The TimeoutManager should keep the Executor alive until its destroyed,
   // and then call Shutdown() explicitly.
@@ -25,18 +27,25 @@ TimeoutExecutor::~TimeoutExecutor() {
 }
 
 nsresult TimeoutExecutor::ScheduleImmediate(const TimeStamp& aDeadline,
                                             const TimeStamp& aNow) {
   MOZ_DIAGNOSTIC_ASSERT(mDeadline.IsNull());
   MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::None);
   MOZ_DIAGNOSTIC_ASSERT(aDeadline <= (aNow + mAllowedEarlyFiringTime));
 
-  nsresult rv =
-      mOwner->EventTarget()->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+  nsresult rv;
+  if (mIsIdleQueue) {
+    RefPtr<TimeoutExecutor> runnable(this);
+    MOZ_LOG(gTimeoutLog, LogLevel::Debug, ("Starting IdleDispatch runnable"));
+    rv = NS_DispatchToCurrentThreadQueue(runnable.forget(), mMaxIdleDeferMS,
+                                         EventQueuePriority::DeferredTimers);
+  } else {
+    rv = mOwner->EventTarget()->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+  }
   NS_ENSURE_SUCCESS(rv, rv);
 
   mMode = Mode::Immediate;
   mDeadline = aDeadline;
 
   return NS_OK;
 }
 
@@ -45,24 +54,35 @@ nsresult TimeoutExecutor::ScheduleDelaye
                                           const TimeDuration& aMinDelay) {
   MOZ_DIAGNOSTIC_ASSERT(mDeadline.IsNull());
   MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::None);
   MOZ_DIAGNOSTIC_ASSERT(!aMinDelay.IsZero() ||
                         aDeadline > (aNow + mAllowedEarlyFiringTime));
 
   nsresult rv = NS_OK;
 
+  if (mIsIdleQueue) {
+    // Nothing goes into the idletimeouts list if it wasn't going to
+    // fire at that time, so we can always schedule idle-execution of
+    // these immediately
+    return ScheduleImmediate(aNow, aNow);
+  }
+
   if (!mTimer) {
     mTimer = NS_NewTimer(mOwner->EventTarget());
     NS_ENSURE_TRUE(mTimer, NS_ERROR_OUT_OF_MEMORY);
 
     uint32_t earlyMicros = 0;
     MOZ_ALWAYS_SUCCEEDS(
         mTimer->GetAllowedEarlyFiringMicroseconds(&earlyMicros));
     mAllowedEarlyFiringTime = TimeDuration::FromMicroseconds(earlyMicros);
+    // Re-evaluate if we should have scheduled this immediately
+    if (aDeadline <= (aNow + mAllowedEarlyFiringTime)) {
+      return ScheduleImmediate(aDeadline, aNow);
+    }
   } else {
     // Always call Cancel() in case we are re-using a timer.
     rv = mTimer->Cancel();
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Calculate the delay based on the deadline and current time.  If we have
   // a minimum delay set then clamp to that value.
@@ -149,21 +169,25 @@ void TimeoutExecutor::MaybeExecute() {
   TimeStamp now(TimeStamp::Now());
   TimeStamp limit = now + mAllowedEarlyFiringTime;
   if (deadline > limit) {
     deadline = limit;
   }
 
   Cancel();
 
-  mOwner->RunTimeout(now, deadline);
+  mOwner->RunTimeout(now, deadline, mIsIdleQueue);
 }
 
-TimeoutExecutor::TimeoutExecutor(TimeoutManager* aOwner)
-    : mOwner(aOwner), mMode(Mode::None) {
+TimeoutExecutor::TimeoutExecutor(TimeoutManager* aOwner, bool aIsIdleQueue,
+                                 uint32_t aMaxIdleDeferMS)
+    : mOwner(aOwner),
+      mIsIdleQueue(aIsIdleQueue),
+      mMaxIdleDeferMS(aMaxIdleDeferMS),
+      mMode(Mode::None) {
   MOZ_DIAGNOSTIC_ASSERT(mOwner);
 }
 
 void TimeoutExecutor::Shutdown() {
   mOwner = nullptr;
 
   if (mTimer) {
     mTimer->Cancel();
@@ -196,16 +220,18 @@ void TimeoutExecutor::Cancel() {
   mMode = Mode::None;
   mDeadline = TimeStamp();
 }
 
 NS_IMETHODIMP
 TimeoutExecutor::Run() {
   // If the executor is canceled and then rescheduled its possible to get
   // spurious executions here.  Ignore these unless our current mode matches.
+  MOZ_LOG(gTimeoutLog, LogLevel::Debug,
+          ("Running Immediate %stimers", mIsIdleQueue ? "Idle" : ""));
   if (mMode == Mode::Immediate) {
     MaybeExecute();
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TimeoutExecutor::Notify(nsITimer* aTimer) {
--- a/dom/base/TimeoutExecutor.h
+++ b/dom/base/TimeoutExecutor.h
@@ -17,18 +17,20 @@ namespace mozilla {
 namespace dom {
 
 class TimeoutManager;
 
 class TimeoutExecutor final : public nsIRunnable,
                               public nsITimerCallback,
                               public nsINamed {
   TimeoutManager* mOwner;
+  bool mIsIdleQueue;
   nsCOMPtr<nsITimer> mTimer;
   TimeStamp mDeadline;
+  uint32_t mMaxIdleDeferMS;
 
   // Limits how far we allow timers to fire into the future from their
   // deadline.  Starts off at zero, but is then adjusted when we start
   // using nsITimer.  The nsITimer implementation may sometimes fire
   // early and we should allow that to minimize additional wakeups.
   TimeDuration mAllowedEarlyFiringTime;
 
   // The TimeoutExecutor is repeatedly scheduled by the TimeoutManager
@@ -62,17 +64,18 @@ class TimeoutExecutor final : public nsI
   nsresult Schedule(const TimeStamp& aDeadline, const TimeDuration& aMinDelay);
 
   nsresult MaybeReschedule(const TimeStamp& aDeadline,
                            const TimeDuration& aMinDelay);
 
   void MaybeExecute();
 
  public:
-  explicit TimeoutExecutor(TimeoutManager* aOwner);
+  TimeoutExecutor(TimeoutManager* aOwner, bool aIsIdleQueue,
+                  uint32_t aMaxIdleDeferMS);
 
   void Shutdown();
 
   nsresult MaybeSchedule(const TimeStamp& aDeadline,
                          const TimeDuration& aMinDelay);
 
   void Cancel();
 
--- a/dom/base/TimeoutManager.cpp
+++ b/dom/base/TimeoutManager.cpp
@@ -17,21 +17,24 @@
 #include "nsITimeoutHandler.h"
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/PopupBlocker.h"
 #include "mozilla/dom/TabGroup.h"
 #include "TimeoutExecutor.h"
 #include "TimeoutBudgetManager.h"
 #include "mozilla/net/WebSocketEventService.h"
 #include "mozilla/MediaManager.h"
+#ifdef MOZ_GECKO_PROFILER
+#  include "ProfilerMarkerPayload.h"
+#endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
-static LazyLogModule gLog("Timeout");
+LazyLogModule gTimeoutLog("Timeout");
 
 static int32_t gRunningTimeoutDepth = 0;
 
 // The default shortest interval/timeout we permit
 #define DEFAULT_MIN_CLAMP_TIMEOUT_VALUE 4                   // 4ms
 #define DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE 1000           // 1000ms
 #define DEFAULT_MIN_TRACKING_TIMEOUT_VALUE 4                // 4ms
 #define DEFAULT_MIN_TRACKING_BACKGROUND_TIMEOUT_VALUE 1000  // 1000ms
@@ -121,16 +124,75 @@ bool TimeoutManager::IsActive() const {
   // Check if we're playing audio
   if (mWindow.AsInner()->IsPlayingAudio()) {
     return true;
   }
 
   return false;
 }
 
+void TimeoutManager::SetLoading(bool value) {
+  // When moving from loading to non-loading, we may need to
+  // reschedule any existing timeouts from the idle timeout queue
+  // to the normal queue.
+  MOZ_LOG(gTimeoutLog, LogLevel::Debug, ("%p: SetLoading(%d)", this, value));
+  if (mIsLoading && !value) {
+    MoveIdleToActive();
+  }
+  // We don't immediately move existing timeouts to the idle queue if we
+  // move to loading.  When they would have fired, we'll see we're loading
+  // and move them then.
+  mIsLoading = value;
+}
+
+void TimeoutManager::MoveIdleToActive() {
+  uint32_t num = 0;
+  TimeStamp when;
+  TimeStamp now;
+  // Ensure we maintain the ordering of timeouts, so timeouts
+  // never fire before a timeout set for an earlier time, or
+  // before a timeout for the same time already submitted.
+  // See https://html.spec.whatwg.org/#dom-settimeout #16 and #17
+  while (RefPtr<Timeout> timeout = mIdleTimeouts.GetLast()) {
+    if (num == 0) {
+      when = timeout->When();
+    }
+    timeout->remove();
+    mTimeouts.InsertFront(timeout);
+#if MOZ_GECKO_PROFILER
+    if (profiler_is_active()) {
+      if (num == 0) {
+        now = TimeStamp::Now();
+      }
+      TimeDuration elapsed = now - timeout->SubmitTime();
+      TimeDuration target = timeout->When() - timeout->SubmitTime();
+      TimeDuration delta = now - timeout->When();
+      nsPrintfCString marker(
+          "Releasing deferred setTimeout() for %dms (original target time was "
+          "%dms (%dms delta))",
+          int(elapsed.ToMilliseconds()), int(target.ToMilliseconds()),
+          int(delta.ToMilliseconds()));
+      // don't have end before start...
+      profiler_add_marker(
+          "setTimeout release", js::ProfilingStackFrame::Category::DOM,
+          MakeUnique<TextMarkerPayload>(
+              marker, delta.ToMilliseconds() >= 0 ? timeout->When() : now,
+              now));
+    }
+#endif
+    num++;
+  }
+  if (num > 0) {
+    MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(when));
+    mIdleExecutor->Cancel();
+  }
+  MOZ_LOG(gTimeoutLog, LogLevel::Debug,
+          ("%p: Moved %d timeouts from Idle to active", this, num));
+}
+
 uint32_t TimeoutManager::CreateFiringId() {
   uint32_t id = mNextFiringId;
   mNextFiringId += 1;
   if (mNextFiringId == InvalidFiringId) {
     mNextFiringId += 1;
   }
 
   mFiringIdStack.AppendElement(id);
@@ -377,43 +439,49 @@ uint32_t gMaxConsecutiveCallbacksMillise
 
 // Only propagate the open window click permission if the setTimeout() is equal
 // to or less than this value.
 #define DEFAULT_DISABLE_OPEN_CLICK_DELAY 0
 int32_t gDisableOpenClickDelay;
 
 }  // anonymous namespace
 
-TimeoutManager::TimeoutManager(nsGlobalWindowInner& aWindow)
+TimeoutManager::TimeoutManager(nsGlobalWindowInner& aWindow,
+                               uint32_t aMaxIdleDeferMS)
     : mWindow(aWindow),
-      mExecutor(new TimeoutExecutor(this)),
+      mExecutor(new TimeoutExecutor(this, false, 0)),
+      mIdleExecutor(new TimeoutExecutor(this, true, aMaxIdleDeferMS)),
       mTimeouts(*this),
       mTimeoutIdCounter(1),
       mNextFiringId(InvalidFiringId + 1),
       mRunningTimeout(nullptr),
+      mIdleTimeouts(*this),
       mIdleCallbackTimeoutCounter(1),
       mLastBudgetUpdate(TimeStamp::Now()),
       mExecutionBudget(GetMaxBudget(mWindow.IsBackgroundInternal())),
       mThrottleTimeouts(false),
       mThrottleTrackingTimeouts(false),
-      mBudgetThrottleTimeouts(false) {
-  MOZ_LOG(gLog, LogLevel::Debug,
+      mBudgetThrottleTimeouts(false),
+      mIsLoading(false) {
+  MOZ_LOG(gTimeoutLog, LogLevel::Debug,
           ("TimeoutManager %p created, tracking bucketing %s\n", this,
            StaticPrefs::privacy_trackingprotection_annotate_channels()
                ? "enabled"
                : "disabled"));
 }
 
 TimeoutManager::~TimeoutManager() {
   MOZ_DIAGNOSTIC_ASSERT(mWindow.IsDying());
   MOZ_DIAGNOSTIC_ASSERT(!mThrottleTimeoutsTimer);
 
   mExecutor->Shutdown();
+  mIdleExecutor->Shutdown();
 
-  MOZ_LOG(gLog, LogLevel::Debug, ("TimeoutManager %p destroyed\n", this));
+  MOZ_LOG(gTimeoutLog, LogLevel::Debug,
+          ("TimeoutManager %p destroyed\n", this));
 }
 
 /* static */
 void TimeoutManager::Initialize() {
   Preferences::AddIntVarCache(&gMinClampTimeoutValue, "dom.min_timeout_value",
                               DEFAULT_MIN_CLAMP_TIMEOUT_VALUE);
   Preferences::AddIntVarCache(&gMinBackgroundTimeoutValue,
                               "dom.min_background_timeout_value",
@@ -533,54 +601,68 @@ nsresult TimeoutManager::SetTimeout(nsIT
   Timeouts::SortBy sort(mWindow.IsFrozen() ? Timeouts::SortBy::TimeRemaining
                                            : Timeouts::SortBy::TimeWhen);
   mTimeouts.Insert(timeout, sort);
 
   timeout->mTimeoutId = GetTimeoutId(aReason);
   *aReturn = timeout->mTimeoutId;
 
   MOZ_LOG(
-      gLog, LogLevel::Debug,
+      gTimeoutLog, LogLevel::Debug,
       ("Set%s(TimeoutManager=%p, timeout=%p, delay=%i, "
        "minimum=%f, throttling=%s, state=%s(%s), realInterval=%f) "
        "returned timeout ID %u, budget=%d\n",
        aIsInterval ? "Interval" : "Timeout", this, timeout.get(), interval,
        (CalculateDelay(timeout) - timeout->mInterval).ToMilliseconds(),
        mThrottleTimeouts ? "yes" : (mThrottleTimeoutsTimer ? "pending" : "no"),
        IsActive() ? "active" : "inactive",
        mWindow.IsBackgroundInternal() ? "background" : "foreground",
        realInterval.ToMilliseconds(), timeout->mTimeoutId,
        int(mExecutionBudget.ToMilliseconds())));
 
   return NS_OK;
 }
 
+// Make sure we clear it no matter which list it's in
 void TimeoutManager::ClearTimeout(int32_t aTimerId, Timeout::Reason aReason) {
+  if (ClearTimeoutInternal(aTimerId, aReason, false) ||
+      mIdleTimeouts.IsEmpty()) {
+    return;  // no need to check the other list if we cleared the timeout
+  }
+  ClearTimeoutInternal(aTimerId, aReason, true);
+}
+
+bool TimeoutManager::ClearTimeoutInternal(int32_t aTimerId,
+                                          Timeout::Reason aReason,
+                                          bool aIsIdle) {
   uint32_t timerId = (uint32_t)aTimerId;
-
+  Timeouts& timeouts = aIsIdle ? mIdleTimeouts : mTimeouts;
+  RefPtr<TimeoutExecutor>& executor = aIsIdle ? mIdleExecutor : mExecutor;
   bool firstTimeout = true;
   bool deferredDeletion = false;
+  bool cleared = false;
 
-  mTimeouts.ForEachAbortable([&](Timeout* aTimeout) {
-    MOZ_LOG(gLog, LogLevel::Debug,
+  timeouts.ForEachAbortable([&](Timeout* aTimeout) {
+    MOZ_LOG(gTimeoutLog, LogLevel::Debug,
             ("Clear%s(TimeoutManager=%p, timeout=%p, aTimerId=%u, ID=%u)\n",
              aTimeout->mIsInterval ? "Interval" : "Timeout", this, aTimeout,
              timerId, aTimeout->mTimeoutId));
 
     if (aTimeout->mTimeoutId == timerId && aTimeout->mReason == aReason) {
       if (aTimeout->mRunning) {
         /* We're running from inside the aTimeout. Mark this
            aTimeout for deferred deletion by the code in
            RunTimeout() */
         aTimeout->mIsInterval = false;
         deferredDeletion = true;
       } else {
         /* Delete the aTimeout from the pending aTimeout list */
         aTimeout->remove();
       }
+      cleared = true;
       return true;  // abort!
     }
 
     firstTimeout = false;
 
     return false;
   });
 
@@ -588,38 +670,47 @@ void TimeoutManager::ClearTimeout(int32_
   //  * If the we weren't cancelling the first timeout, then the executor's
   //    state doesn't need to change.  It will only reflect the next soonest
   //    Timeout.
   //  * If we did cancel the first Timeout, but its currently running, then
   //    RunTimeout() will handle rescheduling the executor.
   //  * If the window has become suspended then we should not start executing
   //    Timeouts.
   if (!firstTimeout || deferredDeletion || mWindow.IsSuspended()) {
-    return;
+    return cleared;
   }
 
   // Stop the executor and restart it at the next soonest deadline.
-  mExecutor->Cancel();
+  executor->Cancel();
 
-  Timeout* nextTimeout = mTimeouts.GetFirst();
+  Timeout* nextTimeout = timeouts.GetFirst();
   if (nextTimeout) {
-    MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(nextTimeout->When()));
+    if (aIsIdle) {
+      MOZ_ALWAYS_SUCCEEDS(
+          executor->MaybeSchedule(nextTimeout->When(), TimeDuration(0)));
+    } else {
+      MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(nextTimeout->When()));
+    }
   }
+  return cleared;
 }
 
 void TimeoutManager::RunTimeout(const TimeStamp& aNow,
-                                const TimeStamp& aTargetDeadline) {
+                                const TimeStamp& aTargetDeadline,
+                                bool aProcessIdle) {
   MOZ_DIAGNOSTIC_ASSERT(!aNow.IsNull());
   MOZ_DIAGNOSTIC_ASSERT(!aTargetDeadline.IsNull());
 
   MOZ_ASSERT_IF(mWindow.IsFrozen(), mWindow.IsSuspended());
   if (mWindow.IsSuspended()) {
     return;
   }
 
+  Timeouts& timeouts(aProcessIdle ? mIdleTimeouts : mTimeouts);
+
   // Limit the overall time spent in RunTimeout() to reduce jank.
   uint32_t totalTimeLimitMS =
       std::max(1u, gMaxConsecutiveCallbacksMilliseconds);
   const TimeDuration totalTimeLimit =
       TimeDuration::Min(TimeDuration::FromMilliseconds(totalTimeLimitMS),
                         TimeDuration::Max(TimeDuration(), mExecutionBudget));
 
   // Allow up to 25% of our total time budget to be used figuring out which
@@ -666,17 +757,17 @@ void TimeoutManager::RunTimeout(const Ti
   uint32_t numTimersToRun = 0;
 
   // The timeout list is kept in deadline order. Discover the latest timeout
   // whose deadline has expired. On some platforms, native timeout events fire
   // "early", but we handled that above by setting deadline to aTargetDeadline
   // if the timer fired early.  So we can stop walking if we get to timeouts
   // whose When() is greater than deadline, since once that happens we know
   // nothing past that point is expired.
-  for (Timeout* timeout = mTimeouts.GetFirst(); timeout != nullptr;
+  for (Timeout* timeout = timeouts.GetFirst(); timeout != nullptr;
        timeout = timeout->getNext()) {
     if (totalTimeLimit.IsZero() || timeout->When() > deadline) {
       nextDeadline = timeout->When();
       break;
     }
 
     if (IsInvalidFiringId(timeout->mFiringId)) {
       // Mark any timeouts that are on the list to be fired with the
@@ -691,30 +782,44 @@ void TimeoutManager::RunTimeout(const Ti
         TimeDuration elapsed(now - start);
         if (elapsed >= initialTimeLimit) {
           nextDeadline = timeout->When();
           break;
         }
       }
     }
   }
+  if (aProcessIdle) {
+    MOZ_LOG(gTimeoutLog, LogLevel::Debug,
+            ("Running %u deferred timeouts on idle (TimeoutManager=%p), "
+             "nextDeadline = %gms from now",
+             numTimersToRun, this, (nextDeadline - now).ToMilliseconds()));
+  }
 
   now = TimeStamp::Now();
 
   // Wherever we stopped in the timer list, schedule the executor to
   // run for the next unexpired deadline.  Note, this *must* be done
   // before we start executing any content script handlers.  If one
   // of them spins the event loop the executor must already be scheduled
   // in order for timeouts to fire properly.
   if (!nextDeadline.IsNull()) {
     // Note, we verified the window is not suspended at the top of
     // method and the window should not have been suspended while
     // executing the loop above since it doesn't call out to js.
     MOZ_DIAGNOSTIC_ASSERT(!mWindow.IsSuspended());
-    MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(nextDeadline, now));
+    if (aProcessIdle) {
+      // We don't want to update timing budget for idle queue firings, and
+      // all timeouts in the IdleTimeouts list have hit their deadlines,
+      // and so should run as soon as possible.
+      MOZ_ALWAYS_SUCCEEDS(
+          mIdleExecutor->MaybeSchedule(nextDeadline, TimeDuration()));
+    } else {
+      MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(nextDeadline, now));
+    }
   }
 
   // Maybe the timeout that the event was fired for has been deleted
   // and there are no others timeouts with deadlines that make them
   // eligible for execution yet. Go away.
   if (!numTimersToRun) {
     return;
   }
@@ -731,26 +836,31 @@ void TimeoutManager::RunTimeout(const Ti
     // iterating are freed after the loop.
 
     // The next timeout to run. This is used to advance the loop, but
     // we cannot set it until we've run the current timeout, since
     // running the current timeout might remove the immediate next
     // timeout.
     RefPtr<Timeout> next;
 
-    for (RefPtr<Timeout> timeout = mTimeouts.GetFirst(); timeout != nullptr;
+    for (RefPtr<Timeout> timeout = timeouts.GetFirst(); timeout != nullptr;
          timeout = next) {
       next = timeout->getNext();
       // We should only execute callbacks for the set of expired Timeout
       // objects we computed above.
       if (timeout->mFiringId != firingId) {
         // If the FiringId does not match, but is still valid, then this is
-        // a TImeout for another RunTimeout() on the call stack.  Just
+        // a Timeout for another RunTimeout() on the call stack.  Just
         // skip it.
         if (IsValidFiringId(timeout->mFiringId)) {
+          MOZ_LOG(gTimeoutLog, LogLevel::Debug,
+                  ("Skipping Run%s(TimeoutManager=%p, timeout=%p) since "
+                   "firingId %d is valid (processing firingId %d)",
+                   timeout->mIsInterval ? "Interval" : "Timeout", this,
+                   timeout.get(), timeout->mFiringId, firingId));
           continue;
         }
 
         // If, however, the FiringId is invalid then we have reached Timeout
         // objects beyond the list we calculated above.  This can happen
         // if the Timeout just beyond our last expired Timeout is cancelled
         // by one of the callbacks we've just executed.  In this case we
         // should just stop iterating.  We're done.
@@ -762,90 +872,144 @@ void TimeoutManager::RunTimeout(const Ti
       MOZ_ASSERT_IF(mWindow.IsFrozen(), mWindow.IsSuspended());
       if (mWindow.IsSuspended()) {
         break;
       }
 
       // The timeout is on the list to run at this depth, go ahead and
       // process it.
 
-      // Get the script context (a strong ref to prevent it going away)
-      // for this timeout and ensure the script language is enabled.
-      nsCOMPtr<nsIScriptContext> scx = mWindow.GetContextInternal();
-
-      if (!scx) {
-        // No context means this window was closed or never properly
-        // initialized for this language.  This timer will never fire
-        // so just remove it.
+      if (mIsLoading && !aProcessIdle) {
+        // Any timeouts that would fire during a load will be deferred
+        // until the load event occurs, but if there's an idle time,
+        // they'll be run before the load event.
         timeout->remove();
-        continue;
-      }
+        // MOZ_RELEASE_ASSERT(timeout->When() <= (TimeStamp::Now()));
+        mIdleTimeouts.InsertBack(timeout);
+        if (MOZ_LOG_TEST(gTimeoutLog, LogLevel::Debug)) {
+          uint32_t num = 0;
+          for (Timeout* t = mIdleTimeouts.GetFirst(); t != nullptr;
+               t = t->getNext()) {
+            num++;
+          }
+          MOZ_LOG(
+              gTimeoutLog, LogLevel::Debug,
+              ("Deferring Run%s(TimeoutManager=%p, timeout=%p (%gms in the "
+               "past)) (%u deferred)",
+               timeout->mIsInterval ? "Interval" : "Timeout", this,
+               timeout.get(), (now - timeout->When()).ToMilliseconds(), num));
+        }
+        MOZ_ALWAYS_SUCCEEDS(mIdleExecutor->MaybeSchedule(now, TimeDuration()));
+      } else {
+        // Get the script context (a strong ref to prevent it going away)
+        // for this timeout and ensure the script language is enabled.
+        nsCOMPtr<nsIScriptContext> scx = mWindow.GetContextInternal();
 
-      // This timeout is good to run
-      bool timeout_was_cleared = mWindow.RunTimeoutHandler(timeout, scx);
-
-      MOZ_LOG(gLog, LogLevel::Debug,
-              ("Run%s(TimeoutManager=%p, timeout=%p) returned %d\n",
-               timeout->mIsInterval ? "Interval" : "Timeout", this,
-               timeout.get(), !!timeout_was_cleared));
-
-      if (timeout_was_cleared) {
-        // Make sure we're not holding any Timeout objects alive.
-        next = nullptr;
-
-        // Since ClearAllTimeouts() was called the lists should be empty.
-        MOZ_DIAGNOSTIC_ASSERT(!HasTimeouts());
+        if (!scx) {
+          // No context means this window was closed or never properly
+          // initialized for this language.  This timer will never fire
+          // so just remove it.
+          timeout->remove();
+          continue;
+        }
 
-        return;
-      }
+        // This timeout is good to run
+#if MOZ_GECKO_PROFILER
+        if (profiler_is_active()) {
+          if (aProcessIdle) {
+            TimeDuration elapsed = now - timeout->SubmitTime();
+            TimeDuration target = timeout->When() - timeout->SubmitTime();
+            TimeDuration delta = now - timeout->When();
+            nsPrintfCString marker(
+                "%ssetTimeout() for %dms (original target time was %dms (%dms "
+                "delta))",
+                aProcessIdle ? "Deferred " : "", int(elapsed.ToMilliseconds()),
+                int(target.ToMilliseconds()), int(delta.ToMilliseconds()));
+            // don't have end before start...
+            profiler_add_marker(
+                "setTimeout", js::ProfilingStackFrame::Category::DOM,
+                MakeUnique<TextMarkerPayload>(
+                    marker, delta.ToMilliseconds() >= 0 ? timeout->When() : now,
+                    now));
+          }
+        }
+#endif
+        bool timeout_was_cleared = mWindow.RunTimeoutHandler(timeout, scx);
 
-      // If we need to reschedule a setInterval() the delay should be
-      // calculated based on when its callback started to execute.  So
-      // save off the last time before updating our "now" timestamp to
-      // account for its callback execution time.
-      TimeStamp lastCallbackTime = now;
-      now = TimeStamp::Now();
+        MOZ_LOG(gTimeoutLog, LogLevel::Debug,
+                ("Run%s(TimeoutManager=%p, timeout=%p) returned %d\n",
+                 timeout->mIsInterval ? "Interval" : "Timeout", this,
+                 timeout.get(), !!timeout_was_cleared));
 
-      // If we have a regular interval timer, we re-schedule the
-      // timeout, accounting for clock drift.
-      bool needsReinsertion = RescheduleTimeout(timeout, lastCallbackTime, now);
+        if (timeout_was_cleared) {
+          // Make sure we're not holding any Timeout objects alive.
+          next = nullptr;
 
-      // Running a timeout can cause another timeout to be deleted, so
-      // we need to reset the pointer to the following timeout.
-      next = timeout->getNext();
+          // Since ClearAllTimeouts() was called the lists should be empty.
+          MOZ_DIAGNOSTIC_ASSERT(!HasTimeouts());
+
+          return;
+        }
 
-      timeout->remove();
+        // If we need to reschedule a setInterval() the delay should be
+        // calculated based on when its callback started to execute.  So
+        // save off the last time before updating our "now" timestamp to
+        // account for its callback execution time.
+        TimeStamp lastCallbackTime = now;
+        now = TimeStamp::Now();
 
-      if (needsReinsertion) {
-        // Insert interval timeout onto the corresponding list sorted in
-        // deadline order. AddRefs timeout.
-        mTimeouts.Insert(timeout, mWindow.IsFrozen()
-                                      ? Timeouts::SortBy::TimeRemaining
-                                      : Timeouts::SortBy::TimeWhen);
+        // If we have a regular interval timer, we re-schedule the
+        // timeout, accounting for clock drift.
+        bool needsReinsertion =
+            RescheduleTimeout(timeout, lastCallbackTime, now);
+
+        // Running a timeout can cause another timeout to be deleted, so
+        // we need to reset the pointer to the following timeout.
+        next = timeout->getNext();
+
+        timeout->remove();
+
+        if (needsReinsertion) {
+          // Insert interval timeout onto the corresponding list sorted in
+          // deadline order. AddRefs timeout.
+          // Always re-insert into the normal time queue!
+          mTimeouts.Insert(timeout, mWindow.IsFrozen()
+                                        ? Timeouts::SortBy::TimeRemaining
+                                        : Timeouts::SortBy::TimeWhen);
+        }
       }
-
       // Check to see if we have run out of time to execute timeout handlers.
       // If we've exceeded our time budget then terminate the loop immediately.
       TimeDuration elapsed = now - start;
       if (elapsed >= totalTimeLimit) {
         // We ran out of time.  Make sure to schedule the executor to
         // run immediately for the next timer, if it exists.  Its possible,
         // however, that the last timeout handler suspended the window.  If
         // that happened then we must skip this step.
         if (!mWindow.IsSuspended()) {
           if (next) {
-            // If we ran out of execution budget we need to force a
-            // reschedule. By cancelling the executor we will not run
-            // immediately, but instead reschedule to the minimum
-            // scheduling delay.
-            if (mExecutionBudget < TimeDuration()) {
-              mExecutor->Cancel();
+            if (aProcessIdle) {
+              // We don't want to update timing budget for idle queue firings,
+              // and all timeouts in the IdleTimeouts list have hit their
+              // deadlines, and so should run as soon as possible.
+
+              // Shouldn't need cancelling since it never waits
+              MOZ_ALWAYS_SUCCEEDS(
+                  mIdleExecutor->MaybeSchedule(next->When(), TimeDuration()));
+            } else {
+              // If we ran out of execution budget we need to force a
+              // reschedule. By cancelling the executor we will not run
+              // immediately, but instead reschedule to the minimum
+              // scheduling delay.
+              if (mExecutionBudget < TimeDuration()) {
+                mExecutor->Cancel();
+              }
+
+              MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(next->When(), now));
             }
-
-            MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(next->When(), now));
           }
         }
         break;
       }
     }
   }
 }
 
@@ -888,43 +1052,45 @@ bool TimeoutManager::RescheduleTimeout(T
   NS_ENSURE_SUCCESS(rv, false);
 
   return true;
 }
 
 void TimeoutManager::ClearAllTimeouts() {
   bool seenRunningTimeout = false;
 
-  MOZ_LOG(gLog, LogLevel::Debug,
+  MOZ_LOG(gTimeoutLog, LogLevel::Debug,
           ("ClearAllTimeouts(TimeoutManager=%p)\n", this));
 
   if (mThrottleTimeoutsTimer) {
     mThrottleTimeoutsTimer->Cancel();
     mThrottleTimeoutsTimer = nullptr;
   }
 
   mExecutor->Cancel();
+  mIdleExecutor->Cancel();
 
   ForEachUnorderedTimeout([&](Timeout* aTimeout) {
     /* If RunTimeout() is higher up on the stack for this
        window, e.g. as a result of document.write from a timeout,
        then we need to reset the list insertion point for
        newly-created timeouts in case the user adds a timeout,
        before we pop the stack back to RunTimeout. */
     if (mRunningTimeout == aTimeout) {
       seenRunningTimeout = true;
     }
 
     // Set timeout->mCleared to true to indicate that the timeout was
     // cleared and taken out of the list of timeouts
     aTimeout->mCleared = true;
   });
 
-  // Clear out our list
+  // Clear out our lists
   mTimeouts.Clear();
+  mIdleTimeouts.Clear();
 }
 
 void TimeoutManager::Timeouts::Insert(Timeout* aTimeout, SortBy aSortBy) {
   // Start at mLastTimeout and go backwards.  Stop if we see a Timeout with a
   // valid FiringId since those timers are currently being processed by
   // RunTimeout.  This optimizes for the common case of insertion at the end.
   Timeout* prevSibling;
   for (prevSibling = GetLast();
@@ -971,44 +1137,50 @@ void TimeoutManager::UnmarkGrayTimers() 
   ForEachUnorderedTimeout([](Timeout* aTimeout) {
     if (aTimeout->mScriptHandler) {
       aTimeout->mScriptHandler->MarkForCC();
     }
   });
 }
 
 void TimeoutManager::Suspend() {
-  MOZ_LOG(gLog, LogLevel::Debug, ("Suspend(TimeoutManager=%p)\n", this));
+  MOZ_LOG(gTimeoutLog, LogLevel::Debug, ("Suspend(TimeoutManager=%p)\n", this));
 
   if (mThrottleTimeoutsTimer) {
     mThrottleTimeoutsTimer->Cancel();
     mThrottleTimeoutsTimer = nullptr;
   }
 
   mExecutor->Cancel();
+  mIdleExecutor->Cancel();
 }
 
 void TimeoutManager::Resume() {
-  MOZ_LOG(gLog, LogLevel::Debug, ("Resume(TimeoutManager=%p)\n", this));
+  MOZ_LOG(gTimeoutLog, LogLevel::Debug, ("Resume(TimeoutManager=%p)\n", this));
 
   // When Suspend() has been called after IsDocumentLoaded(), but the
   // throttle tracking timer never managed to fire, start the timer
   // again.
   if (mWindow.AsInner()->IsDocumentLoaded() && !mThrottleTimeouts) {
     MaybeStartThrottleTimeout();
   }
 
   Timeout* nextTimeout = mTimeouts.GetFirst();
   if (nextTimeout) {
     MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(nextTimeout->When()));
   }
+  nextTimeout = mIdleTimeouts.GetFirst();
+  if (nextTimeout) {
+    MOZ_ALWAYS_SUCCEEDS(
+        mIdleExecutor->MaybeSchedule(nextTimeout->When(), TimeDuration()));
+  }
 }
 
 void TimeoutManager::Freeze() {
-  MOZ_LOG(gLog, LogLevel::Debug, ("Freeze(TimeoutManager=%p)\n", this));
+  MOZ_LOG(gTimeoutLog, LogLevel::Debug, ("Freeze(TimeoutManager=%p)\n", this));
 
   TimeStamp now = TimeStamp::Now();
   ForEachUnorderedTimeout([&](Timeout* aTimeout) {
     // Save the current remaining time for this timeout.  We will
     // re-apply it when the window is Thaw()'d.  This effectively
     // shifts timers to the right as if time does not pass while
     // the window is frozen.
     TimeDuration delta(0);
@@ -1016,17 +1188,17 @@ void TimeoutManager::Freeze() {
       delta = aTimeout->When() - now;
     }
     aTimeout->SetWhenOrTimeRemaining(now, delta);
     MOZ_DIAGNOSTIC_ASSERT(aTimeout->TimeRemaining() == delta);
   });
 }
 
 void TimeoutManager::Thaw() {
-  MOZ_LOG(gLog, LogLevel::Debug, ("Thaw(TimeoutManager=%p)\n", this));
+  MOZ_LOG(gTimeoutLog, LogLevel::Debug, ("Thaw(TimeoutManager=%p)\n", this));
 
   TimeStamp now = TimeStamp::Now();
 
   ForEachUnorderedTimeout([&](Timeout* aTimeout) {
     // Set When() back to the time when the timer is supposed to fire.
     aTimeout->SetWhenOrTimeRemaining(now, aTimeout->TimeRemaining());
     MOZ_DIAGNOSTIC_ASSERT(!aTimeout->When().IsNull());
   });
@@ -1040,16 +1212,27 @@ void TimeoutManager::UpdateBackgroundSta
   // changed.  Only do this if the window is not suspended and we
   // actually have a timeout.
   if (!mWindow.IsSuspended()) {
     Timeout* nextTimeout = mTimeouts.GetFirst();
     if (nextTimeout) {
       mExecutor->Cancel();
       MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(nextTimeout->When()));
     }
+    // the Idle queue should all be past their firing time, so there we just
+    // need to restart the queue
+
+    // XXX May not be needed if we don't stop the idle queue, as
+    // MinSchedulingDelay isn't relevant here
+    nextTimeout = mIdleTimeouts.GetFirst();
+    if (nextTimeout) {
+      mIdleExecutor->Cancel();
+      MOZ_ALWAYS_SUCCEEDS(
+          mIdleExecutor->MaybeSchedule(nextTimeout->When(), TimeDuration()));
+    }
   }
 }
 
 namespace {
 
 class ThrottleTimeoutsCallback final : public nsITimerCallback,
                                        public nsINamed {
  public:
@@ -1119,17 +1302,17 @@ bool TimeoutManager::BudgetThrottlingEna
 
   return true;
 }
 
 void TimeoutManager::StartThrottlingTimeouts() {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_DIAGNOSTIC_ASSERT(mThrottleTimeoutsTimer);
 
-  MOZ_LOG(gLog, LogLevel::Debug,
+  MOZ_LOG(gTimeoutLog, LogLevel::Debug,
           ("TimeoutManager %p started to throttle tracking timeouts\n", this));
 
   MOZ_DIAGNOSTIC_ASSERT(!mThrottleTimeouts);
   mThrottleTimeouts = true;
   mThrottleTrackingTimeouts = true;
   mBudgetThrottleTimeouts = gEnableBudgetTimeoutThrottling;
   mThrottleTimeoutsTimer = nullptr;
 }
@@ -1146,17 +1329,17 @@ void TimeoutManager::OnDocumentLoaded() 
 void TimeoutManager::MaybeStartThrottleTimeout() {
   if (gTimeoutThrottlingDelay <= 0 || mWindow.IsDying() ||
       mWindow.IsSuspended()) {
     return;
   }
 
   MOZ_DIAGNOSTIC_ASSERT(!mThrottleTimeouts);
 
-  MOZ_LOG(gLog, LogLevel::Debug,
+  MOZ_LOG(gTimeoutLog, LogLevel::Debug,
           ("TimeoutManager %p delaying tracking timeout throttling by %dms\n",
            this, gTimeoutThrottlingDelay));
 
   nsCOMPtr<nsITimerCallback> callback = new ThrottleTimeoutsCallback(&mWindow);
 
   NS_NewTimerWithCallback(getter_AddRefs(mThrottleTimeoutsTimer), callback,
                           gTimeoutThrottlingDelay, nsITimer::TYPE_ONE_SHOT,
                           EventTarget());
--- a/dom/base/TimeoutManager.h
+++ b/dom/base/TimeoutManager.h
@@ -20,36 +20,45 @@ namespace mozilla {
 class PerformanceCounter;
 
 namespace dom {
 
 class TimeoutExecutor;
 
 // This class manages the timeouts in a Window's setTimeout/setInterval pool.
 class TimeoutManager final {
+ private:
+  struct Timeouts;
+
  public:
-  explicit TimeoutManager(nsGlobalWindowInner& aWindow);
+  TimeoutManager(nsGlobalWindowInner& aWindow, uint32_t aMaxIdleDeferMS);
   ~TimeoutManager();
   TimeoutManager(const TimeoutManager& rhs) = delete;
   void operator=(const TimeoutManager& rhs) = delete;
 
   bool IsRunningTimeout() const;
 
   static uint32_t GetNestingLevel() { return sNestingLevel; }
   static void SetNestingLevel(uint32_t aLevel) { sNestingLevel = aLevel; }
 
-  bool HasTimeouts() const { return !mTimeouts.IsEmpty(); }
+  bool HasTimeouts() const {
+    return !mTimeouts.IsEmpty() || !mIdleTimeouts.IsEmpty();
+  }
 
   nsresult SetTimeout(nsITimeoutHandler* aHandler, int32_t interval,
                       bool aIsInterval, mozilla::dom::Timeout::Reason aReason,
                       int32_t* aReturn);
   void ClearTimeout(int32_t aTimerId, mozilla::dom::Timeout::Reason aReason);
+  bool ClearTimeoutInternal(int32_t aTimerId,
+                            mozilla::dom::Timeout::Reason aReason,
+                            bool aIsIdle);
 
   // The timeout implementation functions.
-  void RunTimeout(const TimeStamp& aNow, const TimeStamp& aTargetDeadline);
+  void RunTimeout(const TimeStamp& aNow, const TimeStamp& aTargetDeadline,
+                  bool aProcessIdle);
 
   void ClearAllTimeouts();
   uint32_t GetTimeoutId(mozilla::dom::Timeout::Reason aReason);
 
   TimeDuration CalculateDelay(Timeout* aTimeout) const;
 
   // aTimeout is the timeout that we're about to start running.  This function
   // returns the current timeout.
@@ -76,36 +85,41 @@ class TimeoutManager final {
   // The document finished loading
   void OnDocumentLoaded();
   void StartThrottlingTimeouts();
 
   // Run some code for each Timeout in our list.  Note that this function
   // doesn't guarantee that Timeouts are iterated in any particular order.
   template <class Callable>
   void ForEachUnorderedTimeout(Callable c) {
+    mIdleTimeouts.ForEach(c);
     mTimeouts.ForEach(c);
   }
 
   void BeginSyncOperation();
   void EndSyncOperation();
 
   nsIEventTarget* EventTarget();
 
   bool BudgetThrottlingEnabled(bool aIsBackground) const;
 
   static const uint32_t InvalidFiringId;
 
+  void SetLoading(bool value);
+
  private:
   void MaybeStartThrottleTimeout();
 
   // Return true if |aTimeout| needs to be reinserted into the timeout list.
   bool RescheduleTimeout(mozilla::dom::Timeout* aTimeout,
                          const TimeStamp& aLastCallbackTime,
                          const TimeStamp& aCurrentNow);
 
+  void MoveIdleToActive();
+
   bool IsBackground() const;
 
   bool IsActive() const;
 
   uint32_t CreateFiringId();
 
   void DestroyFiringId(uint32_t aFiringId);
 
@@ -136,16 +150,17 @@ class TimeoutManager final {
     void Insert(mozilla::dom::Timeout* aTimeout, SortBy aSortBy);
 
     const Timeout* GetFirst() const { return mTimeoutList.getFirst(); }
     Timeout* GetFirst() { return mTimeoutList.getFirst(); }
     const Timeout* GetLast() const { return mTimeoutList.getLast(); }
     Timeout* GetLast() { return mTimeoutList.getLast(); }
     bool IsEmpty() const { return mTimeoutList.isEmpty(); }
     void InsertFront(Timeout* aTimeout) { mTimeoutList.insertFront(aTimeout); }
+    void InsertBack(Timeout* aTimeout) { mTimeoutList.insertBack(aTimeout); }
     void Clear() { mTimeoutList.clear(); }
 
     template <class Callable>
     void ForEach(Callable c) {
       for (Timeout* timeout = GetFirst(); timeout;
            timeout = timeout->getNext()) {
         c(timeout);
       }
@@ -177,33 +192,41 @@ class TimeoutManager final {
 
   // Each nsGlobalWindowInner object has a TimeoutManager member.  This
   // reference points to that holder object.
   nsGlobalWindowInner& mWindow;
   // The executor is specific to the nsGlobalWindow/TimeoutManager, but it
   // can live past the destruction of the window if its scheduled.  Therefore
   // it must be a separate ref-counted object.
   RefPtr<TimeoutExecutor> mExecutor;
+  // For timeouts run off the idle queue
+  RefPtr<TimeoutExecutor> mIdleExecutor;
   // The list of timeouts coming from non-tracking scripts.
   Timeouts mTimeouts;
   uint32_t mTimeoutIdCounter;
   uint32_t mNextFiringId;
   AutoTArray<uint32_t, 2> mFiringIdStack;
   mozilla::dom::Timeout* mRunningTimeout;
 
+  // Timeouts that would have fired but are being deferred until MainThread
+  // is idle (because we're loading)
+  Timeouts mIdleTimeouts;
+
   // The current idle request callback timeout handle
   uint32_t mIdleCallbackTimeoutCounter;
 
   nsCOMPtr<nsITimer> mThrottleTimeoutsTimer;
   mozilla::TimeStamp mLastBudgetUpdate;
   mozilla::TimeDuration mExecutionBudget;
 
   bool mThrottleTimeouts;
   bool mThrottleTrackingTimeouts;
   bool mBudgetThrottleTimeouts;
 
+  bool mIsLoading;
+
   static uint32_t sNestingLevel;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif
--- 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,
@@ -881,17 +881,18 @@ nsGlobalWindowInner::nsGlobalWindowInner
   nsLayoutStatics::AddRef();
 
   // Initialize the PRCList (this).
   PR_INIT_CLIST(this);
 
   // add this inner window to the outer window list of inners.
   PR_INSERT_AFTER(this, aOuterWindow);
 
-  mTimeoutManager = MakeUnique<dom::TimeoutManager>(*this);
+  mTimeoutManager = MakeUnique<dom::TimeoutManager>(
+      *this, StaticPrefs::dom_timeout_max_idle_defer_ms());
 
   mObserver = new nsGlobalWindowObserver(this);
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   if (os) {
     // Watch for online/offline status changes so we can fire events. Use
     // a strong reference.
     os->AddObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, false);
 
@@ -2536,16 +2537,22 @@ bool nsPIDOMWindowInner::HasOpenWebSocke
 
 void nsPIDOMWindowInner::SetAudioCapture(bool aCapture) {
   RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
   if (service) {
     service->SetWindowAudioCaptured(GetOuterWindow(), mWindowID, aCapture);
   }
 }
 
+void nsPIDOMWindowInner::SetActiveLoadingState(bool aIsLoading) /* const? */ {
+  if (!nsGlobalWindowInner::Cast(this)->IsChromeWindow()) {
+    mTimeoutManager->SetLoading(aIsLoading);
+  }
+}
+
 // nsISpeechSynthesisGetter
 
 #ifdef MOZ_WEBSPEECH
 SpeechSynthesis* nsGlobalWindowInner::GetSpeechSynthesis(ErrorResult& aError) {
   if (!mSpeechSynthesis) {
     mSpeechSynthesis = new SpeechSynthesis(this);
   }
 
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -2051,16 +2051,22 @@ nsresult nsGlobalWindowOuter::SetNewDocu
       xpc::ClearContentXBLScope(newInnerGlobal);
     }
   } else {
     if (aState) {
       newInnerWindow = wsh->GetInnerWindow();
       newInnerGlobal = newInnerWindow->GetWrapperPreserveColor();
     } else {
       newInnerWindow = nsGlobalWindowInner::Create(this, thisChrome);
+      if (StaticPrefs::dom_timeout_defer_during_load()) {
+        // ensure the initial loading state is known
+        newInnerWindow->SetActiveLoadingState(
+            aDocument->GetReadyStateEnum() ==
+            Document::ReadyState::READYSTATE_LOADING);
+      }
 
       // The outer window is automatically treated as frozen when we
       // null out the inner window. As a result, initializing classes
       // on the new inner won't end up reaching into the old inner
       // window for classes etc.
       //
       // [This happens with Object.prototype when XPConnect creates
       // a temporary global while initializing classes; the reason
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -164,20 +164,24 @@ class nsPIDOMWindowInner : public mozIDO
   // Returns true if the document of this window is the active document.  This
   // is not identical to IsCurrentInnerWindow() because document.open() will
   // keep the same document active but create a new window.
   inline bool HasActiveDocument();
 
   // Returns true if this window is the same as mTopInnerWindow
   inline bool IsTopInnerWindow() const;
 
-  // Check whether a document is currently loading
+  // Check whether a document is currently loading (really checks if the
+  // load event has completed).  May not be reset to false on errors.
   inline bool IsLoading() const;
   inline bool IsHandlingResizeEvent() const;
 
+  // Note: not related to IsLoading.  Set to false if there's an error, etc.
+  void SetActiveLoadingState(bool aIsActiveLoading);
+
   bool AddAudioContext(mozilla::dom::AudioContext* aAudioContext);
   void RemoveAudioContext(mozilla::dom::AudioContext* aAudioContext);
   void MuteAudioContexts();
   void UnmuteAudioContexts();
 
   void SetAudioCapture(bool aCapture);
 
   mozilla::dom::Performance* GetPerformance();
--- 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/dom/svg/DOMSVGAngle.cpp
+++ b/dom/svg/DOMSVGAngle.cpp
@@ -2,26 +2,27 @@
 /* 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 "DOMSVGAngle.h"
 #include "SVGAngle.h"
 #include "mozilla/dom/SVGAngleBinding.h"
+#include "mozilla/dom/SVGSVGElement.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(DOMSVGAngle, mSVGElement)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DOMSVGAngle, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DOMSVGAngle, Release)
 
-DOMSVGAngle::DOMSVGAngle(SVGElement* aSVGElement)
+DOMSVGAngle::DOMSVGAngle(SVGSVGElement* aSVGElement)
     : mSVGElement(aSVGElement), mType(DOMSVGAngle::CreatedValue) {
   mVal = new SVGAngle();
   mVal->Init();
 }
 
 JSObject* DOMSVGAngle::WrapObject(JSContext* aCx,
                                   JS::Handle<JSObject*> aGivenProto) {
   return SVGAngle_Binding::Wrap(aCx, this, aGivenProto);
--- a/dom/svg/DOMSVGAngle.h
+++ b/dom/svg/DOMSVGAngle.h
@@ -10,16 +10,17 @@
 #include "nsWrapperCache.h"
 #include "SVGElement.h"
 #include "mozilla/Attributes.h"
 
 class SVGAngle;
 
 namespace mozilla {
 namespace dom {
+class SVGSVGElement;
 
 class DOMSVGAngle final : public nsWrapperCache {
  public:
   typedef enum { BaseValue, AnimValue, CreatedValue } AngleType;
 
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGAngle)
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGAngle)
 
@@ -28,17 +29,17 @@ class DOMSVGAngle final : public nsWrapp
    */
   DOMSVGAngle(SVGAngle* aVal, SVGElement* aSVGElement, AngleType aType)
       : mVal(aVal), mSVGElement(aSVGElement), mType(aType) {}
 
   /**
    * Ctor for creating the objects returned by SVGSVGElement.createSVGAngle(),
    * which do not initially belong to an attribute.
    */
-  explicit DOMSVGAngle(SVGElement* aSVGElement);
+  explicit DOMSVGAngle(SVGSVGElement* aSVGElement);
 
   // WebIDL
   SVGElement* GetParentObject() { return mSVGElement; }
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
   uint16_t UnitType() const;
   float Value() const;
   void GetValueAsString(nsAString& aValue);
--- a/dom/svg/DOMSVGNumber.cpp
+++ b/dom/svg/DOMSVGNumber.cpp
@@ -7,16 +7,17 @@
 #include "DOMSVGNumber.h"
 #include "DOMSVGNumberList.h"
 #include "DOMSVGAnimatedNumberList.h"
 #include "SVGAnimatedNumberList.h"
 #include "SVGElement.h"
 #include "nsError.h"
 #include "nsContentUtils.h"  // for NS_ENSURE_FINITE
 #include "mozilla/dom/SVGNumberBinding.h"
+#include "mozilla/dom/SVGSVGElement.h"
 
 // See the architecture comment in DOMSVGAnimatedNumberList.h.
 
 namespace mozilla {
 namespace dom {
 
 // We could use NS_IMPL_CYCLE_COLLECTION(, except that in Unlink() we need to
 // clear our list's weak ref to us to be safe. (The other option would be to
@@ -100,16 +101,24 @@ DOMSVGNumber::DOMSVGNumber(DOMSVGNumberL
 DOMSVGNumber::DOMSVGNumber(nsISupports* aParent)
     : mList(nullptr),
       mParent(aParent),
       mListIndex(0),
       mAttrEnum(0),
       mIsAnimValItem(false),
       mValue(0.0f) {}
 
+DOMSVGNumber::DOMSVGNumber(SVGSVGElement* aParent)
+    : mList(nullptr),
+      mParent(ToSupports(aParent)),
+      mListIndex(0),
+      mAttrEnum(0),
+      mIsAnimValItem(false),
+      mValue(0.0f) {}
+
 float DOMSVGNumber::Value() {
   if (mIsAnimValItem && HasOwner()) {
     Element()->FlushAnimations();  // May make HasOwner() == false
   }
   return HasOwner() ? InternalItem() : mValue;
 }
 
 void DOMSVGNumber::SetValue(float aValue, ErrorResult& aRv) {
--- a/dom/svg/DOMSVGNumber.h
+++ b/dom/svg/DOMSVGNumber.h
@@ -17,16 +17,17 @@
 #include "nsWrapperCache.h"
 
 #define MOZ_SVG_LIST_INDEX_BIT_COUNT 27  // supports > 134 million list items
 
 namespace mozilla {
 
 namespace dom {
 class SVGElement;
+class SVGSVGElement;
 
 /**
  * Class DOMSVGNumber
  *
  * This class creates the DOM objects that wrap internal SVGNumber objects that
  * are in an SVGNumberList. It is also used to create the objects returned by
  * SVGSVGElement.createSVGNumber().
  *
@@ -57,18 +58,22 @@ class DOMSVGNumber final : public nsISup
    */
   DOMSVGNumber(DOMSVGNumberList* aList, uint8_t aAttrEnum, uint32_t aListIndex,
                bool aIsAnimValItem);
 
   /**
    * Ctor for creating the objects returned by SVGSVGElement.createSVGNumber(),
    * which do not initially belong to an attribute.
    */
+  explicit DOMSVGNumber(SVGSVGElement* aParent);
+
+ private:
   explicit DOMSVGNumber(nsISupports* aParent);
 
+ public:
   /**
    * Create an unowned copy. The caller is responsible for the first AddRef().
    */
   DOMSVGNumber* Clone() {
     DOMSVGNumber* clone = new DOMSVGNumber(mParent);
     clone->mValue = ToSVGNumber();
     return clone;
   }
--- a/dom/svg/SVGGeometryElement.cpp
+++ b/dom/svg/SVGGeometryElement.cpp
@@ -78,17 +78,17 @@ bool SVGGeometryElement::GeometryDepends
       return true;
     }
   }
   return false;
 }
 
 bool SVGGeometryElement::IsMarkable() { return false; }
 
-void SVGGeometryElement::GetMarkPoints(nsTArray<nsSVGMark>* aMarks) {}
+void SVGGeometryElement::GetMarkPoints(nsTArray<SVGMark>* aMarks) {}
 
 already_AddRefed<Path> SVGGeometryElement::GetOrBuildPath(
     const DrawTarget* aDrawTarget, FillRule aFillRule) {
   // We only cache the path if it matches the backend used for screen painting:
   bool cacheable = aDrawTarget->GetBackendType() ==
                    gfxPlatform::GetPlatform()->GetDefaultContentBackend();
 
   if (cacheable && mCachedPath && mCachedPath->GetFillRule() == aFillRule &&
--- a/dom/svg/SVGGeometryElement.h
+++ b/dom/svg/SVGGeometryElement.h
@@ -7,32 +7,33 @@
 #ifndef mozilla_dom_SVGGeometryElement_h
 #define mozilla_dom_SVGGeometryElement_h
 
 #include "mozilla/gfx/2D.h"
 #include "SVGGraphicsElement.h"
 #include "nsISVGPoint.h"
 #include "nsSVGNumber2.h"
 
-struct nsSVGMark {
+namespace mozilla {
+
+struct SVGMark {
   enum Type {
     eStart,
     eMid,
     eEnd,
 
     eTypeCount
   };
 
   float x, y, angle;
   Type type;
-  nsSVGMark(float aX, float aY, float aAngle, Type aType)
+  SVGMark(float aX, float aY, float aAngle, Type aType)
       : x(aX), y(aY), angle(aAngle), type(aType) {}
 };
 
-namespace mozilla {
 namespace dom {
 
 class SVGAnimatedNumber;
 
 typedef mozilla::dom::SVGGraphicsElement SVGGeometryElementBase;
 
 class SVGGeometryElement : public SVGGeometryElementBase {
  protected:
@@ -73,17 +74,17 @@ class SVGGeometryElement : public SVGGeo
    * which AttributeDefinesGeometry returns true has a percentage value.
    *
    * This could be moved up to a more general class so it can be used for
    * non-leaf elements, but that would require care and for now there's no need.
    */
   bool GeometryDependsOnCoordCtx();
 
   virtual bool IsMarkable();
-  virtual void GetMarkPoints(nsTArray<nsSVGMark>* aMarks);
+  virtual void GetMarkPoints(nsTArray<SVGMark>* aMarks);
 
   /**
    * A method that can be faster than using a Moz2D Path and calling GetBounds/
    * GetStrokedBounds on it.  It also helps us avoid rounding error for simple
    * shapes and simple transforms where the Moz2D Path backends can fail to
    * produce the clean integer bounds that content authors expect in some cases.
    *
    * If |aToNonScalingStrokeSpace| is non-null then |aBounds|, which is computed
--- a/dom/svg/SVGLineElement.cpp
+++ b/dom/svg/SVGLineElement.cpp
@@ -94,25 +94,25 @@ SVGLineElement::IsAttributeMapped(const 
 SVGElement::LengthAttributesInfo SVGLineElement::GetLengthInfo() {
   return LengthAttributesInfo(mLengthAttributes, sLengthInfo,
                               ArrayLength(sLengthInfo));
 }
 
 //----------------------------------------------------------------------
 // SVGGeometryElement methods
 
-void SVGLineElement::GetMarkPoints(nsTArray<nsSVGMark>* aMarks) {
+void SVGLineElement::GetMarkPoints(nsTArray<SVGMark>* aMarks) {
   float x1, y1, x2, y2;
 
   GetAnimatedLengthValues(&x1, &y1, &x2, &y2, nullptr);
 
   float angle = atan2(y2 - y1, x2 - x1);
 
-  aMarks->AppendElement(nsSVGMark(x1, y1, angle, nsSVGMark::eStart));
-  aMarks->AppendElement(nsSVGMark(x2, y2, angle, nsSVGMark::eEnd));
+  aMarks->AppendElement(SVGMark(x1, y1, angle, SVGMark::eStart));
+  aMarks->AppendElement(SVGMark(x2, y2, angle, SVGMark::eEnd));
 }
 
 void SVGLineElement::GetAsSimplePath(SimplePath* aSimplePath) {
   float x1, y1, x2, y2;
   GetAnimatedLengthValues(&x1, &y1, &x2, &y2, nullptr);
 
   MaybeAdjustForZeroLength(x1, y1, x2, y2);
   aSimplePath->SetLine(x1, y1, x2, y2);
--- a/dom/svg/SVGLineElement.h
+++ b/dom/svg/SVGLineElement.h
@@ -32,17 +32,17 @@ class SVGLineElement final : public SVGL
   void MaybeAdjustForZeroLength(float aX1, float aY1, float& aX2, float aY2);
 
  public:
   // nsIContent interface
   NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* name) const override;
 
   // SVGGeometryElement methods:
   virtual bool IsMarkable() override { return true; }
-  virtual void GetMarkPoints(nsTArray<nsSVGMark>* aMarks) override;
+  virtual void GetMarkPoints(nsTArray<SVGMark>* aMarks) override;
   virtual void GetAsSimplePath(SimplePath* aSimplePath) override;
   virtual already_AddRefed<Path> BuildPath(PathBuilder* aBuilder) override;
   virtual bool GetGeometryBounds(
       Rect* aBounds, const StrokeOptions& aStrokeOptions,
       const Matrix& aToBoundsSpace,
       const Matrix* aToNonScalingStrokeSpace = nullptr) override;
 
   virtual nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
--- a/dom/svg/SVGMarkerElement.cpp
+++ b/dom/svg/SVGMarkerElement.cpp
@@ -245,29 +245,29 @@ SVGViewBox* SVGMarkerElement::GetViewBox
 SVGAnimatedPreserveAspectRatio* SVGMarkerElement::GetPreserveAspectRatio() {
   return &mPreserveAspectRatio;
 }
 
 //----------------------------------------------------------------------
 // public helpers
 
 gfx::Matrix SVGMarkerElement::GetMarkerTransform(float aStrokeWidth,
-                                                 const nsSVGMark& aMark) {
+                                                 const SVGMark& aMark) {
   float scale =
       mEnumAttributes[MARKERUNITS].GetAnimValue() == SVG_MARKERUNITS_STROKEWIDTH
           ? aStrokeWidth
           : 1.0f;
 
   float angle;
   switch (mOrientType.GetAnimValueInternal()) {
     case SVG_MARKER_ORIENT_AUTO:
       angle = aMark.angle;
       break;
     case SVG_MARKER_ORIENT_AUTO_START_REVERSE:
-      angle = aMark.angle + (aMark.type == nsSVGMark::eStart ? M_PI : 0.0f);
+      angle = aMark.angle + (aMark.type == SVGMark::eStart ? M_PI : 0.0f);
       break;
     default:  // SVG_MARKER_ORIENT_ANGLE
       angle = mAngleAttributes[ORIENT].GetAnimValue() * M_PI / 180.0f;
       break;
   }
 
   return gfx::Matrix(cos(angle) * scale, sin(angle) * scale,
                      -sin(angle) * scale, cos(angle) * scale, aMark.x, aMark.y);
--- a/dom/svg/SVGMarkerElement.h
+++ b/dom/svg/SVGMarkerElement.h
@@ -14,22 +14,24 @@
 #include "nsSVGLength2.h"
 #include "SVGViewBox.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/SVGAnimatedEnumeration.h"
 #include "mozilla/dom/SVGElement.h"
 #include "mozilla/dom/SVGMarkerElementBinding.h"
 
 class nsSVGMarkerFrame;
-struct nsSVGMark;
 
 nsresult NS_NewSVGMarkerElement(
     nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
 
 namespace mozilla {
+
+struct SVGMark;
+
 namespace dom {
 
 // Non-Exposed Marker Orientation Types
 static const uint16_t SVG_MARKER_ORIENT_AUTO_START_REVERSE = 3;
 
 class nsSVGOrientType {
  public:
   nsSVGOrientType()
@@ -103,17 +105,17 @@ class SVGMarkerElement : public SVGMarke
                                 const nsAttrValue* aOldValue,
                                 nsIPrincipal* aMaybeScriptedPrincipal,
                                 bool aNotify) override;
 
   // nsSVGSVGElement methods:
   virtual bool HasValidDimensions() const override;
 
   // public helpers
-  gfx::Matrix GetMarkerTransform(float aStrokeWidth, const nsSVGMark& aMark);
+  gfx::Matrix GetMarkerTransform(float aStrokeWidth, const SVGMark& aMark);
   SVGViewBoxRect GetViewBoxRect();
   gfx::Matrix GetViewBoxTransform();
 
   virtual nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
 
   nsSVGOrientType* GetOrientType() { return &mOrientType; }
 
   // WebIDL
--- a/dom/svg/SVGPathData.cpp
+++ b/dom/svg/SVGPathData.cpp
@@ -13,17 +13,17 @@
 #include "mozilla/gfx/Point.h"
 #include "mozilla/RefPtr.h"
 #include "nsError.h"
 #include "nsString.h"
 #include "SVGPathDataParser.h"
 #include <stdarg.h>
 #include "nsStyleConsts.h"
 #include "SVGContentUtils.h"
-#include "SVGGeometryElement.h"  // for nsSVGMark
+#include "SVGGeometryElement.h"
 #include "SVGPathSegUtils.h"
 #include <algorithm>
 
 using namespace mozilla;
 using namespace mozilla::dom::SVGPathSeg_Binding;
 using namespace mozilla::gfx;
 
 static inline bool IsMoveto(uint16_t aSegType) {
@@ -743,17 +743,17 @@ static double AngleOfVector(const Point&
 
   return (aVector != Point(0.0, 0.0)) ? atan2(aVector.y, aVector.x) : 0.0;
 }
 
 static float AngleOfVector(const Point& cp1, const Point& cp2) {
   return static_cast<float>(AngleOfVector(cp1 - cp2));
 }
 
-void SVGPathData::GetMarkerPositioningData(nsTArray<nsSVGMark>* aMarks) const {
+void SVGPathData::GetMarkerPositioningData(nsTArray<SVGMark>* aMarks) const {
   // This code should assume that ANY type of segment can appear at ANY index.
   // It should also assume that segments such as M and Z can appear in weird
   // places, and repeat multiple times consecutively.
 
   // info on current [sub]path (reset every M command):
   Point pathStart(0.0, 0.0);
   float pathStartAngle = 0.0f;
 
@@ -1013,34 +1013,34 @@ void SVGPathData::GetMarkerPositioningDa
         // Leave any existing marks in aMarks so we have a visual indication of
         // when things went wrong.
         MOZ_ASSERT(false, "Unknown segment type - path corruption?");
         return;
     }
 
     // Set the angle of the mark at the start of this segment:
     if (aMarks->Length()) {
-      nsSVGMark& mark = aMarks->LastElement();
+      SVGMark& mark = aMarks->LastElement();
       if (!IsMoveto(segType) && IsMoveto(prevSegType)) {
         // start of new subpath
         pathStartAngle = mark.angle = segStartAngle;
       } else if (IsMoveto(segType) && !IsMoveto(prevSegType)) {
         // end of a subpath
         if (prevSegType != PATHSEG_CLOSEPATH) mark.angle = prevSegEndAngle;
       } else {
         if (!(segType == PATHSEG_CLOSEPATH && prevSegType == PATHSEG_CLOSEPATH))
           mark.angle =
               SVGContentUtils::AngleBisect(prevSegEndAngle, segStartAngle);
       }
     }
 
     // Add the mark at the end of this segment, and set its position:
-    if (!aMarks->AppendElement(nsSVGMark(static_cast<float>(segEnd.x),
-                                         static_cast<float>(segEnd.y), 0.0f,
-                                         nsSVGMark::eMid))) {
+    if (!aMarks->AppendElement(SVGMark(static_cast<float>(segEnd.x),
+                                       static_cast<float>(segEnd.y), 0.0f,
+                                       SVGMark::eMid))) {
       aMarks->Clear();  // OOM, so try to free some
       return;
     }
 
     if (segType == PATHSEG_CLOSEPATH && prevSegType != PATHSEG_CLOSEPATH) {
       aMarks->LastElement().angle =
           // aMarks->ElementAt(pathStartIndex).angle =
           SVGContentUtils::AngleBisect(segEndAngle, pathStartAngle);
@@ -1052,18 +1052,18 @@ void SVGPathData::GetMarkerPositioningDa
   }
 
   MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
 
   if (aMarks->Length()) {
     if (prevSegType != PATHSEG_CLOSEPATH) {
       aMarks->LastElement().angle = prevSegEndAngle;
     }
-    aMarks->LastElement().type = nsSVGMark::eEnd;
-    aMarks->ElementAt(0).type = nsSVGMark::eStart;
+    aMarks->LastElement().type = SVGMark::eEnd;
+    aMarks->ElementAt(0).type = SVGMark::eStart;
   }
 }
 
 size_t SVGPathData::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
   return mData.ShallowSizeOfExcludingThis(aMallocSizeOf);
 }
 
 size_t SVGPathData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
--- a/dom/svg/SVGPathData.h
+++ b/dom/svg/SVGPathData.h
@@ -16,19 +16,19 @@
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/Types.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/RefPtr.h"
 #include "nsTArray.h"
 
 #include <string.h>
 
-struct nsSVGMark;
+namespace mozilla {
 
-namespace mozilla {
+struct SVGMark;
 
 class SVGPathDataParser;  // IWYU pragma: keep
 
 /**
  * ATTENTION! WARNING! WATCH OUT!!
  *
  * Consumers that modify objects of this type absolutely MUST keep the DOM
  * wrappers for those lists (if any) in sync!! That's why this class is so
@@ -132,17 +132,17 @@ class SVGPathData {
   }
 
   void Compact() { mData.Compact(); }
 
   float GetPathLength() const;
 
   uint32_t GetPathSegAtLength(float aLength) const;
 
-  void GetMarkerPositioningData(nsTArray<nsSVGMark>* aMarks) const;
+  void GetMarkerPositioningData(nsTArray<SVGMark>* aMarks) const;
 
   /**
    * Returns true, except on OOM, in which case returns false.
    */
   bool GetSegmentLengths(nsTArray<double>* aLengths) const;
 
   /**
    * Returns true, except on OOM, in which case returns false.
--- a/dom/svg/SVGPathElement.cpp
+++ b/dom/svg/SVGPathElement.cpp
@@ -239,17 +239,17 @@ already_AddRefed<Path> SVGPathElement::G
 // SVGGeometryElement methods
 
 bool SVGPathElement::AttributeDefinesGeometry(const nsAtom* aName) {
   return aName == nsGkAtoms::d || aName == nsGkAtoms::pathLength;
 }
 
 bool SVGPathElement::IsMarkable() { return true; }
 
-void SVGPathElement::GetMarkPoints(nsTArray<nsSVGMark>* aMarks) {
+void SVGPathElement::GetMarkPoints(nsTArray<SVGMark>* aMarks) {
   mD.GetAnimValue().GetMarkerPositioningData(aMarks);
 }
 
 already_AddRefed<Path> SVGPathElement::BuildPath(PathBuilder* aBuilder) {
   // The Moz2D PathBuilder that our SVGPathData will be using only cares about
   // the fill rule. However, in order to fulfill the requirements of the SVG
   // spec regarding zero length sub-paths when square line caps are in use,
   // SVGPathData needs to know our stroke-linecap style and, if "square", then
--- a/dom/svg/SVGPathElement.h
+++ b/dom/svg/SVGPathElement.h
@@ -42,17 +42,17 @@ class SVGPathElement final : public SVGP
   NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* name) const override;
 
   // nsSVGSVGElement methods:
   virtual bool HasValidDimensions() const override;
 
   // SVGGeometryElement methods:
   virtual bool AttributeDefinesGeometry(const nsAtom* aName) override;
   virtual bool IsMarkable() override;
-  virtual void GetMarkPoints(nsTArray<nsSVGMark>* aMarks) override;
+  virtual void GetMarkPoints(nsTArray<SVGMark>* aMarks) override;
   virtual already_AddRefed<Path> BuildPath(PathBuilder* aBuilder) override;
 
   /**
    * This returns a path without the extra little line segments that
    * ApproximateZeroLengthSubpathSquareCaps can insert if we have square-caps.
    * See the comment for that function for more info on that.
    */
   virtual already_AddRefed<Path> GetOrBuildPathForMeasuring() override;
--- a/dom/svg/SVGPolyElement.cpp
+++ b/dom/svg/SVGPolyElement.cpp
@@ -59,47 +59,47 @@ SVGPolyElement::IsAttributeMapped(const 
 // SVGGeometryElement methods
 
 bool SVGPolyElement::AttributeDefinesGeometry(const nsAtom* aName) {
   if (aName == nsGkAtoms::points) return true;
 
   return false;
 }
 
-void SVGPolyElement::GetMarkPoints(nsTArray<nsSVGMark>* aMarks) {
+void SVGPolyElement::GetMarkPoints(nsTArray<SVGMark>* aMarks) {
   const SVGPointList& points = mPoints.GetAnimValue();
 
   if (!points.Length()) return;
 
   float px = points[0].mX, py = points[0].mY, prevAngle = 0.0;
 
-  aMarks->AppendElement(nsSVGMark(px, py, 0, nsSVGMark::eStart));
+  aMarks->AppendElement(SVGMark(px, py, 0, SVGMark::eStart));
 
   for (uint32_t i = 1; i < points.Length(); ++i) {
     float x = points[i].mX;
     float y = points[i].mY;
     float angle = atan2(y - py, x - px);
 
     // Vertex marker.
     if (i == 1) {
       aMarks->ElementAt(0).angle = angle;
     } else {
       aMarks->ElementAt(aMarks->Length() - 1).angle =
           SVGContentUtils::AngleBisect(prevAngle, angle);
     }
 
-    aMarks->AppendElement(nsSVGMark(x, y, 0, nsSVGMark::eMid));
+    aMarks->AppendElement(SVGMark(x, y, 0, SVGMark::eMid));
 
     prevAngle = angle;
     px = x;
     py = y;
   }
 
   aMarks->LastElement().angle = prevAngle;
-  aMarks->LastElement().type = nsSVGMark::eEnd;
+  aMarks->LastElement().type = SVGMark::eEnd;
 }
 
 bool SVGPolyElement::GetGeometryBounds(Rect* aBounds,
                                        const StrokeOptions& aStrokeOptions,
                                        const Matrix& aToBoundsSpace,
                                        const Matrix* aToNonScalingStrokeSpace) {
   const SVGPointList& points = mPoints.GetAnimValue();
 
--- a/dom/svg/SVGPolyElement.h
+++ b/dom/svg/SVGPolyElement.h
@@ -40,17 +40,17 @@ class SVGPolyElement : public SVGPolyEle
   }
 
   // SVGElement methods:
   virtual bool HasValidDimensions() const override;
 
   // SVGGeometryElement methods:
   virtual bool AttributeDefinesGeometry(const nsAtom* aName) override;
   virtual bool IsMarkable() override { return true; }
-  virtual void GetMarkPoints(nsTArray<nsSVGMark>* aMarks) override;
+  virtual void GetMarkPoints(nsTArray<SVGMark>* aMarks) override;
   virtual bool GetGeometryBounds(
       Rect* aBounds, const StrokeOptions& aStrokeOptions,
       const Matrix& aToBoundsSpace,
       const Matrix* aToNonScalingStrokeSpace = nullptr) override;
 
   // WebIDL
   already_AddRefed<mozilla::DOMSVGPointList> Points();
   already_AddRefed<mozilla::DOMSVGPointList> AnimatedPoints();
--- a/dom/svg/SVGPolygonElement.cpp
+++ b/dom/svg/SVGPolygonElement.cpp
@@ -32,35 +32,35 @@ SVGPolygonElement::SVGPolygonElement(
 //----------------------------------------------------------------------
 // nsINode methods
 
 NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGPolygonElement)
 
 //----------------------------------------------------------------------
 // SVGGeometryElement methods
 
-void SVGPolygonElement::GetMarkPoints(nsTArray<nsSVGMark> *aMarks) {
+void SVGPolygonElement::GetMarkPoints(nsTArray<SVGMark> *aMarks) {
   SVGPolyElement::GetMarkPoints(aMarks);
 
-  if (aMarks->IsEmpty() || aMarks->LastElement().type != nsSVGMark::eEnd) {
+  if (aMarks->IsEmpty() || aMarks->LastElement().type != SVGMark::eEnd) {
     return;
   }
 
-  nsSVGMark *endMark = &aMarks->LastElement();
-  nsSVGMark *startMark = &aMarks->ElementAt(0);
+  SVGMark *endMark = &aMarks->LastElement();
+  SVGMark *startMark = &aMarks->ElementAt(0);
   float angle = atan2(startMark->y - endMark->y, startMark->x - endMark->x);
 
-  endMark->type = nsSVGMark::eMid;
+  endMark->type = SVGMark::eMid;
   endMark->angle = SVGContentUtils::AngleBisect(angle, endMark->angle);
   startMark->angle = SVGContentUtils::AngleBisect(angle, startMark->angle);
   // for a polygon (as opposed to a polyline) there's an implicit extra point
   // co-located with the start point that SVGPolyElement::GetMarkPoints
   // doesn't return
   aMarks->AppendElement(
-      nsSVGMark(startMark->x, startMark->y, startMark->angle, nsSVGMark::eEnd));
+      SVGMark(startMark->x, startMark->y, startMark->angle, SVGMark::eEnd));
 }
 
 already_AddRefed<Path> SVGPolygonElement::BuildPath(PathBuilder *aBuilder) {
   const SVGPointList &points = mPoints.GetAnimValue();
 
   if (points.IsEmpty()) {
     return nullptr;
   }
--- a/dom/svg/SVGPolygonElement.h
+++ b/dom/svg/SVGPolygonElement.h
@@ -25,17 +25,17 @@ class SVGPolygonElement final : public S
   virtual JSObject* WrapNode(JSContext* cx,
                              JS::Handle<JSObject*> aGivenProto) override;
   friend nsresult(::NS_NewSVGPolygonElement(
       nsIContent** aResult,
       already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo));
 
  public:
   // SVGGeometryElement methods:
-  virtual void GetMarkPoints(nsTArray<nsSVGMark>* aMarks) override;
+  virtual void GetMarkPoints(nsTArray<SVGMark>* aMarks) override;
   virtual already_AddRefed<Path> BuildPath(PathBuilder* aBuilder) override;
 
   virtual nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
--- a/dom/svg/SVGSVGElement.cpp
+++ b/dom/svg/SVGSVGElement.cpp
@@ -247,52 +247,46 @@ void SVGSVGElement::DeselectAll() {
   nsIFrame* frame = GetPrimaryFrame();
   if (frame) {
     RefPtr<nsFrameSelection> frameSelection = frame->GetFrameSelection();
     frameSelection->ClearNormalSelection();
   }
 }
 
 already_AddRefed<DOMSVGNumber> SVGSVGElement::CreateSVGNumber() {
-  RefPtr<DOMSVGNumber> number = new DOMSVGNumber(ToSupports(this));
-  return number.forget();
+  return do_AddRef(new DOMSVGNumber(this));
 }
 
 already_AddRefed<DOMSVGLength> SVGSVGElement::CreateSVGLength() {
-  nsCOMPtr<DOMSVGLength> length = new DOMSVGLength();
-  return length.forget();
+  return do_AddRef(new DOMSVGLength());
 }
 
 already_AddRefed<DOMSVGAngle> SVGSVGElement::CreateSVGAngle() {
   return do_AddRef(new DOMSVGAngle(this));
 }
 
 already_AddRefed<nsISVGPoint> SVGSVGElement::CreateSVGPoint() {
-  nsCOMPtr<nsISVGPoint> point = new DOMSVGPoint(0, 0);
-  return point.forget();
+  return do_AddRef(new DOMSVGPoint(0, 0));
 }
 
 already_AddRefed<SVGMatrix> SVGSVGElement::CreateSVGMatrix() {
-  RefPtr<SVGMatrix> matrix = new SVGMatrix();
-  return matrix.forget();
+  return do_AddRef(new SVGMatrix());
 }
 
 already_AddRefed<SVGIRect> SVGSVGElement::CreateSVGRect() {
   return NS_NewSVGRect(this);
 }
 
 already_AddRefed<DOMSVGTransform> SVGSVGElement::CreateSVGTransform() {
-  RefPtr<DOMSVGTransform> transform = new DOMSVGTransform();
-  return transform.forget();
+  return do_AddRef(new DOMSVGTransform());
 }
 
 already_AddRefed<DOMSVGTransform> SVGSVGElement::CreateSVGTransformFromMatrix(
     SVGMatrix& matrix) {
-  RefPtr<DOMSVGTransform> transform = new DOMSVGTransform(matrix.GetMatrix());
-  return transform.forget();
+  return do_AddRef(new DOMSVGTransform(matrix.GetMatrix()));
 }
 
 //----------------------------------------------------------------------
 // helper method for implementing SetCurrentScale/Translate
 
 void SVGSVGElement::SetCurrentScaleTranslate(float s, float x, float y) {
   if (s == mCurrentScale && x == mCurrentTranslate.GetX() &&
       y == mCurrentTranslate.GetY()) {
--- 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/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -3056,16 +3056,19 @@ bool gfxFont::SplitAndInitTextRun(
         DebugOnly<char16_t> boundary16 = boundary;
         NS_ASSERTION(boundary16 < 256, "unexpected boundary!");
         gfxShapedWord* sw = GetShapedWord(
             aDrawTarget, &boundary, 1, gfxShapedWord::HashMix(0, boundary),
             aRunScript, vertical, appUnitsPerDevUnit,
             flags | gfx::ShapedTextFlags::TEXT_IS_8BIT, rounding, tp);
         if (sw) {
           aTextRun->CopyGlyphDataFrom(sw, aRunStart + i);
+          if (boundary == ' ') {
+            aTextRun->GetCharacterGlyphs()[aRunStart + i].SetIsSpace();
+          }
         } else {
           return false;
         }
       }
       hash = 0;
       wordStart = i + 1;
       wordIs8Bit = true;
       continue;
--- 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/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -1531,16 +1531,17 @@ void gfxTextRun::SetSpaceGlyph(gfxFont* 
       aFont->GetRoundOffsetsToPixels(aDrawTarget);
   gfxShapedWord* sw = aFont->GetShapedWord(
       aDrawTarget, &space, 1, gfxShapedWord::HashMix(0, ' '), Script::LATIN,
       vertical, mAppUnitsPerDevUnit, flags, roundingFlags, nullptr);
   if (sw) {
     AddGlyphRun(aFont, gfxTextRange::MatchType::kFontGroup, aCharIndex, false,
                 aOrientation);
     CopyGlyphDataFrom(sw, aCharIndex);
+    GetCharacterGlyphs()[aCharIndex].SetIsSpace();
   }
 }
 
 bool gfxTextRun::SetSpaceGlyphIfSimple(gfxFont* aFont, uint32_t aCharIndex,
                                        char16_t aSpaceChar,
                                        gfx::ShapedTextFlags aOrientation) {
   uint32_t spaceGlyph = aFont->GetSpaceGlyph();
   if (!spaceGlyph || !CompressedGlyph::IsSimpleGlyphID(spaceGlyph)) {
--- 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()) {
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text/1522857-1-ref.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<div style="font:300px monospace;width:3ch;text-align:right">ab<br>cd</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text/1522857-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<div style="font:300px monospace;width:3ch;text-align:right">ab cd</div>
--- a/layout/reftests/text/reftest.list
+++ b/layout/reftests/text/reftest.list
@@ -183,16 +183,17 @@ random-if(!winWidget) == arial-bold-lam-
 == 745555-2.html 745555-2-ref.html
 == 820255.html 820255-ref.html
 != 1170688.html 1170688-ref.html
 fails-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == 1320665-cmap-format-13.html 1320665-cmap-format-13-ref.html # see bug 1320665 comments 8-9
 == 1331339-script-extensions-shaping-1.html 1331339-script-extensions-shaping-1-ref.html
 skip-if(!cocoaWidget) != 1349308-1.html 1349308-notref.html # macOS-specific test for -apple-system glyph metrics
 fails-if(winWidget||gtkWidget||Android) == 1463020-letter-spacing-text-transform-1.html 1463020-letter-spacing-text-transform-1-ref.html # Linux, Win7: bug 1463161; Android, Win10: regional indicators not supported by system emoji font
 fails-if(Android) == 1463020-letter-spacing-text-transform-2.html 1463020-letter-spacing-text-transform-2-ref.html # missing font coverage on Android
+fuzzy-if(!webrender,12-66,288-1660) == 1522857-1.html 1522857-1-ref.html # antialiasing fuzz in non-webrender cases
 
 # ensure emoji chars don't render blank (bug 715798, bug 779042);
 # should at least render hexboxes if there's no font support
 != emoji-01.html emoji-01-notref.html
 != emoji-02.html emoji-02-notref.html
 
 # Bug 727276: tests with variation selectors 15 and 16 to control emoji rendering style
 == emoji-03.html emoji-03-ref.html
--- a/layout/svg/SVGGeometryFrame.cpp
+++ b/layout/svg/SVGGeometryFrame.cpp
@@ -595,24 +595,24 @@ SVGBBox SVGGeometryFrame::GetBBoxContrib
       bbox.UnionEdges(strokeBBoxExtents);
 #endif
     }
   }
 
   // Account for markers:
   if ((aFlags & nsSVGUtils::eBBoxIncludeMarkers) != 0 &&
       element->IsMarkable()) {
-    nsSVGMarkerFrame* markerFrames[nsSVGMark::eTypeCount];
+    nsSVGMarkerFrame* markerFrames[SVGMark::eTypeCount];
     if (SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames)) {
-      nsTArray<nsSVGMark> marks;
+      nsTArray<SVGMark> marks;
       element->GetMarkPoints(&marks);
       if (uint32_t num = marks.Length()) {
         float strokeWidth = nsSVGUtils::GetStrokeWidth(this);
         for (uint32_t i = 0; i < num; i++) {
-          const nsSVGMark& mark = marks[i];
+          const SVGMark& mark = marks[i];
           nsSVGMarkerFrame* frame = markerFrames[mark.type];
           if (frame) {
             SVGBBox mbbox = frame->GetMarkBBoxContribution(
                 aToBBoxUserspace, aFlags, this, mark, strokeWidth);
             MOZ_ASSERT(mbbox.IsFinite(), "bbox is about to go bad");
             bbox.UnionEdges(mbbox);
           }
         }
@@ -753,26 +753,26 @@ void SVGGeometryFrame::Render(gfxContext
 }
 
 void SVGGeometryFrame::PaintMarkers(gfxContext& aContext,
                                     const gfxMatrix& aTransform,
                                     imgDrawingParams& aImgParams) {
   auto element = static_cast<SVGGeometryElement*>(GetContent());
 
   if (element->IsMarkable()) {
-    nsSVGMarkerFrame* markerFrames[nsSVGMark::eTypeCount];
+    nsSVGMarkerFrame* markerFrames[SVGMark::eTypeCount];
     if (SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames)) {
-      nsTArray<nsSVGMark> marks;
+      nsTArray<SVGMark> marks;
       element->GetMarkPoints(&marks);
       if (uint32_t num = marks.Length()) {
         SVGContextPaint* contextPaint =
             SVGContextPaint::GetContextPaint(GetContent());
         float strokeWidth = nsSVGUtils::GetStrokeWidth(this, contextPaint);
         for (uint32_t i = 0; i < num; i++) {
-          const nsSVGMark& mark = marks[i];
+          const SVGMark& mark = marks[i];
           nsSVGMarkerFrame* frame = markerFrames[mark.type];
           if (frame) {
             frame->PaintMark(aContext, aTransform, this, mark, strokeWidth,
                              aImgParams);
           }
         }
       }
     }
--- a/layout/svg/SVGObserverUtils.cpp
+++ b/layout/svg/SVGObserverUtils.cpp
@@ -1039,17 +1039,17 @@ bool SVGObserverUtils::GetAndObserveMark
 #define GET_MARKER(type)                                                    \
   markerURL = GetMarkerURI(aMarkedFrame, &nsStyleSVG::mMarker##type);       \
   observer =                                                                \
       GetEffectProperty(markerURL, aMarkedFrame, Marker##type##Property()); \
   marker = observer ? observer->GetAndObserveReferencedFrame(               \
                           LayoutFrameType::SVGMarker, nullptr)              \
                     : nullptr;                                              \
   foundMarker = foundMarker || bool(marker);                                \
-  (*aFrames)[nsSVGMark::e##type] = static_cast<nsSVGMarkerFrame*>(marker);
+  (*aFrames)[SVGMark::e##type] = static_cast<nsSVGMarkerFrame*>(marker);
 
   GET_MARKER(Start)
   GET_MARKER(Mid)
   GET_MARKER(End)
 
 #undef GET_MARKER
 
   return foundMarker;
--- a/layout/svg/nsSVGMarkerFrame.cpp
+++ b/layout/svg/nsSVGMarkerFrame.cpp
@@ -83,17 +83,17 @@ static nsIFrame* GetAnonymousChildFrame(
   MOZ_ASSERT(kid && kid->IsSVGMarkerAnonChildFrame(),
              "expected to find anonymous child of marker frame");
   return kid;
 }
 
 void nsSVGMarkerFrame::PaintMark(gfxContext& aContext,
                                  const gfxMatrix& aToMarkedFrameUserSpace,
                                  SVGGeometryFrame* aMarkedFrame,
-                                 const nsSVGMark& aMark, float aStrokeWidth,
+                                 const SVGMark& aMark, float aStrokeWidth,
                                  imgDrawingParams& aImgParams) {
   // If the flag is set when we get here, it means this marker frame
   // has already been used painting the current mark, and the document
   // has a marker reference loop.
   if (mInUse) {
     return;
   }
 
@@ -131,18 +131,17 @@ void nsSVGMarkerFrame::PaintMark(gfxCont
   SVGFrame->NotifySVGChanged(nsSVGDisplayableFrame::TRANSFORM_CHANGED);
   nsSVGUtils::PaintFrameWithEffects(kid, aContext, markTM, aImgParams);
 
   if (StyleDisplay()->IsScrollableOverflow()) aContext.Restore();
 }
 
 SVGBBox nsSVGMarkerFrame::GetMarkBBoxContribution(
     const Matrix& aToBBoxUserspace, uint32_t aFlags,
-    SVGGeometryFrame* aMarkedFrame, const nsSVGMark& aMark,
-    float aStrokeWidth) {
+    SVGGeometryFrame* aMarkedFrame, const SVGMark& aMark, float aStrokeWidth) {
   SVGBBox bbox;
 
   // If the flag is set when we get here, it means this marker frame
   // has already been used in calculating the current mark bbox, and
   // the document has a marker reference loop.
   if (mInUse) {
     return bbox;
   }
--- a/layout/svg/nsSVGMarkerFrame.h
+++ b/layout/svg/nsSVGMarkerFrame.h
@@ -14,25 +14,28 @@
 #include "nsLiteralString.h"
 #include "nsQueryFrame.h"
 #include "nsSVGContainerFrame.h"
 #include "nsSVGUtils.h"
 
 class gfxContext;
 
 namespace mozilla {
+
 class SVGGeometryFrame;
+
+struct SVGMark;
+
 namespace dom {
 class SVGViewportElement;
 }  // namespace dom
 }  // namespace mozilla
 
-struct nsSVGMark;
-
 class nsSVGMarkerFrame final : public nsSVGContainerFrame {
+  typedef mozilla::SVGMark SVGMark;
   typedef mozilla::image::imgDrawingParams imgDrawingParams;
 
   friend class nsSVGMarkerAnonChildFrame;
   friend nsContainerFrame* NS_NewSVGMarkerFrame(nsIPresShell* aPresShell,
                                                 ComputedStyle* aStyle);
 
  protected:
   explicit nsSVGMarkerFrame(ComputedStyle* aStyle)
@@ -70,24 +73,23 @@ class nsSVGMarkerFrame final : public ns
         PrincipalChildList().FirstChild() &&
             PrincipalChildList().FirstChild()->IsSVGMarkerAnonChildFrame(),
         "Where is our anonymous child?");
     return PrincipalChildList().FirstChild()->GetContentInsertionFrame();
   }
 
   // nsSVGMarkerFrame methods:
   void PaintMark(gfxContext& aContext, const gfxMatrix& aToMarkedFrameUserSpace,
-                 mozilla::SVGGeometryFrame* aMarkedFrame,
-                 const nsSVGMark& aMark, float aStrokeWidth,
-                 imgDrawingParams& aImgParams);
+                 mozilla::SVGGeometryFrame* aMarkedFrame, const SVGMark& aMark,
+                 float aStrokeWidth, imgDrawingParams& aImgParams);
 
   SVGBBox GetMarkBBoxContribution(const Matrix& aToBBoxUserspace,
                                   uint32_t aFlags,
                                   mozilla::SVGGeometryFrame* aMarkedFrame,
-                                  const nsSVGMark& aMark, float aStrokeWidth);
+                                  const SVGMark& aMark, float aStrokeWidth);
 
   // Return our anonymous box child.
   void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) override;
 
  private:
   // stuff needed for callback
   mozilla::SVGGeometryFrame* mMarkedFrame;
   Matrix mMarkerTM;
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -239,16 +239,31 @@ VARCACHE_PREF(
 )
 
 VARCACHE_PREF(
   "dom.performance.enable_scheduler_timing",
   dom_performance_enable_scheduler_timing,
   RelaxedAtomicBool, true
 )
 
+// Should we defer timeouts and intervals while loading a page.  Released
+// on Idle or when the page is loaded.
+VARCACHE_PREF(
+  "dom.timeout.defer_during_load",
+  dom_timeout_defer_during_load,
+  bool, true
+)
+
+// Maximum deferral time for setTimeout/Interval in milliseconds
+VARCACHE_PREF(
+  "dom.timeout.max_idle_defer_ms",
+  dom_timeout_max_idle_defer_ms,
+  uint32_t, 10*1000
+)
+
 VARCACHE_PREF(
   "dom.performance.children_results_ipc_timeout",
   dom_performance_children_results_ipc_timeout,
   uint32_t, 1000
 )
 
 // If true. then the service worker interception and the ServiceWorkerManager
 // will live in the parent process.  This only takes effect on browser start.
--- a/testing/talos/talos/pageloader/chrome/lh_hero.js
+++ b/testing/talos/talos/pageloader/chrome/lh_hero.js
@@ -1,14 +1,14 @@
 /* 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/. */
 
 
-function _contentHeroHandler() {
+function _contentHeroHandler(isload) {
   var obs = null;
   var el = content.window.document.querySelector("[elementtiming]");
   if (el) {
     function callback(entries, observer) {
       entries.forEach(entry => {
         sendAsyncMessage("PageLoader:LoadEvent",
                          {"time": Date.now(), // eslint-disable-line
                           "name": "tphero"});
@@ -18,17 +18,23 @@ function _contentHeroHandler() {
     // we want the element 100% visible on the viewport
     var options = {root: null, rootMargin: "0px", threshold: [1]};
     try {
       obs = new content.window.IntersectionObserver(callback, options);
       obs.observe(el);
     } catch (err) {
       sendAsyncMessage("PageLoader:Error", {"msg": err.message});
     }
+  } else if (isload) {
+    // If the hero element is added from a settimeout handler, it might not run before 'load'
+    setTimeout(function() { _contentHeroHandler(false); }, 5000);
   } else {
-      var err = "Could not find a tag with an elmenttiming attr on the page";
-      sendAsyncMessage("PageLoader:Error", {"msg": err});
+    var err = "Could not find a tag with an elmenttiming attr on the page";
+    sendAsyncMessage("PageLoader:Error", {"msg": err});
   }
   return obs;
 }
 
+function _contentHeroLoadHandler() {
+  _contentHeroHandler(true);
+}
 
-addEventListener("load", contentLoadHandlerCallback(_contentHeroHandler), true); // eslint-disable-line no-undef
+addEventListener("load", contentLoadHandlerCallback(_contentHeroLoadHandler), true); // eslint-disable-line no-undef
--- 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/toolkit/components/extensions/test/mochitest/mochitest-common.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest-common.ini
@@ -51,16 +51,17 @@ support-files =
   !/dom/tests/mochitest/geolocation/network_geolocation.sjs
   !/toolkit/components/passwordmgr/test/authenticate.sjs
   file_redirect_data_uri.html
 prefs =
   security.mixed_content.upgrade_display_content=false
   browser.chrome.guess_favicon=true
 
 [test_ext_async_clipboard.html]
+skip-if = (toolkit == 'android') # near-permafail after landing bug 1270059: Bug 1523131
 [test_ext_background_canvas.html]
 [test_ext_background_page.html]
 skip-if = (toolkit == 'android') # android doesn't have devtools
 [test_ext_canvas_resistFingerprinting.html]
 [test_ext_clipboard.html]
 [test_ext_clipboard_image.html]
 skip-if = headless # disabled test case with_permission_allow_copy, see inline comment. Headless: Bug 1405872
 [test_ext_contentscript_about_blank.html]
--- 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
@@ -10,20 +10,21 @@
 #include "mozilla/AlreadyAddRefed.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Mutex.h"
 
 class nsIRunnable;
 
 namespace mozilla {
 
-enum class EventPriority {
+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.
@@ -41,25 +42,25 @@ enum class EventPriority {
 // implemented through nsIRunnablePriority.
 class AbstractEventQueue {
  public:
   // Add an event to the end of the queue. Implementors are free to use
   // aPriority however they wish.  If the runnable supports nsIRunnablePriority
   // and the implementing class supports prioritization, aPriority represents
   // the result of calling nsIRunnablePriority::GetPriority().
   virtual void PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
-                        EventPriority aPriority,
+                        EventQueuePriority 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. This should return
   // null if the queue is non-empty but the event in front is not ready to run.
   virtual already_AddRefed<nsIRunnable> GetEvent(
-      EventPriority* aPriority, const MutexAutoLock& aProofOfLock) = 0;
+      EventQueuePriority* aPriority, const MutexAutoLock& aProofOfLock) = 0;
 
   // Returns true if the queue is empty. Implies !HasReadyEvent().
   virtual bool IsEmpty(const MutexAutoLock& aProofOfLock) = 0;
 
   // Returns true if the queue is non-empty and if the event in front is ready
   // to run. Implies !IsEmpty(). This should return true iff GetEvent returns a
   // non-null value.
   virtual bool HasReadyEvent(const MutexAutoLock& aProofOfLock) = 0;
--- a/xpcom/threads/EventQueue.cpp
+++ b/xpcom/threads/EventQueue.cpp
@@ -4,33 +4,33 @@
  * 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"
 #include "nsIRunnable.h"
 
 using namespace mozilla;
 
-EventQueue::EventQueue(EventPriority aPriority) {}
+EventQueue::EventQueue(EventQueuePriority aPriority) {}
 
 void EventQueue::PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
-                          EventPriority aPriority,
+                          EventQueuePriority aPriority,
                           const MutexAutoLock& aProofOfLock) {
   nsCOMPtr<nsIRunnable> event(aEvent);
   mQueue.Push(std::move(event));
 }
 
 already_AddRefed<nsIRunnable> EventQueue::GetEvent(
-    EventPriority* aPriority, const MutexAutoLock& aProofOfLock) {
+    EventQueuePriority* aPriority, const MutexAutoLock& aProofOfLock) {
   if (mQueue.IsEmpty()) {
     return nullptr;
   }
 
   if (aPriority) {
-    *aPriority = EventPriority::Normal;
+    *aPriority = EventQueuePriority::Normal;
   }
 
   nsCOMPtr<nsIRunnable> result = mQueue.Pop();
   return result.forget();
 }
 
 bool EventQueue::IsEmpty(const MutexAutoLock& aProofOfLock) {
   return mQueue.IsEmpty();
--- a/xpcom/threads/EventQueue.h
+++ b/xpcom/threads/EventQueue.h
@@ -15,22 +15,23 @@ class nsIRunnable;
 
 namespace mozilla {
 
 class EventQueue final : public AbstractEventQueue {
  public:
   static const bool SupportsPrioritization = false;
 
   EventQueue() {}
-  explicit EventQueue(EventPriority aPriority);
+  explicit EventQueue(EventQueuePriority aPriority);
 
-  void PutEvent(already_AddRefed<nsIRunnable>&& aEvent, EventPriority aPriority,
+  void PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
+                EventQueuePriority aPriority,
                 const MutexAutoLock& aProofOfLock) final;
   already_AddRefed<nsIRunnable> GetEvent(
-      EventPriority* aPriority, const MutexAutoLock& aProofOfLock) final;
+      EventQueuePriority* aPriority, const MutexAutoLock& aProofOfLock) final;
 
   bool IsEmpty(const MutexAutoLock& aProofOfLock) final;
   bool HasReadyEvent(const MutexAutoLock& aProofOfLock) final;
   bool HasPendingHighPriorityEvents(const MutexAutoLock& aProofOfLock) final {
     // EventQueue doesn't support any prioritization.
     return false;
   }
 
--- 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
@@ -18,20 +18,22 @@ namespace mozilla {
 
 template <typename SynchronizedQueueT, typename InnerQueueT>
 inline already_AddRefed<nsThread> CreateMainThread(
     nsIIdlePeriod* aIdlePeriod,
     SynchronizedQueueT** aSynchronizedQueue = nullptr) {
   using MainThreadQueueT = PrioritizedEventQueue<InnerQueueT>;
 
   auto queue = MakeUnique<MainThreadQueueT>(
-      MakeUnique<InnerQueueT>(EventPriority::High),
-      MakeUnique<InnerQueueT>(EventPriority::Input),
-      MakeUnique<InnerQueueT>(EventPriority::Normal),
-      MakeUnique<InnerQueueT>(EventPriority::Idle), do_AddRef(aIdlePeriod));
+      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));
 
   prioritized->SetMutexRef(synchronizedQueue->MutexRef());
 
--- a/xpcom/threads/PrioritizedEventQueue.cpp
+++ b/xpcom/threads/PrioritizedEventQueue.cpp
@@ -11,54 +11,61 @@
 #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(
-    already_AddRefed<nsIRunnable>&& aEvent, EventPriority aPriority,
+    already_AddRefed<nsIRunnable>&& aEvent, EventQueuePriority aPriority,
     const MutexAutoLock& aProofOfLock) {
   // Double check the priority with a QI.
   RefPtr<nsIRunnable> event(aEvent);
-  EventPriority priority = aPriority;
+  EventQueuePriority priority = aPriority;
 
-  if (priority == EventPriority::Input && mInputQueueState == STATE_DISABLED) {
-    priority = EventPriority::Normal;
+  if (priority == EventQueuePriority::Input &&
+      mInputQueueState == STATE_DISABLED) {
+    priority = EventQueuePriority::Normal;
   }
 
   switch (priority) {
-    case EventPriority::High:
+    case EventQueuePriority::High:
       mHighQueue->PutEvent(event.forget(), priority, aProofOfLock);
       break;
-    case EventPriority::Input:
+    case EventQueuePriority::Input:
       mInputQueue->PutEvent(event.forget(), priority, aProofOfLock);
       break;
-    case EventPriority::Normal:
+    case EventQueuePriority::Normal:
       mNormalQueue->PutEvent(event.forget(), priority, aProofOfLock);
       break;
-    case EventPriority::Idle:
+    case EventQueuePriority::DeferredTimers:
+      mDeferredTimersQueue->PutEvent(event.forget(), priority, aProofOfLock);
+      break;
+    case EventQueuePriority::Idle:
       mIdleQueue->PutEvent(event.forget(), priority, aProofOfLock);
       break;
-    case EventPriority::Count:
-      MOZ_CRASH("EventPriority::Count isn't a valid priority");
+    case EventQueuePriority::Count:
+      MOZ_CRASH("EventQueuePriority::Count isn't a valid priority");
       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
@@ -100,20 +107,18 @@ TimeStamp PrioritizedEventQueue<InnerQue
     // 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>
-EventPriority PrioritizedEventQueue<InnerQueueT>::SelectQueue(
+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);
   }
 
@@ -125,111 +130,122 @@ EventPriority PrioritizedEventQueue<Inne
   // 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.
-  EventPriority queue;
+  EventQueuePriority queue;
+  bool highPending = !mHighQueue->IsEmpty(aProofOfLock);
 
   if (mProcessHighPriorityQueue) {
-    queue = EventPriority::High;
+    queue = EventQueuePriority::High;
   } else if (inputCount > 0 && (mInputQueueState == STATE_FLUSHING ||
                                 (mInputQueueState == STATE_ENABLED &&
                                  !mInputHandlingStartTime.IsNull() &&
                                  TimeStamp::Now() > mInputHandlingStartTime))) {
-    queue = EventPriority::Input;
-  } else if (normalPending) {
+    queue = EventQueuePriority::Input;
+  } else if (!mNormalQueue->IsEmpty(aProofOfLock)) {
     MOZ_ASSERT(mInputQueueState != STATE_FLUSHING,
-               "Shouldn't consume normal event when flusing input events");
-    queue = EventPriority::Normal;
+               "Shouldn't consume normal event when flushing input events");
+    queue = EventQueuePriority::Normal;
   } else if (highPending) {
-    queue = EventPriority::High;
+    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 = EventPriority::Input;
+    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 = EventPriority::Idle;
+    queue = EventQueuePriority::Idle;
   }
 
   MOZ_ASSERT_IF(
-      queue == EventPriority::Input,
+      queue == EventQueuePriority::Input,
       mInputQueueState != STATE_DISABLED && mInputQueueState != STATE_SUSPEND);
 
   if (aUpdateState) {
     mProcessHighPriorityQueue = highPending;
   }
 
   return queue;
 }
 
 template <class InnerQueueT>
 already_AddRefed<nsIRunnable> PrioritizedEventQueue<InnerQueueT>::GetEvent(
-    EventPriority* aPriority, const MutexAutoLock& aProofOfLock) {
+    EventQueuePriority* aPriority, const MutexAutoLock& aProofOfLock) {
   auto guard =
       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
 
-  EventPriority queue = SelectQueue(true, aProofOfLock);
+  EventQueuePriority queue = SelectQueue(true, aProofOfLock);
 
   if (aPriority) {
     *aPriority = queue;
   }
 
-  if (queue == EventPriority::High) {
+  if (queue == EventQueuePriority::High) {
     nsCOMPtr<nsIRunnable> event = mHighQueue->GetEvent(aPriority, aProofOfLock);
     MOZ_ASSERT(event);
     mInputHandlingStartTime = TimeStamp();
     mProcessHighPriorityQueue = false;
     return event.forget();
   }
 
-  if (queue == EventPriority::Input) {
+  if (queue == EventQueuePriority::Input) {
     nsCOMPtr<nsIRunnable> event =
         mInputQueue->GetEvent(aPriority, aProofOfLock);
     MOZ_ASSERT(event);
     return event.forget();
   }
 
-  if (queue == EventPriority::Normal) {
+  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 == EventPriority::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
@@ -244,44 +260,48 @@ 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;
 
-  EventPriority queue = SelectQueue(false, aProofOfLock);
+  EventQueuePriority queue = SelectQueue(false, aProofOfLock);
 
-  if (queue == EventPriority::High) {
+  if (queue == EventQueuePriority::High) {
     return mHighQueue->HasReadyEvent(aProofOfLock);
-  } else if (queue == EventPriority::Input) {
+  } else if (queue == EventQueuePriority::Input) {
     return mInputQueue->HasReadyEvent(aProofOfLock);
-  } else if (queue == EventPriority::Normal) {
+  } else if (queue == EventQueuePriority::Normal) {
     return mNormalQueue->HasReadyEvent(aProofOfLock);
   }
 
-  MOZ_ASSERT(queue == EventPriority::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
@@ -13,19 +13,19 @@
 #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.
+// This AbstractEventQueue implementation has one queue for each
+// EventQueuePriority. 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:
@@ -38,23 +38,25 @@ 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, EventPriority aPriority,
+  void PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
+                EventQueuePriority aPriority,
                 const MutexAutoLock& aProofOfLock) final;
   already_AddRefed<nsIRunnable> GetEvent(
-      EventPriority* aPriority, const MutexAutoLock& aProofOfLock) final;
+      EventQueuePriority* aPriority, const MutexAutoLock& aProofOfLock) final;
 
   bool IsEmpty(const MutexAutoLock& aProofOfLock) final;
   size_t Count(const MutexAutoLock& aProofOfLock) const final;
   bool HasReadyEvent(const MutexAutoLock& aProofOfLock) final;
   bool HasPendingHighPriorityEvents(const MutexAutoLock& aProofOfLock) 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
@@ -78,35 +80,37 @@ 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;
   }
 
  private:
-  EventPriority SelectQueue(bool aUpdateState,
-                            const MutexAutoLock& aProofOfLock);
+  EventQueuePriority SelectQueue(bool aUpdateState,
+                                 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/SchedulerGroup.h
+++ b/xpcom/threads/SchedulerGroup.h
@@ -168,17 +168,17 @@ class SchedulerGroup : public LinkedList
     uintptr_t mEpochNumber;
 
     EpochQueueEntry(already_AddRefed<nsIRunnable> aRunnable, uintptr_t aEpoch)
         : mRunnable(aRunnable), mEpochNumber(aEpoch) {}
   };
 
   using RunnableEpochQueue = Queue<EpochQueueEntry, 32>;
 
-  RunnableEpochQueue& GetQueue(mozilla::EventPriority aPriority) {
+  RunnableEpochQueue& GetQueue(mozilla::EventQueuePriority aPriority) {
     return mEventQueues[size_t(aPriority)];
   }
 
  protected:
   nsresult DispatchWithDocGroup(TaskCategory aCategory,
                                 already_AddRefed<nsIRunnable>&& aRunnable,
                                 dom::DocGroup* aDocGroup);
 
@@ -213,17 +213,17 @@ class SchedulerGroup : public LinkedList
   bool mIsRunning;
 
   // Number of events that are currently enqueued for this SchedulerGroup
   // (across all queues).
   size_t mEventCount = 0;
 
   nsCOMPtr<nsISerialEventTarget> mEventTargets[size_t(TaskCategory::Count)];
   RefPtr<AbstractThread> mAbstractThreads[size_t(TaskCategory::Count)];
-  RunnableEpochQueue mEventQueues[size_t(mozilla::EventPriority::Count)];
+  RunnableEpochQueue mEventQueues[size_t(mozilla::EventQueuePriority::Count)];
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(SchedulerGroup::Runnable,
                               NS_SCHEDULERGROUPRUNNABLE_IID);
 
 }  // namespace mozilla
 
 #endif  // mozilla_SchedulerGroup_h
--- a/xpcom/threads/SynchronizedEventQueue.h
+++ b/xpcom/threads/SynchronizedEventQueue.h
@@ -31,17 +31,17 @@ namespace mozilla {
 // 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;
+                        EventQueuePriority aPriority) = 0;
 
   // After this method is called, no more events can be posted.
   virtual void Disconnect(const MutexAutoLock& aProofOfLock) = 0;
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
@@ -49,18 +49,18 @@ class ThreadTargetSink {
       mozilla::MallocSizeOf aMallocSizeOf) const = 0;
 
  protected:
   virtual ~ThreadTargetSink() {}
 };
 
 class SynchronizedEventQueue : public ThreadTargetSink {
  public:
-  virtual already_AddRefed<nsIRunnable> GetEvent(bool aMayWait,
-                                                 EventPriority* aPriority) = 0;
+  virtual already_AddRefed<nsIRunnable> GetEvent(
+      bool aMayWait, EventQueuePriority* aPriority) = 0;
   virtual bool HasPendingEvent() = 0;
 
   virtual bool HasPendingHighPriorityEvents() = 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;
--- a/xpcom/threads/ThreadEventQueue.cpp
+++ b/xpcom/threads/ThreadEventQueue.cpp
@@ -18,17 +18,17 @@ 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 {
+                EventQueuePriority aPriority) final {
     return mOwner->PutEventInternal(std::move(aEvent), aPriority, this);
   }
 
   void Disconnect(const MutexAutoLock& aProofOfLock) final { mQueue = nullptr; }
 
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
     if (mQueue) {
       return mQueue->SizeOfIncludingThis(aMallocSizeOf);
@@ -56,42 +56,42 @@ ThreadEventQueue<InnerQueueT>::ThreadEve
 
 template <class InnerQueueT>
 ThreadEventQueue<InnerQueueT>::~ThreadEventQueue() {
   MOZ_ASSERT(mNestedQueues.IsEmpty());
 }
 
 template <class InnerQueueT>
 bool ThreadEventQueue<InnerQueueT>::PutEvent(
-    already_AddRefed<nsIRunnable>&& aEvent, EventPriority aPriority) {
+    already_AddRefed<nsIRunnable>&& aEvent, EventQueuePriority aPriority) {
   return PutEventInternal(std::move(aEvent), aPriority, nullptr);
 }
 
 template <class InnerQueueT>
 bool ThreadEventQueue<InnerQueueT>::PutEventInternal(
-    already_AddRefed<nsIRunnable>&& aEvent, EventPriority aPriority,
+    already_AddRefed<nsIRunnable>&& aEvent, EventQueuePriority 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(std::move(aEvent));
   nsCOMPtr<nsIThreadObserver> obs;
 
   {
     // Check if the runnable wants to override the passed-in priority.
     // Do this outside the lock, so runnables implemented in JS can QI
     // (and possibly GC) outside of the lock.
     if (InnerQueueT::SupportsPrioritization) {
       auto* e = event.get();  // can't do_QueryInterface on LeakRefPtr.
       if (nsCOMPtr<nsIRunnablePriority> runnablePrio = do_QueryInterface(e)) {
         uint32_t prio = nsIRunnablePriority::PRIORITY_NORMAL;
         runnablePrio->GetPriority(&prio);
         if (prio == nsIRunnablePriority::PRIORITY_HIGH) {
-          aPriority = EventPriority::High;
+          aPriority = EventQueuePriority::High;
         } else if (prio == nsIRunnablePriority::PRIORITY_INPUT) {
-          aPriority = EventPriority::Input;
+          aPriority = EventQueuePriority::Input;
         }
       }
     }
 
     MutexAutoLock lock(mLock);
 
     if (mEventsAreDoomed) {
       return false;
@@ -120,17 +120,17 @@ bool ThreadEventQueue<InnerQueueT>::PutE
     obs->OnDispatchedEvent();
   }
 
   return true;
 }
 
 template <class InnerQueueT>
 already_AddRefed<nsIRunnable> ThreadEventQueue<InnerQueueT>::GetEvent(
-    bool aMayWait, EventPriority* aPriority) {
+    bool aMayWait, EventQueuePriority* 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
@@ -239,17 +239,17 @@ void ThreadEventQueue<InnerQueueT>::PopE
   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;
+  EventQueuePriority prio;
   while ((event = item.mQueue->GetEvent(&prio, lock))) {
     prevQueue->PutEvent(event.forget(), prio, lock);
   }
 
   mNestedQueues.RemoveLastElement();
 }
 
 template <class InnerQueueT>
--- a/xpcom/threads/ThreadEventQueue.h
+++ b/xpcom/threads/ThreadEventQueue.h
@@ -32,20 +32,20 @@ class ThreadEventTarget;
 // 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;
+                EventQueuePriority aPriority) final;
 
   already_AddRefed<nsIRunnable> GetEvent(bool aMayWait,
-                                         EventPriority* aPriority) final;
+                                         EventQueuePriority* aPriority) final;
   bool HasPendingEvent() final;
   bool HasPendingHighPriorityEvents() final;
 
   bool ShutdownIfNoPendingEvents() final;
 
   void Disconnect(const MutexAutoLock& aProofOfLock) final {}
 
   void EnableInputEventPrioritization() final;
@@ -85,17 +85,17 @@ class ThreadEventQueue final : public Sy
       mozilla::MallocSizeOf aMallocSizeOf) const override;
 
  private:
   class NestedSink;
 
   virtual ~ThreadEventQueue();
 
   bool PutEventInternal(already_AddRefed<nsIRunnable>&& aEvent,
-                        EventPriority aPriority, NestedSink* aQueue);
+                        EventQueuePriority aPriority, NestedSink* aQueue);
 
   UniquePtr<InnerQueueT> mBaseQueue;
 
   struct NestedQueueItem {
     UniquePtr<EventQueue> mQueue;
     RefPtr<ThreadEventTarget> mEventTarget;
 
     NestedQueueItem(UniquePtr<EventQueue> aQueue,
--- a/xpcom/threads/ThreadEventTarget.cpp
+++ b/xpcom/threads/ThreadEventTarget.cpp
@@ -133,17 +133,17 @@ ThreadEventTarget::Dispatch(already_AddR
 
     // 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
+                                   EventQueuePriority::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;
     }
 
@@ -151,17 +151,17 @@ ThreadEventTarget::Dispatch(already_AddR
     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)) {
+  if (!mSink->PutEvent(event.take(), EventQueuePriority::Normal)) {
     return NS_ERROR_UNEXPECTED;
   }
   // Delay to encourage the receiving task to run before we do work.
   DelayForChaosMode(ChaosFeature::TaskDispatching, 1000);
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/xpcom/threads/ThrottledEventQueue.cpp
+++ b/xpcom/threads/ThrottledEventQueue.cpp
@@ -309,17 +309,17 @@ class ThrottledEventQueue::Inner final :
       // fallible, so do it first. Our lock will prevent the executor from
       // accessing the event queue before we add the event below.
       nsresult rv = EnsureExecutor(lock);
       if (NS_FAILED(rv)) return rv;
     }
 
     // Only add the event to the underlying queue if are able to
     // dispatch to our base target.
-    mEventQueue.PutEvent(std::move(aEvent), EventPriority::Normal, lock);
+    mEventQueue.PutEvent(std::move(aEvent), EventQueuePriority::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.
     return NS_ERROR_NOT_IMPLEMENTED;
--- 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
@@ -659,17 +659,17 @@ nsresult nsThread::Init(const nsACString
     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.
   {
     mEvents->PutEvent(do_AddRef(startup),
-                      EventPriority::Normal);  // retain a reference
+                      EventQueuePriority::Normal);  // retain a reference
   }
 
   // Wait for thread to call ThreadManager::SetupCurrentThread, which completes
   // initialization of ThreadFunc.
   startup->Wait();
   return NS_OK;
 }
 
@@ -803,17 +803,17 @@ nsThreadShutdownContext* nsThread::Shutd
   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?
-  mEvents->PutEvent(event.forget(), EventPriority::Normal);
+  mEvents->PutEvent(event.forget(), EventQueuePriority::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 nsThread::ShutdownComplete(NotNull<nsThreadShutdownContext*> aContext) {
@@ -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(), EventPriority::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;
 }
@@ -961,31 +962,31 @@ void canary_alarm_handler(int signum) {
         obs_ = iter_.GetNext();                                             \
         obs_->func_ params_;                                                \
       }                                                                     \
     }                                                                       \
   } while (0)
 
 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
 static bool GetLabeledRunnableName(nsIRunnable* aEvent, nsACString& aName,
-                                   EventPriority aPriority) {
+                                   EventQueuePriority aPriority) {
   bool labeled = false;
   if (RefPtr<SchedulerGroup::Runnable> groupRunnable = do_QueryObject(aEvent)) {
     labeled = true;
     MOZ_ALWAYS_TRUE(NS_SUCCEEDED(groupRunnable->GetName(aName)));
   } else if (nsCOMPtr<nsINamed> named = do_QueryInterface(aEvent)) {
     MOZ_ALWAYS_TRUE(NS_SUCCEEDED(named->GetName(aName)));
   } else {
     aName.AssignLiteral("non-nsINamed runnable");
   }
   if (aName.IsEmpty()) {
     aName.AssignLiteral("anonymous runnable");
   }
 
-  if (!labeled && aPriority > EventPriority::Input) {
+  if (!labeled && aPriority > EventQueuePriority::Input) {
     aName.AppendLiteral("(unlabeled)");
   }
 
   return labeled;
 }
 #endif
 
 mozilla::PerformanceCounter* nsThread::GetPerformanceCounter(
@@ -1079,17 +1080,17 @@ nsThread::ProcessNextEvent(bool aMayWait
   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.
-    EventPriority priority;
+    EventQueuePriority priority;
     nsCOMPtr<nsIRunnable> event = mEvents->GetEvent(reallyWait, &priority);
 
     *aResult = (event.get() != nullptr);
 
     if (event) {
       LOG(("THRD(%p) running [%p]\n", this, event.get()));
 
       // Delay event processing to encourage whoever dispatched this event
@@ -1134,17 +1135,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 == EventPriority::Input) {
+      if (priority == EventQueuePriority::Input) {
         timeDurationHelper.emplace();
       }
 
       // The event starts to run, storing the timestamp.
       bool recursiveEvent = mNestedEventLoopDepth > mCurrentEventLoopDepth;
       mCurrentEventLoopDepth = mNestedEventLoopDepth;
       if (IsMainThread() && !recursiveEvent) {
         mCurrentEventStart = mozilla::TimeStamp::Now();
@@ -1161,24 +1162,25 @@ nsThread::ProcessNextEvent(bool aMayWait
 
       mozilla::TimeDuration duration;
       // Remember the last 50ms+ task on mainthread for Long Task.
       if (IsMainThread() && !recursiveEvent) {
         TimeStamp now = TimeStamp::Now();
         duration = now - mCurrentEventStart;
         if (duration.ToMilliseconds() > LONGTASK_BUSY_WINDOW_MS) {
           // Idle events (gc...) don't *really* count here
-          if (priority != EventPriority::Idle) {
+          if (priority != EventQueuePriority::Idle) {
             mLastLongNonIdleTaskEnd = now;
           }
           mLastLongTaskEnd = now;
 #ifdef MOZ_GECKO_PROFILER
           if (profiler_thread_is_being_profiled()) {
             profiler_add_marker(
-                (priority != EventPriority::Idle) ? "LongTask" : "LongIdleTask",
+                (priority != EventQueuePriority::Idle) ? "LongTask"
+                                                       : "LongIdleTask",
                 js::ProfilingStackFrame::Category::OTHER,
                 MakeUnique<LongTaskMarkerPayload>(mCurrentEventStart, now));
           }
 #endif
         }
       }
 
       // End of execution, we can send the duration for the group
--- 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/nsThreadPool.cpp
+++ b/xpcom/threads/nsThreadPool.cpp
@@ -86,17 +86,17 @@ nsresult nsThreadPool::PutEvent(already_
     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(std::move(aEvent), EventPriority::Normal, lock);
+    mEvents.PutEvent(std::move(aEvent), EventQueuePriority::Normal, lock);
     mEventsAvailable.Notify();
     stackSize = mStackSize;
   }
 
   auto delay = MakeScopeExit([&]() {
     // Delay to encourage the receiving task to run before we do work.
     DelayForChaosMode(ChaosFeature::TaskDispatching, 1000);
   });
--- 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.
  *
--- a/xpcom/threads/nsTimerImpl.cpp
+++ b/xpcom/threads/nsTimerImpl.cpp
@@ -291,17 +291,17 @@ nsresult nsTimerImpl::InitCommon(uint32_
   return InitCommon(TimeDuration::FromMilliseconds(aDelayMS), aType,
                     std::move(aNewCallback));
 }
 
 nsresult nsTimerImpl::InitCommon(const TimeDuration& aDelay, uint32_t aType,
                                  Callback&& newCallback) {
   mMutex.AssertCurrentThreadOwns();
 
-  if (NS_WARN_IF(!gThread)) {
+  if (!gThread) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   if (!mEventTarget) {
     NS_ERROR("mEventTarget is NULL");
     return NS_ERROR_NOT_INITIALIZED;
   }