Merge inbound to mozilla-central. a=merge
authorBrindusan Cristian <cbrindusan@mozilla.com>
Sun, 27 Jan 2019 11:35:28 +0200
changeset 499331 2013099b2c13997ffe1362e6e1e9cf09cd4cd4ac
parent 499325 11f88edba10fcab0a5e508b1fba9b58203a51fa4 (current diff)
parent 499330 ed3d84fc13910adbc9bc27ca3d59e3ee03b9eaf6 (diff)
child 499332 d743672a092cac271f2a0ca0a7976161fb2c19f7
child 499335 579d66b2e57dcaf66fab49df4be13ceb84ca150b
push idunknown
push userunknown
push dateunknown
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;
   }