Bug 1589561. Factor out idle handling from PrioritizedEventQueue. r=smaug
☠☠ backed out by 21b88cef47e1 ☠ ☠
authorBoris Zbarsky <bzbarsky@mit.edu>
Fri, 18 Oct 2019 17:28:50 +0000
changeset 498259 798bab343a0aa9e7940e5a449e6555096ecdc4ac
parent 498258 b5f7dccff453e27c686b89f4d94852d6ec37d1fa
child 498260 7c59ec30b84c4f66c50a82613295c60e92a4fb0c
push id98346
push userbzbarsky@mozilla.com
push dateFri, 18 Oct 2019 17:29:25 +0000
treeherderautoland@798bab343a0a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1589561
milestone71.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1589561. Factor out idle handling from PrioritizedEventQueue. r=smaug We could try to move the EnforcePendingTaskGuarantee() bit into PeekIdleDeadline, but then we'd need to check HasReadyEvent() on mDeferredTimersQueue and mIdleQueue before we unlock the mutex and PeekIdleDeadline, and it's not clear that that state cannot change once the mutex is unlocked... The EnsureIsActive() call at the end of GetIdleDeadlineInternal in the !aIsPeek case only makes sense if there are in fact idle tasks available to run when GetDeadlineForIdleTask is called, because otherwise it would incorrectly set us active when we are not running any tasks. Differential Revision: https://phabricator.services.mozilla.com/D49696
ipc/glue/IdleSchedulerChild.cpp
ipc/glue/IdleSchedulerChild.h
xpcom/threads/IdlePeriodState.cpp
xpcom/threads/IdlePeriodState.h
xpcom/threads/PrioritizedEventQueue.cpp
xpcom/threads/PrioritizedEventQueue.h
xpcom/threads/moz.build
--- a/ipc/glue/IdleSchedulerChild.cpp
+++ b/ipc/glue/IdleSchedulerChild.cpp
@@ -2,54 +2,54 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/ipc/IdleSchedulerChild.h"
 #include "mozilla/ipc/IdleSchedulerParent.h"
 #include "mozilla/Atomics.h"
-#include "mozilla/PrioritizedEventQueue.h"
+#include "mozilla/IdlePeriodState.h"
 #include "BackgroundChild.h"
 
 namespace mozilla {
 namespace ipc {
 
 static IdleSchedulerChild* sMainThreadIdleScheduler = nullptr;
 
 IdleSchedulerChild::~IdleSchedulerChild() {
   if (sMainThreadIdleScheduler == this) {
     sMainThreadIdleScheduler = nullptr;
   }
-  MOZ_ASSERT(!mEventQueue);
+  MOZ_ASSERT(!mIdlePeriodState);
 }
 
-void IdleSchedulerChild::Init(PrioritizedEventQueue* aEventQueue) {
-  mEventQueue = aEventQueue;
+void IdleSchedulerChild::Init(IdlePeriodState* aIdlePeriodState) {
+  mIdlePeriodState = aIdlePeriodState;
 
   RefPtr<IdleSchedulerChild> scheduler = this;
   auto resolve =
       [&](Tuple<mozilla::Maybe<SharedMemoryHandle>, uint32_t>&& aResult) {
         if (Get<0>(aResult)) {
           mActiveCounter.SetHandle(*Get<0>(aResult), false);
           mActiveCounter.Map(sizeof(int32_t));
           mChildId = Get<1>(aResult);
-          if (mChildId && mEventQueue && mEventQueue->IsActive()) {
+          if (mChildId && mIdlePeriodState && mIdlePeriodState->IsActive()) {
             SetActive();
           }
         }
       };
 
   auto reject = [&](ResponseRejectReason) {};
   SendInitForIdleUse(std::move(resolve), std::move(reject));
 }
 
 IPCResult IdleSchedulerChild::RecvIdleTime(uint64_t aId, TimeDuration aBudget) {
-  if (mEventQueue) {
-    mEventQueue->SetIdleToken(aId, aBudget);
+  if (mIdlePeriodState) {
+    mIdlePeriodState->SetIdleToken(aId, aBudget);
   }
   return IPC_OK();
 }
 
 void IdleSchedulerChild::SetActive() {
   if (mChildId && CanSend() && mActiveCounter.memory()) {
     ++(static_cast<Atomic<int32_t>*>(
         mActiveCounter.memory())[NS_IDLE_SCHEDULER_INDEX_OF_ACTIVITY_COUNTER]);
--- a/ipc/glue/IdleSchedulerChild.h
+++ b/ipc/glue/IdleSchedulerChild.h
@@ -9,50 +9,50 @@
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ipc/PIdleSchedulerChild.h"
 
 class nsIIdlePeriod;
 
 namespace mozilla {
-class PrioritizedEventQueue;
+class IdlePeriodState;
 
 namespace ipc {
 
 class BackgroundChildImpl;
 
 class IdleSchedulerChild final : public PIdleSchedulerChild {
  public:
   IdleSchedulerChild() = default;
 
   NS_INLINE_DECL_REFCOUNTING(IdleSchedulerChild)
 
   IPCResult RecvIdleTime(uint64_t aId, TimeDuration aBudget);
 
-  void Init(PrioritizedEventQueue* aEventQueue);
+  void Init(IdlePeriodState* aIdlePeriodState);
 
-  void Disconnect() { mEventQueue = nullptr; }
+  void Disconnect() { mIdlePeriodState = nullptr; }
 
   // See similar methods on PrioritizedEventQueue.
   void SetActive();
   // Returns true if activity state dropped below cpu count.
   bool SetPaused();
 
   static IdleSchedulerChild* GetMainThreadIdleScheduler();
 
  private:
   ~IdleSchedulerChild();
 
   friend class BackgroundChildImpl;
 
   // See IdleScheduleParent::sActiveChildCounter
   base::SharedMemory mActiveCounter;
 
-  PrioritizedEventQueue* mEventQueue = nullptr;
+  IdlePeriodState* mIdlePeriodState = nullptr;
 
   uint32_t mChildId = 0;
 };
 
 }  // namespace ipc
 }  // namespace mozilla
 
 #endif  // mozilla_ipc_IdleSchedulerChild_h__
copy from xpcom/threads/PrioritizedEventQueue.cpp
copy to xpcom/threads/IdlePeriodState.cpp
--- a/xpcom/threads/PrioritizedEventQueue.cpp
+++ b/xpcom/threads/IdlePeriodState.cpp
@@ -1,107 +1,127 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "PrioritizedEventQueue.h"
-#include "mozilla/EventQueue.h"
-#include "mozilla/ScopeExit.h"
+#include "mozilla/IdlePeriodState.h"
 #include "mozilla/StaticPrefs_idle_period.h"
-#include "mozilla/StaticPrefs_threads.h"
 #include "mozilla/ipc/IdleSchedulerChild.h"
+#include "nsIIdlePeriod.h"
 #include "nsThreadManager.h"
-#include "nsXPCOMPrivate.h"  // for gXPCOMThreadsShutDown
-#include "InputEventStatistics.h"
-
-using namespace mozilla;
+#include "nsThreadUtils.h"
+#include "nsXPCOM.h"
+#include "nsXULAppAPI.h"
 
 static uint64_t sIdleRequestCounter = 0;
 
-PrioritizedEventQueue::PrioritizedEventQueue(
-    already_AddRefed<nsIIdlePeriod> aIdlePeriod)
-    : mHighQueue(MakeUnique<EventQueue>(EventQueuePriority::High)),
-      mInputQueue(MakeUnique<EventQueue>(EventQueuePriority::Input)),
-      mMediumHighQueue(MakeUnique<EventQueue>(EventQueuePriority::MediumHigh)),
-      mNormalQueue(MakeUnique<EventQueue>(EventQueuePriority::Normal)),
-      mDeferredTimersQueue(
-          MakeUnique<EventQueue>(EventQueuePriority::DeferredTimers)),
-      mIdleQueue(MakeUnique<EventQueue>(EventQueuePriority::Idle)),
-      mIdlePeriod(aIdlePeriod) {}
+namespace mozilla {
 
-PrioritizedEventQueue::~PrioritizedEventQueue() {
+IdlePeriodState::IdlePeriodState(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod)
+    : mIdlePeriod(aIdlePeriod) {
+  MOZ_ASSERT(NS_IsMainThread(),
+             "Why are we touching idle state off the main thread?");
+}
+
+IdlePeriodState::~IdlePeriodState() {
+  MOZ_ASSERT(NS_IsMainThread(),
+             "Why are we touching idle state off the main thread?");
   if (mIdleScheduler) {
     mIdleScheduler->Disconnect();
   }
 }
 
-void PrioritizedEventQueue::PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
-                                     EventQueuePriority aPriority,
-                                     const MutexAutoLock& aProofOfLock) {
-  // Double check the priority with a QI.
-  RefPtr<nsIRunnable> event(aEvent);
-  EventQueuePriority priority = aPriority;
-
-  if (priority == EventQueuePriority::Input &&
-      mInputQueueState == STATE_DISABLED) {
-    priority = EventQueuePriority::Normal;
-  } else if (priority == EventQueuePriority::MediumHigh &&
-             !StaticPrefs::threads_medium_high_event_queue_enabled()) {
-    priority = EventQueuePriority::Normal;
+size_t IdlePeriodState::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+  size_t n = 0;
+  if (mIdlePeriod) {
+    n += aMallocSizeOf(mIdlePeriod);
   }
 
-  switch (priority) {
-    case EventQueuePriority::High:
-      mHighQueue->PutEvent(event.forget(), priority, aProofOfLock);
-      break;
-    case EventQueuePriority::Input:
-      mInputQueue->PutEvent(event.forget(), priority, aProofOfLock);
-      break;
-    case EventQueuePriority::MediumHigh:
-      mMediumHighQueue->PutEvent(event.forget(), priority, aProofOfLock);
-      break;
-    case EventQueuePriority::Normal:
-      mNormalQueue->PutEvent(event.forget(), priority, aProofOfLock);
-      break;
-    case EventQueuePriority::DeferredTimers:
-      mDeferredTimersQueue->PutEvent(event.forget(), priority, aProofOfLock);
-      break;
-    case EventQueuePriority::Idle:
-      mIdleQueue->PutEvent(event.forget(), priority, aProofOfLock);
-      break;
-    case EventQueuePriority::Count:
-      MOZ_CRASH("EventQueuePriority::Count isn't a valid priority");
-      break;
+  return n;
+}
+
+void IdlePeriodState::FlagNotIdle(const MutexAutoUnlock& aProofOfUnlock) {
+  MOZ_ASSERT(NS_IsMainThread(),
+             "Why are we touching idle state off the main thread?");
+
+  EnsureIsActive();
+  if (mIdleToken && mIdleToken < TimeStamp::Now()) {
+    ClearIdleToken(aProofOfUnlock);
   }
 }
 
-TimeStamp PrioritizedEventQueue::GetLocalIdleDeadline(bool& aShuttingDown) {
+void IdlePeriodState::RanOutOfTasks(const MutexAutoUnlock& aProofOfUnlock) {
+  MOZ_ASSERT(NS_IsMainThread(),
+             "Why are we touching idle state off the main thread?");
+  MOZ_ASSERT(!mHasPendingEventsPromisedIdleEvent);
+  EnsureIsPaused(aProofOfUnlock);
+  ClearIdleToken(aProofOfUnlock);
+}
+
+TimeStamp IdlePeriodState::GetIdleDeadlineInternal(
+    bool aIsPeek, const MutexAutoUnlock& aProofOfUnlock) {
+  MOZ_ASSERT(NS_IsMainThread(),
+             "Why are we touching idle state off the main thread?");
+
+  bool shuttingDown;
+  TimeStamp localIdleDeadline =
+      GetLocalIdleDeadline(shuttingDown, aProofOfUnlock);
+  if (!localIdleDeadline) {
+    if (!aIsPeek) {
+      EnsureIsPaused(aProofOfUnlock);
+      ClearIdleToken(aProofOfUnlock);
+    }
+    return TimeStamp();
+  }
+
+  TimeStamp idleDeadline =
+      mHasPendingEventsPromisedIdleEvent || shuttingDown
+          ? localIdleDeadline
+          : GetIdleToken(localIdleDeadline, aProofOfUnlock);
+  if (!idleDeadline) {
+    if (!aIsPeek) {
+      EnsureIsPaused(aProofOfUnlock);
+
+      // Don't call ClearIdleToken() here, since we may have a pending
+      // request already.
+      RequestIdleToken(localIdleDeadline, aProofOfUnlock);
+    }
+    return TimeStamp();
+  }
+
+  if (!aIsPeek) {
+    EnsureIsActive();
+  }
+  return idleDeadline;
+}
+
+TimeStamp IdlePeriodState::GetLocalIdleDeadline(
+    bool& aShuttingDown, const MutexAutoUnlock& aProofOfUnlock) {
+  MOZ_ASSERT(NS_IsMainThread(),
+             "Why are we touching idle state off the main thread?");
   // If we are shutting down, we won't honor the idle period, and we will
   // always process idle runnables.  This will ensure that the idle queue
   // gets exhausted at shutdown time to prevent intermittently leaking
   // some runnables inside that queue and even worse potentially leaving
   // some important cleanup work unfinished.
   if (gXPCOMThreadsShutDown ||
       nsThreadManager::get().GetCurrentThread()->ShuttingDown()) {
     aShuttingDown = true;
     return TimeStamp::Now();
   }
 
   aShuttingDown = false;
   TimeStamp idleDeadline;
-  {
-    // Releasing the lock temporarily since getting the idle period
-    // might need to lock the timer thread. Unlocking here might make
-    // us receive an event on the main queue, but we've committed to
-    // run an idle event anyhow.
-    MutexAutoUnlock unlock(*mMutex);
-    mIdlePeriod->GetIdlePeriodHint(&idleDeadline);
-  }
+  // This GetIdlePeriodHint call is why we need aProofOfUnlock, since getting
+  // the idle period might need to lock the timer thread.  Can we get right of
+  // this somehow?
+  Unused << aProofOfUnlock;
+  mIdlePeriod->GetIdlePeriodHint(&idleDeadline);
 
   // If HasPendingEvents() has been called and it has returned true because of
   // pending idle events, there is a risk that we may decide here that we aren't
   // idle and return null, in which case HasPendingEvents() has effectively
   // lied.  Since we can't go back and fix the past, we have to adjust what we
   // do here and forcefully pick the idle queue task here.  Note that this means
   // that we are choosing to run a task from the idle queue when we would
   // normally decide that we aren't in an idle period, but this can only happen
@@ -117,320 +137,46 @@ TimeStamp PrioritizedEventQueue::GetLoca
     // If HasPendingEvents() has been called and it has returned true, but we're
     // no longer in the idle period, we must return a valid timestamp to pretend
     // that we are still in the idle period.
     return TimeStamp::Now();
   }
   return idleDeadline;
 }
 
-EventQueuePriority PrioritizedEventQueue::SelectQueue(
-    bool aUpdateState, const MutexAutoLock& aProofOfLock) {
-  size_t inputCount = mInputQueue->Count(aProofOfLock);
-
-  if (aUpdateState && mInputQueueState == STATE_ENABLED &&
-      mInputHandlingStartTime.IsNull() && inputCount > 0) {
-    mInputHandlingStartTime =
-        InputEventStatistics::Get().GetInputHandlingStartTime(inputCount);
-  }
-
-  // We check the different queues in the following order. The conditions we use
-  // are meant to avoid starvation and to ensure that we don't process an event
-  // at the wrong time.
-  //
-  // HIGH: if mProcessHighPriorityQueue
-  // INPUT: if inputCount > 0 && TimeStamp::Now() > mInputHandlingStartTime
-  // MEDIUMHIGH: if medium high pending
-  // NORMAL: if normal pending
-  //
-  // If we still don't have an event, then we take events from the queues
-  // in the following order:
-  //
-  // HIGH
-  // INPUT
-  // DEFERREDTIMERS: if GetLocalIdleDeadline()
-  // IDLE: if GetLocalIdleDeadline()
-  //
-  // If we don't get an event in this pass, then we return null since no events
-  // are ready.
-
-  // This variable determines which queue we will take an event from.
-  EventQueuePriority queue;
-  bool highPending = !mHighQueue->IsEmpty(aProofOfLock);
-
-  if (mProcessHighPriorityQueue) {
-    queue = EventQueuePriority::High;
-  } else if (inputCount > 0 && (mInputQueueState == STATE_FLUSHING ||
-                                (mInputQueueState == STATE_ENABLED &&
-                                 !mInputHandlingStartTime.IsNull() &&
-                                 TimeStamp::Now() > mInputHandlingStartTime))) {
-    queue = EventQueuePriority::Input;
-  } else if (!mMediumHighQueue->IsEmpty(aProofOfLock)) {
-    MOZ_ASSERT(
-        mInputQueueState != STATE_FLUSHING,
-        "Shouldn't consume medium high event when flushing input events");
-    queue = EventQueuePriority::MediumHigh;
-  } else if (!mNormalQueue->IsEmpty(aProofOfLock)) {
-    MOZ_ASSERT(mInputQueueState != STATE_FLUSHING,
-               "Shouldn't consume normal event when flushing input events");
-    queue = EventQueuePriority::Normal;
-  } else if (highPending) {
-    queue = EventQueuePriority::High;
-  } else if (inputCount > 0 && mInputQueueState != STATE_SUSPEND) {
-    MOZ_ASSERT(
-        mInputQueueState != STATE_DISABLED,
-        "Shouldn't consume input events when the input queue is disabled");
-    queue = EventQueuePriority::Input;
-  } else if (!mDeferredTimersQueue->IsEmpty(aProofOfLock)) {
-    // We may not actually return an idle event in this case.
-    queue = EventQueuePriority::DeferredTimers;
-  } else {
-    // We may not actually return an idle event in this case.
-    queue = EventQueuePriority::Idle;
-  }
-
-  MOZ_ASSERT_IF(
-      queue == EventQueuePriority::Input,
-      mInputQueueState != STATE_DISABLED && mInputQueueState != STATE_SUSPEND);
-
-  if (aUpdateState) {
-    mProcessHighPriorityQueue = highPending;
-  }
-
-  return queue;
-}
-
-already_AddRefed<nsIRunnable> PrioritizedEventQueue::GetEvent(
-    EventQueuePriority* aPriority, const MutexAutoLock& aProofOfLock) {
-#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
-
-  EventQueuePriority queue = SelectQueue(true, aProofOfLock);
-  auto guard = MakeScopeExit([&] {
-    mHasPendingEventsPromisedIdleEvent = false;
-    if (queue != EventQueuePriority::Idle &&
-        queue != EventQueuePriority::DeferredTimers) {
-      EnsureIsActive();
-      if (mIdleToken && mIdleToken < TimeStamp::Now()) {
-        ClearIdleToken();
-      }
-    }
-  });
-
-  if (aPriority) {
-    *aPriority = queue;
-  }
-
-  if (queue == EventQueuePriority::High) {
-    nsCOMPtr<nsIRunnable> event = mHighQueue->GetEvent(aPriority, aProofOfLock);
-    MOZ_ASSERT(event);
-    mInputHandlingStartTime = TimeStamp();
-    mProcessHighPriorityQueue = false;
-    return event.forget();
-  }
-
-  if (queue == EventQueuePriority::Input) {
-    nsCOMPtr<nsIRunnable> event =
-        mInputQueue->GetEvent(aPriority, aProofOfLock);
-    MOZ_ASSERT(event);
-    return event.forget();
-  }
-
-  if (queue == EventQueuePriority::MediumHigh) {
-    nsCOMPtr<nsIRunnable> event =
-        mMediumHighQueue->GetEvent(aPriority, aProofOfLock);
-    return event.forget();
-  }
-
-  if (queue == EventQueuePriority::Normal) {
-    nsCOMPtr<nsIRunnable> event =
-        mNormalQueue->GetEvent(aPriority, aProofOfLock);
-    return event.forget();
-  }
-
-  // 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) &&
-      mDeferredTimersQueue->IsEmpty(aProofOfLock)) {
-    MOZ_ASSERT(!mHasPendingEventsPromisedIdleEvent);
-    EnsureIsPaused();
-    ClearIdleToken();
-    return nullptr;
-  }
+TimeStamp IdlePeriodState::GetIdleToken(TimeStamp aLocalIdlePeriodHint,
+                                        const MutexAutoUnlock& aProofOfUnlock) {
+  MOZ_ASSERT(NS_IsMainThread(),
+             "Why are we touching idle state off the main thread?");
 
-  bool shuttingDown;
-  TimeStamp localIdleDeadline = GetLocalIdleDeadline(shuttingDown);
-  if (!localIdleDeadline) {
-    EnsureIsPaused();
-    ClearIdleToken();
-    return nullptr;
-  }
-
-  TimeStamp idleDeadline = mHasPendingEventsPromisedIdleEvent || shuttingDown
-                               ? localIdleDeadline
-                               : GetIdleToken(localIdleDeadline);
-  if (!idleDeadline) {
-    EnsureIsPaused();
-
-    // Don't call ClearIdleToken() here, since we may have a pending
-    // request already.
-    MutexAutoUnlock unlock(*mMutex);
-    RequestIdleToken(localIdleDeadline);
-    return nullptr;
-  }
-
-  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
-    // in ProcessNextEvent.
-    *mNextIdleDeadline = idleDeadline;
-#endif
-  }
-
-  EnsureIsActive();
-  return event.forget();
-}
-
-void PrioritizedEventQueue::DidRunEvent(const MutexAutoLock& aProofOfLock) {
-  if (IsEmpty(aProofOfLock)) {
-    if (IsActive()) {
-      SetPaused();
-    }
-    ClearIdleToken();
-  }
-}
-
-bool PrioritizedEventQueue::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) &&
-         mMediumHighQueue->IsEmpty(aProofOfLock) &&
-         mNormalQueue->IsEmpty(aProofOfLock) &&
-         mDeferredTimersQueue->IsEmpty(aProofOfLock) &&
-         mIdleQueue->IsEmpty(aProofOfLock);
-}
-
-bool PrioritizedEventQueue::HasReadyEvent(const MutexAutoLock& aProofOfLock) {
-  mHasPendingEventsPromisedIdleEvent = false;
-
-  EventQueuePriority queue = SelectQueue(false, aProofOfLock);
-
-  if (queue == EventQueuePriority::High) {
-    return mHighQueue->HasReadyEvent(aProofOfLock);
-  } else if (queue == EventQueuePriority::Input) {
-    return mInputQueue->HasReadyEvent(aProofOfLock);
-  } else if (queue == EventQueuePriority::MediumHigh) {
-    return mMediumHighQueue->HasReadyEvent(aProofOfLock);
-  } else if (queue == EventQueuePriority::Normal) {
-    return mNormalQueue->HasReadyEvent(aProofOfLock);
-  }
-
-  MOZ_ASSERT(queue == EventQueuePriority::Idle ||
-             queue == EventQueuePriority::DeferredTimers);
-
-  // If we get here, then both the high and normal queues are empty.
-
-  if (mDeferredTimersQueue->IsEmpty(aProofOfLock) &&
-      mIdleQueue->IsEmpty(aProofOfLock)) {
-    return false;
-  }
-
-  bool shuttingDown;
-  TimeStamp localIdleDeadline = GetLocalIdleDeadline(shuttingDown);
-  if (localIdleDeadline) {
-    TimeStamp idleDeadline = mHasPendingEventsPromisedIdleEvent || shuttingDown
-                                 ? localIdleDeadline
-                                 : GetIdleToken(localIdleDeadline);
-    if (idleDeadline && (mDeferredTimersQueue->HasReadyEvent(aProofOfLock) ||
-                         mIdleQueue->HasReadyEvent(aProofOfLock))) {
-      mHasPendingEventsPromisedIdleEvent = true;
-      return true;
-    }
-  }
-
-  return false;
-}
-
-bool PrioritizedEventQueue::HasPendingHighPriorityEvents(
-    const MutexAutoLock& aProofOfLock) {
-  return !mHighQueue->IsEmpty(aProofOfLock);
-}
-
-size_t PrioritizedEventQueue::Count(const MutexAutoLock& aProofOfLock) const {
-  MOZ_CRASH("unimplemented");
-}
-
-void PrioritizedEventQueue::EnableInputEventPrioritization(
-    const MutexAutoLock& aProofOfLock) {
-  MOZ_ASSERT(mInputQueueState == STATE_DISABLED);
-  mInputQueueState = STATE_ENABLED;
-  mInputHandlingStartTime = TimeStamp();
-}
-
-void PrioritizedEventQueue::FlushInputEventPrioritization(
-    const MutexAutoLock& aProofOfLock) {
-  MOZ_ASSERT(mInputQueueState == STATE_ENABLED ||
-             mInputQueueState == STATE_SUSPEND);
-  mInputQueueState =
-      mInputQueueState == STATE_ENABLED ? STATE_FLUSHING : STATE_SUSPEND;
-}
-
-void PrioritizedEventQueue::SuspendInputEventPrioritization(
-    const MutexAutoLock& aProofOfLock) {
-  MOZ_ASSERT(mInputQueueState == STATE_ENABLED ||
-             mInputQueueState == STATE_FLUSHING);
-  mInputQueueState = STATE_SUSPEND;
-}
-
-void PrioritizedEventQueue::ResumeInputEventPrioritization(
-    const MutexAutoLock& aProofOfLock) {
-  MOZ_ASSERT(mInputQueueState == STATE_SUSPEND);
-  mInputQueueState = STATE_ENABLED;
-}
-
-mozilla::TimeStamp PrioritizedEventQueue::GetIdleToken(
-    TimeStamp aLocalIdlePeriodHint) {
   if (XRE_IsParentProcess()) {
     return aLocalIdlePeriodHint;
   }
   if (mIdleToken) {
     TimeStamp now = TimeStamp::Now();
     if (mIdleToken < now) {
-      ClearIdleToken();
+      ClearIdleToken(aProofOfUnlock);
       return mIdleToken;
     }
     return mIdleToken < aLocalIdlePeriodHint ? mIdleToken
                                              : aLocalIdlePeriodHint;
   }
   return TimeStamp();
 }
 
-void PrioritizedEventQueue::RequestIdleToken(TimeStamp aLocalIdlePeriodHint) {
+void IdlePeriodState::RequestIdleToken(TimeStamp aLocalIdlePeriodHint,
+                                       const MutexAutoUnlock& aProofOfUnlock) {
+  MOZ_ASSERT(NS_IsMainThread(),
+             "Why are we touching idle state off the main thread?");
   MOZ_ASSERT(!mActive);
 
   if (!mIdleSchedulerInitialized) {
     mIdleSchedulerInitialized = true;
     if (StaticPrefs::idle_period_cross_process_scheduling() &&
-        XRE_IsContentProcess() && NS_IsMainThread() &&
+        XRE_IsContentProcess() &&
         // Disable when recording/replaying, as IdleSchedulerChild uses mutable
         // shared memory which needs special handling.
         !recordreplay::IsRecordingOrReplaying()) {
       // For now cross-process idle scheduler is supported only on the main
       // threads of the child processes.
       mIdleScheduler = ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
       if (mIdleScheduler) {
         mIdleScheduler->Init(this);
@@ -440,49 +186,67 @@ void PrioritizedEventQueue::RequestIdleT
 
   if (mIdleScheduler && !mIdleRequestId) {
     TimeStamp now = TimeStamp::Now();
     if (aLocalIdlePeriodHint <= now) {
       return;
     }
 
     mIdleRequestId = ++sIdleRequestCounter;
+    // This is presumably the bit where we need the "not holding mutexes"
+    // guarantee.  Can we avoid that somehow?
+    Unused << aProofOfUnlock;
     mIdleScheduler->SendRequestIdleTime(mIdleRequestId,
                                         aLocalIdlePeriodHint - now);
   }
 }
 
-void PrioritizedEventQueue::SetIdleToken(uint64_t aId, TimeDuration aDuration) {
+void IdlePeriodState::SetIdleToken(uint64_t aId, TimeDuration aDuration) {
+  MOZ_ASSERT(NS_IsMainThread(),
+             "Why are we touching idle state off the main thread?");
   if (mIdleRequestId == aId) {
     mIdleToken = TimeStamp::Now() + aDuration;
   }
 }
 
-void PrioritizedEventQueue::SetActive() {
+void IdlePeriodState::SetActive() {
+  MOZ_ASSERT(NS_IsMainThread(),
+             "Why are we touching idle state off the main thread?");
   MOZ_ASSERT(!mActive);
   if (mIdleScheduler) {
     mIdleScheduler->SetActive();
   }
   mActive = true;
 }
 
-void PrioritizedEventQueue::SetPaused() {
+void IdlePeriodState::SetPaused(const MutexAutoUnlock& aProofOfUnlock) {
+  MOZ_ASSERT(NS_IsMainThread(),
+             "Why are we touching idle state off the main thread?");
   MOZ_ASSERT(mActive);
   if (mIdleScheduler && mIdleScheduler->SetPaused()) {
-    MutexAutoUnlock unlock(*mMutex);
     // We may have gotten a free cpu core for running idle tasks.
     // We don't try to catch the case when there are prioritized processes
     // running.
+    // This SendSchedule call is why we need to aProofOfUnlock, because IPC can
+    // do weird things with mutexes.
+    Unused << aProofOfUnlock;
     mIdleScheduler->SendSchedule();
   }
   mActive = false;
 }
 
-void PrioritizedEventQueue::ClearIdleToken() {
+void IdlePeriodState::ClearIdleToken(const MutexAutoUnlock& aProofOfUnlock) {
+  MOZ_ASSERT(NS_IsMainThread(),
+             "Why are we touching idle state off the main thread?");
+
   if (mIdleRequestId) {
     if (mIdleScheduler) {
-      MutexAutoUnlock unlock(*mMutex);
+      // This SendIdleTimeUsed call is why we need a proof of unlock.  Can we
+      // eliminate that requirement somehow?
+      Unused << aProofOfUnlock;
       mIdleScheduler->SendIdleTimeUsed(mIdleRequestId);
     }
     mIdleRequestId = 0;
     mIdleToken = TimeStamp();
   }
 }
+
+}  // namespace mozilla
copy from xpcom/threads/PrioritizedEventQueue.h
copy to xpcom/threads/IdlePeriodState.h
--- a/xpcom/threads/PrioritizedEventQueue.h
+++ b/xpcom/threads/IdlePeriodState.h
@@ -1,207 +1,190 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#ifndef mozilla_PrioritizedEventQueue_h
-#define mozilla_PrioritizedEventQueue_h
+#ifndef mozilla_IdlePeriodState_h
+#define mozilla_IdlePeriodState_h
 
-#include "mozilla/AbstractEventQueue.h"
-#include "mozilla/EventQueue.h"
+/**
+ * A class for tracking the state of our idle period.  This includes keeping
+ * track of both the state of our process-local idle period estimate and, for
+ * content processes, managing communication with the parent process for
+ * cross-pprocess idle detection.
+ */
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
 #include "mozilla/TimeStamp.h"
-#include "mozilla/TypeTraits.h"
-#include "mozilla/UniquePtr.h"
 #include "nsCOMPtr.h"
-#include "nsIIdlePeriod.h"
 
-class nsIRunnable;
+#include <stdint.h>
+
+class nsIIdlePeriod;
 
 namespace mozilla {
 namespace ipc {
 class IdleSchedulerChild;
-}
+}  // namespace ipc
+
+class IdlePeriodState {
+ public:
+  explicit IdlePeriodState(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod);
+
+  ~IdlePeriodState();
+
+  // Integration with memory reporting.
+  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
 
-// 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:
-// - We try to avoid processing too many high-priority events in a row so
-//   that the normal priority queue is not starved. When there are high-
-//   and normal-priority events available, we interleave popping from the
-//   normal and high queues.
-// - We do not select events from the idle queue if the current idle period
-//   is almost over.
-class PrioritizedEventQueue final : public AbstractEventQueue {
- public:
-  static const bool SupportsPrioritization = true;
+  // Notification that whoever we are tracking idle state for has found a
+  // non-idle task to process.
+  //
+  // Do not call this while holding any mutexes, because it may lock mutexes
+  // itself.
+  void FlagNotIdle(const MutexAutoUnlock& aProofOfUnlock);
 
-  explicit PrioritizedEventQueue(already_AddRefed<nsIIdlePeriod> aIdlePeriod);
-
-  virtual ~PrioritizedEventQueue();
-
-  void PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
-                EventQueuePriority aPriority,
-                const MutexAutoLock& aProofOfLock) final;
-  already_AddRefed<nsIRunnable> GetEvent(
-      EventQueuePriority* aPriority, const MutexAutoLock& aProofOfLock) final;
-  void DidRunEvent(const MutexAutoLock& aProofOfLock);
+  // Notification that whoever we are tracking idle state for has no more
+  // tasks (idle or not) to process.
+  //
+  // Do not call this while holding any mutexes, because it may lock mutexes
+  // itself.
+  void RanOutOfTasks(const MutexAutoUnlock& aProofOfUnlock);
 
-  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;
+  // Notification that whoever we are tracking idle state has idle tasks that
+  // they are considering ready to run and that we should keep claiming they are
+  // ready to run until they call ForgetPendingTaskGuarantee().
+  void EnforcePendingTaskGuarantee() {
+    mHasPendingEventsPromisedIdleEvent = true;
+  }
 
-  // When checking the idle deadline, we need to drop whatever mutex protects
-  // this queue. This method allows that mutex to be stored so that we can drop
-  // it and reacquire it when checking the idle deadline. The mutex must live at
-  // least as long as the queue.
-  void SetMutexRef(Mutex& aMutex) { mMutex = &aMutex; }
-
-#ifndef RELEASE_OR_BETA
-  // nsThread.cpp sends telemetry containing the most recently computed idle
-  // deadline. We store a reference to a field in nsThread where this deadline
-  // will be stored so that it can be fetched quickly for telemetry.
-  void SetNextIdleDeadlineRef(TimeStamp& aDeadline) {
-    mNextIdleDeadline = &aDeadline;
+  // Notification that whoever we are tracking idle state for is done with our
+  // "we have an idle event ready to run" guarantee.  When this happens, we can
+  // reset mHasPendingEventsPromisedIdleEvent to false, because we have
+  // fulfilled our contract.
+  void ForgetPendingTaskGuarantee() {
+    mHasPendingEventsPromisedIdleEvent = false;
   }
-#endif
 
-  void EnableInputEventPrioritization(const MutexAutoLock& aProofOfLock) final;
-  void FlushInputEventPrioritization(const MutexAutoLock& aProofOfLock) final;
-  void SuspendInputEventPrioritization(const MutexAutoLock& aProofOfLock) final;
-  void ResumeInputEventPrioritization(const MutexAutoLock& aProofOfLock) final;
-
-  size_t SizeOfExcludingThis(
-      mozilla::MallocSizeOf aMallocSizeOf) const override {
-    size_t n = 0;
+  // Get our current idle deadline so we can run an idle task with that
+  // deadline.  This can return a null timestamp (which means we are not idle
+  // right now), and it can also queue up queries to our parent process, if
+  // we're a content process, to find out whether we're idle.  This should only
+  // be called when there is an actual idle task that might run.
+  //
+  // Do not call this while holding any mutexes, because it may lock mutexes
+  // itself.
+  TimeStamp GetDeadlineForIdleTask(const MutexAutoUnlock& aProofOfUnlock) {
+    return GetIdleDeadlineInternal(false, aProofOfUnlock);
+  }
 
-    n += mHighQueue->SizeOfIncludingThis(aMallocSizeOf);
-    n += mInputQueue->SizeOfIncludingThis(aMallocSizeOf);
-    n += mMediumHighQueue->SizeOfIncludingThis(aMallocSizeOf);
-    n += mNormalQueue->SizeOfIncludingThis(aMallocSizeOf);
-    n += mDeferredTimersQueue->SizeOfIncludingThis(aMallocSizeOf);
-    n += mIdleQueue->SizeOfIncludingThis(aMallocSizeOf);
-
-    if (mIdlePeriod) {
-      n += aMallocSizeOf(mIdlePeriod);
-    }
-
-    return n;
+  // Peek our current idle deadline.  This can return a null timestamp (which
+  // means we are not idle right now).  This method does not have any
+  // side-effects on our state, apart from guaranteeing that if it returns
+  // non-null then GetDeadlineForIdleTask will return non-null until
+  // ForgetPendingTaskGuarantee() is called.
+  //
+  // Do not call this while holding any mutexes, because it may lock mutexes
+  // itself.
+  TimeStamp PeekIdleDeadline(const MutexAutoUnlock& aProofOfUnlock) {
+    return GetIdleDeadlineInternal(true, aProofOfUnlock);
   }
 
   void SetIdleToken(uint64_t aId, TimeDuration aDuration);
 
   bool IsActive() { return mActive; }
 
+ protected:
   void EnsureIsActive() {
     if (!mActive) {
       SetActive();
     }
   }
 
-  void EnsureIsPaused() {
+  void EnsureIsPaused(const MutexAutoUnlock& aProofOfUnlock) {
     if (mActive) {
-      SetPaused();
+      SetPaused(aProofOfUnlock);
     }
   }
 
- private:
-  EventQueuePriority SelectQueue(bool aUpdateState,
-                                 const MutexAutoLock& aProofOfLock);
+  // Returns a null TimeStamp if we're not in the idle period.
+  TimeStamp GetLocalIdleDeadline(bool& aShuttingDown,
+                                 const MutexAutoUnlock& aProofOfUnlock);
+
+  // Gets the idle token, which is the end time of the idle period.
+  //
+  // Do not call this while holding any mutexes, because it may lock mutexes
+  // itself.
+  TimeStamp GetIdleToken(TimeStamp aLocalIdlePeriodHint,
+                         const MutexAutoUnlock& aProofOfUnlock);
 
-  // Returns a null TimeStamp if we're not in the idle period.
-  mozilla::TimeStamp GetLocalIdleDeadline(bool& aShuttingDown);
+  // In case of child processes, requests idle time from the cross-process
+  // idle scheduler.
+  //
+  // Do not call this while holding any mutexes, because it may lock mutexes
+  // itself.
+  void RequestIdleToken(TimeStamp aLocalIdlePeriodHint,
+                        const MutexAutoUnlock& aProofOfUnlock);
+
+  // Mark that we don't have idle time to use, nor are expecting to get an idle
+  // token from the idle scheduler.
+  void ClearIdleToken(const MutexAutoUnlock& aProofOfUnlock);
 
   // SetActive should be called when the event queue is running any type of
   // tasks.
   void SetActive();
   // SetPaused should be called once the event queue doesn't have more
   // tasks to process, or is waiting for the idle token.
-  void SetPaused();
-
-  // Gets the idle token, which is the end time of the idle period.
-  TimeStamp GetIdleToken(TimeStamp aLocalIdlePeriodHint);
-
-  // In case of child processes, requests idle time from the cross-process
-  // idle scheduler.
-  void RequestIdleToken(TimeStamp aLocalIdlePeriodHint);
-
-  // Returns true if the event queue either is waiting for an idle token
-  // from the idle scheduler or has one.
-  bool HasIdleRequest() { return mIdleRequestId != 0; }
-
-  // Mark that the event queue doesn't have idle time to use, nor is expecting
-  // to get idle token from the idle scheduler.
-  void ClearIdleToken();
+  //
+  // Do not call this while holding any mutexes, because it may lock mutexes
+  // itself.
+  void SetPaused(const MutexAutoUnlock& aProofOfUnlock);
 
-  UniquePtr<EventQueue> mHighQueue;
-  UniquePtr<EventQueue> mInputQueue;
-  UniquePtr<EventQueue> mMediumHighQueue;
-  UniquePtr<EventQueue> mNormalQueue;
-  UniquePtr<EventQueue> mDeferredTimersQueue;
-  UniquePtr<EventQueue> mIdleQueue;
-
-  // We need to drop the queue mutex when checking the idle deadline, so we keep
-  // a pointer to it here.
-  Mutex* mMutex = nullptr;
+  // Get or peek our idle deadline.  When peeking, we generally don't change any
+  // of our internal state.  When getting, we may request an idle token as
+  // needed.
+  //
+  // Do not call this while holding any mutexes, because it may lock mutexes
+  // itself.
+  TimeStamp GetIdleDeadlineInternal(bool aIsPeek,
+                                    const MutexAutoUnlock& aProofOfUnlock);
 
-#ifndef RELEASE_OR_BETA
-  // Pointer to a place where the most recently computed idle deadline is
-  // stored.
-  TimeStamp* mNextIdleDeadline = nullptr;
-#endif
+  // Set to true if we have claimed we have a ready-to-run idle task when asked.
+  // In that case, we will ensure that we allow at least one task to run when
+  // someone tries to run a task, even if we have run out of idle period at that
+  // point.  This ensures that we never fail to produce a task to run if we
+  // claim we have a task ready to run.
+  bool mHasPendingEventsPromisedIdleEvent = false;
 
-  // Try to process one high priority runnable after each normal
-  // priority runnable. This gives the processing model HTML spec has for
-  // 'Update the rendering' in the case only vsync messages are in the
-  // secondary queue and prevents starving the normal queue.
-  bool mProcessHighPriorityQueue = false;
-
-  // mIdlePeriod keeps track of the current idle period. If at any
-  // time the main event queue is empty, calling
+  // mIdlePeriod keeps track of the current idle period. Calling
   // mIdlePeriod->GetIdlePeriodHint() will give an estimate of when
   // the current idle period will end.
   nsCOMPtr<nsIIdlePeriod> mIdlePeriod;
 
-  // Set to true if HasPendingEvents() has been called and returned true because
-  // of a pending idle event.  This is used to remember to return that idle
-  // event from GetIdleEvent() to ensure that HasPendingEvents() never lies.
-  bool mHasPendingEventsPromisedIdleEvent = false;
-
-  TimeStamp mInputHandlingStartTime;
-
-  enum InputEventQueueState {
-    STATE_DISABLED,
-    STATE_FLUSHING,
-    STATE_SUSPEND,
-    STATE_ENABLED
-  };
-  InputEventQueueState mInputQueueState = STATE_DISABLED;
-
-  // If non-null, tells the end time of the idle period.
-  // Idle period starts when we get idle token from the parent process and
-  // ends when either there are no runnables in the event queues or
-  // mIdleToken < TimeStamp::Now()
+  // If non-null, this timestamp represents the end time of the idle period.  An
+  // idle period starts when we get the idle token from the parent process and
+  // ends when either there are no more things we want to run at idle priority
+  // or mIdleToken < TimeStamp::Now(), so we have reached our idle deadline.
   TimeStamp mIdleToken;
 
   // The id of the last idle request to the cross-process idle scheduler.
   uint64_t mIdleRequestId = 0;
 
+  // If we're in a content process, we use mIdleScheduler to communicate with
+  // the parent process for purposes of cross-process idle tracking.
   RefPtr<ipc::IdleSchedulerChild> mIdleScheduler;
+
+  // mIdleSchedulerInitialized is true if our mIdleScheduler has been
+  // initialized.  It may be null even after initialiazation, in various
+  // situations.
   bool mIdleSchedulerInitialized = false;
 
-  // mActive tells whether the event queue is running tasks.
+  // mActive is true when the PrioritizedEventQueue or TaskController we are
+  // associated with is running tasks.
   bool mActive = true;
 };
 
 }  // namespace mozilla
 
-#endif  // mozilla_PrioritizedEventQueue_h
+#endif  // mozilla_IdlePeriodState_h
--- a/xpcom/threads/PrioritizedEventQueue.cpp
+++ b/xpcom/threads/PrioritizedEventQueue.cpp
@@ -2,43 +2,36 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "PrioritizedEventQueue.h"
 #include "mozilla/EventQueue.h"
 #include "mozilla/ScopeExit.h"
-#include "mozilla/StaticPrefs_idle_period.h"
 #include "mozilla/StaticPrefs_threads.h"
 #include "mozilla/ipc/IdleSchedulerChild.h"
 #include "nsThreadManager.h"
 #include "nsXPCOMPrivate.h"  // for gXPCOMThreadsShutDown
 #include "InputEventStatistics.h"
 
 using namespace mozilla;
 
-static uint64_t sIdleRequestCounter = 0;
-
 PrioritizedEventQueue::PrioritizedEventQueue(
-    already_AddRefed<nsIIdlePeriod> aIdlePeriod)
+    already_AddRefed<nsIIdlePeriod>&& aIdlePeriod)
     : mHighQueue(MakeUnique<EventQueue>(EventQueuePriority::High)),
       mInputQueue(MakeUnique<EventQueue>(EventQueuePriority::Input)),
       mMediumHighQueue(MakeUnique<EventQueue>(EventQueuePriority::MediumHigh)),
       mNormalQueue(MakeUnique<EventQueue>(EventQueuePriority::Normal)),
       mDeferredTimersQueue(
           MakeUnique<EventQueue>(EventQueuePriority::DeferredTimers)),
       mIdleQueue(MakeUnique<EventQueue>(EventQueuePriority::Idle)),
-      mIdlePeriod(aIdlePeriod) {}
+      mIdlePeriodState(std::move(aIdlePeriod)) {}
 
-PrioritizedEventQueue::~PrioritizedEventQueue() {
-  if (mIdleScheduler) {
-    mIdleScheduler->Disconnect();
-  }
-}
+PrioritizedEventQueue::~PrioritizedEventQueue() = default;
 
 void PrioritizedEventQueue::PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
                                      EventQueuePriority aPriority,
                                      const MutexAutoLock& aProofOfLock) {
   // Double check the priority with a QI.
   RefPtr<nsIRunnable> event(aEvent);
   EventQueuePriority priority = aPriority;
 
@@ -70,63 +63,16 @@ void PrioritizedEventQueue::PutEvent(alr
       mIdleQueue->PutEvent(event.forget(), priority, aProofOfLock);
       break;
     case EventQueuePriority::Count:
       MOZ_CRASH("EventQueuePriority::Count isn't a valid priority");
       break;
   }
 }
 
-TimeStamp PrioritizedEventQueue::GetLocalIdleDeadline(bool& aShuttingDown) {
-  // If we are shutting down, we won't honor the idle period, and we will
-  // always process idle runnables.  This will ensure that the idle queue
-  // gets exhausted at shutdown time to prevent intermittently leaking
-  // some runnables inside that queue and even worse potentially leaving
-  // some important cleanup work unfinished.
-  if (gXPCOMThreadsShutDown ||
-      nsThreadManager::get().GetCurrentThread()->ShuttingDown()) {
-    aShuttingDown = true;
-    return TimeStamp::Now();
-  }
-
-  aShuttingDown = false;
-  TimeStamp idleDeadline;
-  {
-    // Releasing the lock temporarily since getting the idle period
-    // might need to lock the timer thread. Unlocking here might make
-    // us receive an event on the main queue, but we've committed to
-    // run an idle event anyhow.
-    MutexAutoUnlock unlock(*mMutex);
-    mIdlePeriod->GetIdlePeriodHint(&idleDeadline);
-  }
-
-  // If HasPendingEvents() has been called and it has returned true because of
-  // pending idle events, there is a risk that we may decide here that we aren't
-  // idle and return null, in which case HasPendingEvents() has effectively
-  // lied.  Since we can't go back and fix the past, we have to adjust what we
-  // do here and forcefully pick the idle queue task here.  Note that this means
-  // that we are choosing to run a task from the idle queue when we would
-  // normally decide that we aren't in an idle period, but this can only happen
-  // if we fall out of the idle period in between the call to HasPendingEvents()
-  // and here, which should hopefully be quite rare.  We are effectively
-  // choosing to prioritize the sanity of our API semantics over the optimal
-  // scheduling.
-  if (!mHasPendingEventsPromisedIdleEvent &&
-      (!idleDeadline || idleDeadline < TimeStamp::Now())) {
-    return TimeStamp();
-  }
-  if (mHasPendingEventsPromisedIdleEvent && !idleDeadline) {
-    // If HasPendingEvents() has been called and it has returned true, but we're
-    // no longer in the idle period, we must return a valid timestamp to pretend
-    // that we are still in the idle period.
-    return TimeStamp::Now();
-  }
-  return idleDeadline;
-}
-
 EventQueuePriority PrioritizedEventQueue::SelectQueue(
     bool aUpdateState, const MutexAutoLock& aProofOfLock) {
   size_t inputCount = mInputQueue->Count(aProofOfLock);
 
   if (aUpdateState && mInputQueueState == STATE_ENABLED &&
       mInputHandlingStartTime.IsNull() && inputCount > 0) {
     mInputHandlingStartTime =
         InputEventStatistics::Get().GetInputHandlingStartTime(inputCount);
@@ -203,23 +149,21 @@ already_AddRefed<nsIRunnable> Prioritize
 #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
 
   EventQueuePriority queue = SelectQueue(true, aProofOfLock);
   auto guard = MakeScopeExit([&] {
-    mHasPendingEventsPromisedIdleEvent = false;
+    mIdlePeriodState.ForgetPendingTaskGuarantee();
     if (queue != EventQueuePriority::Idle &&
         queue != EventQueuePriority::DeferredTimers) {
-      EnsureIsActive();
-      if (mIdleToken && mIdleToken < TimeStamp::Now()) {
-        ClearIdleToken();
-      }
+      MutexAutoUnlock unlock(*mMutex);
+      mIdlePeriodState.FlagNotIdle(unlock);
     }
   });
 
   if (aPriority) {
     *aPriority = queue;
   }
 
   if (queue == EventQueuePriority::High) {
@@ -250,40 +194,29 @@ already_AddRefed<nsIRunnable> Prioritize
   }
 
   // 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) &&
       mDeferredTimersQueue->IsEmpty(aProofOfLock)) {
-    MOZ_ASSERT(!mHasPendingEventsPromisedIdleEvent);
-    EnsureIsPaused();
-    ClearIdleToken();
+    MutexAutoUnlock unlock(*mMutex);
+    mIdlePeriodState.RanOutOfTasks(unlock);
     return nullptr;
   }
 
-  bool shuttingDown;
-  TimeStamp localIdleDeadline = GetLocalIdleDeadline(shuttingDown);
-  if (!localIdleDeadline) {
-    EnsureIsPaused();
-    ClearIdleToken();
-    return nullptr;
+  TimeStamp idleDeadline;
+  {
+    // Scope for MutexAutoUnlock
+    MutexAutoUnlock unlock(*mMutex);
+    idleDeadline = mIdlePeriodState.GetDeadlineForIdleTask(unlock);
   }
 
-  TimeStamp idleDeadline = mHasPendingEventsPromisedIdleEvent || shuttingDown
-                               ? localIdleDeadline
-                               : GetIdleToken(localIdleDeadline);
   if (!idleDeadline) {
-    EnsureIsPaused();
-
-    // Don't call ClearIdleToken() here, since we may have a pending
-    // request already.
-    MutexAutoUnlock unlock(*mMutex);
-    RequestIdleToken(localIdleDeadline);
     return nullptr;
   }
 
   nsCOMPtr<nsIRunnable> event =
       mDeferredTimersQueue->GetEvent(aPriority, aProofOfLock);
   if (!event) {
     event = mIdleQueue->GetEvent(aPriority, aProofOfLock);
   }
@@ -295,42 +228,40 @@ already_AddRefed<nsIRunnable> Prioritize
 
 #ifndef RELEASE_OR_BETA
     // Store the next idle deadline to be able to determine budget use
     // in ProcessNextEvent.
     *mNextIdleDeadline = idleDeadline;
 #endif
   }
 
-  EnsureIsActive();
   return event.forget();
 }
 
 void PrioritizedEventQueue::DidRunEvent(const MutexAutoLock& aProofOfLock) {
   if (IsEmpty(aProofOfLock)) {
-    if (IsActive()) {
-      SetPaused();
-    }
-    ClearIdleToken();
+    // Certainly no more idle tasks.
+    MutexAutoUnlock unlock(*mMutex);
+    mIdlePeriodState.RanOutOfTasks(unlock);
   }
 }
 
 bool PrioritizedEventQueue::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) &&
          mMediumHighQueue->IsEmpty(aProofOfLock) &&
          mNormalQueue->IsEmpty(aProofOfLock) &&
          mDeferredTimersQueue->IsEmpty(aProofOfLock) &&
          mIdleQueue->IsEmpty(aProofOfLock);
 }
 
 bool PrioritizedEventQueue::HasReadyEvent(const MutexAutoLock& aProofOfLock) {
-  mHasPendingEventsPromisedIdleEvent = false;
+  mIdlePeriodState.ForgetPendingTaskGuarantee();
 
   EventQueuePriority queue = SelectQueue(false, aProofOfLock);
 
   if (queue == EventQueuePriority::High) {
     return mHighQueue->HasReadyEvent(aProofOfLock);
   } else if (queue == EventQueuePriority::Input) {
     return mInputQueue->HasReadyEvent(aProofOfLock);
   } else if (queue == EventQueuePriority::MediumHigh) {
@@ -344,27 +275,25 @@ bool PrioritizedEventQueue::HasReadyEven
 
   // If we get here, then both the high and normal queues are empty.
 
   if (mDeferredTimersQueue->IsEmpty(aProofOfLock) &&
       mIdleQueue->IsEmpty(aProofOfLock)) {
     return false;
   }
 
-  bool shuttingDown;
-  TimeStamp localIdleDeadline = GetLocalIdleDeadline(shuttingDown);
-  if (localIdleDeadline) {
-    TimeStamp idleDeadline = mHasPendingEventsPromisedIdleEvent || shuttingDown
-                                 ? localIdleDeadline
-                                 : GetIdleToken(localIdleDeadline);
-    if (idleDeadline && (mDeferredTimersQueue->HasReadyEvent(aProofOfLock) ||
-                         mIdleQueue->HasReadyEvent(aProofOfLock))) {
-      mHasPendingEventsPromisedIdleEvent = true;
-      return true;
-    }
+  TimeStamp idleDeadline;
+  {
+    MutexAutoUnlock unlock(*mMutex);
+    idleDeadline = mIdlePeriodState.PeekIdleDeadline(unlock);
+  }
+  if (idleDeadline && (mDeferredTimersQueue->HasReadyEvent(aProofOfLock) ||
+                       mIdleQueue->HasReadyEvent(aProofOfLock))) {
+    mIdlePeriodState.EnforcePendingTaskGuarantee();
+    return true;
   }
 
   return false;
 }
 
 bool PrioritizedEventQueue::HasPendingHighPriorityEvents(
     const MutexAutoLock& aProofOfLock) {
   return !mHighQueue->IsEmpty(aProofOfLock);
@@ -396,93 +325,8 @@ void PrioritizedEventQueue::SuspendInput
   mInputQueueState = STATE_SUSPEND;
 }
 
 void PrioritizedEventQueue::ResumeInputEventPrioritization(
     const MutexAutoLock& aProofOfLock) {
   MOZ_ASSERT(mInputQueueState == STATE_SUSPEND);
   mInputQueueState = STATE_ENABLED;
 }
-
-mozilla::TimeStamp PrioritizedEventQueue::GetIdleToken(
-    TimeStamp aLocalIdlePeriodHint) {
-  if (XRE_IsParentProcess()) {
-    return aLocalIdlePeriodHint;
-  }
-  if (mIdleToken) {
-    TimeStamp now = TimeStamp::Now();
-    if (mIdleToken < now) {
-      ClearIdleToken();
-      return mIdleToken;
-    }
-    return mIdleToken < aLocalIdlePeriodHint ? mIdleToken
-                                             : aLocalIdlePeriodHint;
-  }
-  return TimeStamp();
-}
-
-void PrioritizedEventQueue::RequestIdleToken(TimeStamp aLocalIdlePeriodHint) {
-  MOZ_ASSERT(!mActive);
-
-  if (!mIdleSchedulerInitialized) {
-    mIdleSchedulerInitialized = true;
-    if (StaticPrefs::idle_period_cross_process_scheduling() &&
-        XRE_IsContentProcess() && NS_IsMainThread() &&
-        // Disable when recording/replaying, as IdleSchedulerChild uses mutable
-        // shared memory which needs special handling.
-        !recordreplay::IsRecordingOrReplaying()) {
-      // For now cross-process idle scheduler is supported only on the main
-      // threads of the child processes.
-      mIdleScheduler = ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
-      if (mIdleScheduler) {
-        mIdleScheduler->Init(this);
-      }
-    }
-  }
-
-  if (mIdleScheduler && !mIdleRequestId) {
-    TimeStamp now = TimeStamp::Now();
-    if (aLocalIdlePeriodHint <= now) {
-      return;
-    }
-
-    mIdleRequestId = ++sIdleRequestCounter;
-    mIdleScheduler->SendRequestIdleTime(mIdleRequestId,
-                                        aLocalIdlePeriodHint - now);
-  }
-}
-
-void PrioritizedEventQueue::SetIdleToken(uint64_t aId, TimeDuration aDuration) {
-  if (mIdleRequestId == aId) {
-    mIdleToken = TimeStamp::Now() + aDuration;
-  }
-}
-
-void PrioritizedEventQueue::SetActive() {
-  MOZ_ASSERT(!mActive);
-  if (mIdleScheduler) {
-    mIdleScheduler->SetActive();
-  }
-  mActive = true;
-}
-
-void PrioritizedEventQueue::SetPaused() {
-  MOZ_ASSERT(mActive);
-  if (mIdleScheduler && mIdleScheduler->SetPaused()) {
-    MutexAutoUnlock unlock(*mMutex);
-    // We may have gotten a free cpu core for running idle tasks.
-    // We don't try to catch the case when there are prioritized processes
-    // running.
-    mIdleScheduler->SendSchedule();
-  }
-  mActive = false;
-}
-
-void PrioritizedEventQueue::ClearIdleToken() {
-  if (mIdleRequestId) {
-    if (mIdleScheduler) {
-      MutexAutoUnlock unlock(*mMutex);
-      mIdleScheduler->SendIdleTimeUsed(mIdleRequestId);
-    }
-    mIdleRequestId = 0;
-    mIdleToken = TimeStamp();
-  }
-}
--- a/xpcom/threads/PrioritizedEventQueue.h
+++ b/xpcom/threads/PrioritizedEventQueue.h
@@ -4,22 +4,23 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_PrioritizedEventQueue_h
 #define mozilla_PrioritizedEventQueue_h
 
 #include "mozilla/AbstractEventQueue.h"
 #include "mozilla/EventQueue.h"
+#include "mozilla/IdlePeriodState.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/TypeTraits.h"
 #include "mozilla/UniquePtr.h"
 #include "nsCOMPtr.h"
-#include "nsIIdlePeriod.h"
 
+class nsIIdlePeriod;
 class nsIRunnable;
 
 namespace mozilla {
 namespace ipc {
 class IdleSchedulerChild;
 }
 
 // This AbstractEventQueue implementation has one queue for each
@@ -38,17 +39,17 @@ class IdleSchedulerChild;
 //   and normal-priority events available, we interleave popping from the
 //   normal and high queues.
 // - We do not select events from the idle queue if the current idle period
 //   is almost over.
 class PrioritizedEventQueue final : public AbstractEventQueue {
  public:
   static const bool SupportsPrioritization = true;
 
-  explicit PrioritizedEventQueue(already_AddRefed<nsIIdlePeriod> aIdlePeriod);
+  explicit PrioritizedEventQueue(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod);
 
   virtual ~PrioritizedEventQueue();
 
   void PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
                 EventQueuePriority aPriority,
                 const MutexAutoLock& aProofOfLock) final;
   already_AddRefed<nsIRunnable> GetEvent(
       EventQueuePriority* aPriority, const MutexAutoLock& aProofOfLock) final;
@@ -85,68 +86,25 @@ class PrioritizedEventQueue final : publ
 
     n += mHighQueue->SizeOfIncludingThis(aMallocSizeOf);
     n += mInputQueue->SizeOfIncludingThis(aMallocSizeOf);
     n += mMediumHighQueue->SizeOfIncludingThis(aMallocSizeOf);
     n += mNormalQueue->SizeOfIncludingThis(aMallocSizeOf);
     n += mDeferredTimersQueue->SizeOfIncludingThis(aMallocSizeOf);
     n += mIdleQueue->SizeOfIncludingThis(aMallocSizeOf);
 
-    if (mIdlePeriod) {
-      n += aMallocSizeOf(mIdlePeriod);
-    }
+    n += mIdlePeriodState.SizeOfExcludingThis(aMallocSizeOf);
 
     return n;
   }
 
-  void SetIdleToken(uint64_t aId, TimeDuration aDuration);
-
-  bool IsActive() { return mActive; }
-
-  void EnsureIsActive() {
-    if (!mActive) {
-      SetActive();
-    }
-  }
-
-  void EnsureIsPaused() {
-    if (mActive) {
-      SetPaused();
-    }
-  }
-
  private:
   EventQueuePriority SelectQueue(bool aUpdateState,
                                  const MutexAutoLock& aProofOfLock);
 
-  // Returns a null TimeStamp if we're not in the idle period.
-  mozilla::TimeStamp GetLocalIdleDeadline(bool& aShuttingDown);
-
-  // SetActive should be called when the event queue is running any type of
-  // tasks.
-  void SetActive();
-  // SetPaused should be called once the event queue doesn't have more
-  // tasks to process, or is waiting for the idle token.
-  void SetPaused();
-
-  // Gets the idle token, which is the end time of the idle period.
-  TimeStamp GetIdleToken(TimeStamp aLocalIdlePeriodHint);
-
-  // In case of child processes, requests idle time from the cross-process
-  // idle scheduler.
-  void RequestIdleToken(TimeStamp aLocalIdlePeriodHint);
-
-  // Returns true if the event queue either is waiting for an idle token
-  // from the idle scheduler or has one.
-  bool HasIdleRequest() { return mIdleRequestId != 0; }
-
-  // Mark that the event queue doesn't have idle time to use, nor is expecting
-  // to get idle token from the idle scheduler.
-  void ClearIdleToken();
-
   UniquePtr<EventQueue> mHighQueue;
   UniquePtr<EventQueue> mInputQueue;
   UniquePtr<EventQueue> mMediumHighQueue;
   UniquePtr<EventQueue> mNormalQueue;
   UniquePtr<EventQueue> mDeferredTimersQueue;
   UniquePtr<EventQueue> mIdleQueue;
 
   // We need to drop the queue mutex when checking the idle deadline, so we keep
@@ -160,48 +118,25 @@ class PrioritizedEventQueue final : publ
 #endif
 
   // Try to process one high priority runnable after each normal
   // priority runnable. This gives the processing model HTML spec has for
   // 'Update the rendering' in the case only vsync messages are in the
   // secondary queue and prevents starving the normal queue.
   bool mProcessHighPriorityQueue = false;
 
-  // mIdlePeriod keeps track of the current idle period. If at any
-  // time the main event queue is empty, calling
-  // mIdlePeriod->GetIdlePeriodHint() will give an estimate of when
-  // the current idle period will end.
-  nsCOMPtr<nsIIdlePeriod> mIdlePeriod;
-
-  // Set to true if HasPendingEvents() has been called and returned true because
-  // of a pending idle event.  This is used to remember to return that idle
-  // event from GetIdleEvent() to ensure that HasPendingEvents() never lies.
-  bool mHasPendingEventsPromisedIdleEvent = false;
-
   TimeStamp mInputHandlingStartTime;
 
   enum InputEventQueueState {
     STATE_DISABLED,
     STATE_FLUSHING,
     STATE_SUSPEND,
     STATE_ENABLED
   };
   InputEventQueueState mInputQueueState = STATE_DISABLED;
 
-  // If non-null, tells the end time of the idle period.
-  // Idle period starts when we get idle token from the parent process and
-  // ends when either there are no runnables in the event queues or
-  // mIdleToken < TimeStamp::Now()
-  TimeStamp mIdleToken;
-
-  // The id of the last idle request to the cross-process idle scheduler.
-  uint64_t mIdleRequestId = 0;
-
-  RefPtr<ipc::IdleSchedulerChild> mIdleScheduler;
-  bool mIdleSchedulerInitialized = false;
-
-  // mActive tells whether the event queue is running tasks.
-  bool mActive = true;
+  // Tracking of our idle state of various sorts.
+  IdlePeriodState mIdlePeriodState;
 };
 
 }  // namespace mozilla
 
 #endif  // mozilla_PrioritizedEventQueue_h
--- a/xpcom/threads/moz.build
+++ b/xpcom/threads/moz.build
@@ -43,16 +43,17 @@ EXPORTS.mozilla += [
     'AbstractEventQueue.h',
     'AbstractThread.h',
     'BlockingResourceBase.h',
     'CondVar.h',
     'CPUUsageWatcher.h',
     'DataMutex.h',
     'DeadlockDetector.h',
     'EventQueue.h',
+    'IdlePeriodState.h',
     'IdleTaskRunner.h',
     'LazyIdleThread.h',
     'MainThreadIdlePeriod.h',
     'Monitor.h',
     'MozPromise.h',
     'MozPromiseInlines.h',
     'Mutex.h',
     'PerformanceCounter.h',
@@ -81,16 +82,17 @@ SOURCES += [
     'ThreadDelay.cpp',
 ]
 
 UNIFIED_SOURCES += [
     'AbstractThread.cpp',
     'BlockingResourceBase.cpp',
     'CPUUsageWatcher.cpp',
     'EventQueue.cpp',
+    'IdlePeriodState.cpp',
     'InputEventStatistics.cpp',
     'LazyIdleThread.cpp',
     'MainThreadIdlePeriod.cpp',
     'nsEnvironment.cpp',
     'nsMemoryPressure.cpp',
     'nsProcessCommon.cpp',
     'nsProxyRelease.cpp',
     'nsThread.cpp',