Bug 1606706 - Part 2: Integrate new TaskController code into tree and allow usage by pref. r=smaug,?
☠☠ backed out by fb5ee52cb42c ☠ ☠
authorBas Schouten <bschouten@mozilla.com>
Tue, 02 Jun 2020 11:36:38 +0000
changeset 533641 ab3e1a067a7124392ef358135d918929bc078e37
parent 533640 c47cf57dab71d81c60b5a735277c39dd719ce2ba
child 533642 f8a46fbea1f665dc8a76ec4ad6d1dbd3d8c82b7f
push id37475
push userbtara@mozilla.com
push dateWed, 03 Jun 2020 16:12:12 +0000
treeherdermozilla-central@ea34fd156c89 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1606706
milestone79.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 1606706 - Part 2: Integrate new TaskController code into tree and allow usage by pref. r=smaug,? Differential Revision: https://phabricator.services.mozilla.com/D74672
dom/plugins/ipc/PluginProcessChild.cpp
xpcom/build/XPCOMInit.cpp
xpcom/threads/AbstractEventQueue.h
xpcom/threads/IdlePeriodState.h
xpcom/threads/MainThreadQueue.h
xpcom/threads/PrioritizedEventQueue.cpp
xpcom/threads/PrioritizedEventQueue.h
xpcom/threads/TaskController.cpp
xpcom/threads/ThreadEventQueue.cpp
xpcom/threads/ThreadEventQueue.h
xpcom/threads/nsThread.cpp
xpcom/threads/nsThread.h
xpcom/threads/nsThreadManager.cpp
--- a/dom/plugins/ipc/PluginProcessChild.cpp
+++ b/dom/plugins/ipc/PluginProcessChild.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: sw=2 ts=4 et :
  * 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/plugins/PluginProcessChild.h"
+#include "mozilla/TaskController.h"
 
 #include "ClearOnShutdown.h"
 #include "base/command_line.h"
 #include "base/string_util.h"
 #include "mozilla/AbstractThread.h"
 #include "mozilla/ipc/IOThreadChild.h"
 #include "nsDebugImpl.h"
 #include "nsThreadManager.h"
@@ -190,13 +191,16 @@ void PluginProcessChild::CleanUp() {
   // is an event that is regularly part of XPCOM shutdown.  We do not
   // call XPCOM's shutdown but we need this event to be sent to avoid
   // leaking objects labeled as ClearOnShutdown.
   nsThreadManager::get().Shutdown();
   NS_LogTerm();
 #endif
 
   mozilla::KillClearOnShutdown(ShutdownPhase::ShutdownFinal);
+
   AbstractThread::ShutdownMainThread();
+
+  mozilla::TaskController::Shutdown();
 }
 
 }  // namespace plugins
 }  // namespace mozilla
--- a/xpcom/build/XPCOMInit.cpp
+++ b/xpcom/build/XPCOMInit.cpp
@@ -9,16 +9,17 @@
 #include "base/basictypes.h"
 
 #include "mozilla/AbstractThread.h"
 #include "mozilla/AppShutdown.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Poison.h"
 #include "mozilla/RemoteDecoderManagerChild.h"
 #include "mozilla/SharedThreadPool.h"
+#include "mozilla/TaskController.h"
 #include "mozilla/XPCOM.h"
 #include "mozJSComponentLoader.h"
 #include "nsXULAppAPI.h"
 
 #ifndef ANDROID
 #  include "nsTerminator.h"
 #endif
 
@@ -802,16 +803,18 @@ nsresult ShutdownXPCOM(nsIServiceManager
   NS_IF_RELEASE(gDebug);
 
   delete sIOThread;
   sIOThread = nullptr;
 
   delete sMessageLoop;
   sMessageLoop = nullptr;
 
+  mozilla::TaskController::Shutdown();
+
   if (sCommandLineWasInitialized) {
     CommandLine::Terminate();
     sCommandLineWasInitialized = false;
   }
 
   delete sExitManager;
   sExitManager = nullptr;
 
--- a/xpcom/threads/AbstractEventQueue.h
+++ b/xpcom/threads/AbstractEventQueue.h
@@ -12,22 +12,25 @@
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Mutex.h"
 
 class nsIRunnable;
 
 namespace mozilla {
 
 enum class EventQueuePriority {
-  High,
-  Input,
+  Idle,
+  DeferredTimers,
+  InputLow,
+  // Used to ensure high priority tasks don't starve ~normal priority tasks
+  HighLow,
+  Normal,
   MediumHigh,
-  Normal,
-  DeferredTimers,
-  Idle,
+  InputHigh,
+  High,
 
   Count
 };
 
 // AbstractEventQueue is an abstract base class for all our unsynchronized event
 // queue implementations:
 // - EventQueue: A queue of runnables. Used for non-main threads.
 // - PrioritizedEventQueue: Contains a queue for each priority level.
--- a/xpcom/threads/IdlePeriodState.h
+++ b/xpcom/threads/IdlePeriodState.h
@@ -20,16 +20,17 @@
 #include "mozilla/TimeStamp.h"
 #include "nsCOMPtr.h"
 
 #include <stdint.h>
 
 class nsIIdlePeriod;
 
 namespace mozilla {
+class TaskManager;
 namespace ipc {
 class IdleSchedulerChild;
 }  // namespace ipc
 
 class IdlePeriodState {
  public:
   explicit IdlePeriodState(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod);
 
--- a/xpcom/threads/MainThreadQueue.h
+++ b/xpcom/threads/MainThreadQueue.h
@@ -22,17 +22,17 @@ inline already_AddRefed<nsThread> Create
     SynchronizedQueueT** aSynchronizedQueue = nullptr) {
   using MainThreadQueueT = PrioritizedEventQueue;
 
   auto queue = MakeUnique<MainThreadQueueT>(do_AddRef(aIdlePeriod));
 
   MainThreadQueueT* prioritized = queue.get();
 
   RefPtr<SynchronizedQueueT> synchronizedQueue =
-      new SynchronizedQueueT(std::move(queue));
+      new SynchronizedQueueT(std::move(queue), true);
 
   prioritized->SetMutexRef(synchronizedQueue->MutexRef());
 
   // Setup "main" thread
   RefPtr<nsThread> mainThread =
       new nsThread(WrapNotNull(synchronizedQueue), nsThread::MAIN_THREAD, 0);
 
   if (aSynchronizedQueue) {
--- a/xpcom/threads/PrioritizedEventQueue.cpp
+++ b/xpcom/threads/PrioritizedEventQueue.cpp
@@ -7,62 +7,94 @@
 #include "PrioritizedEventQueue.h"
 #include "mozilla/EventQueue.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/StaticPrefs_threads.h"
 #include "mozilla/ipc/IdleSchedulerChild.h"
 #include "nsThreadManager.h"
 #include "nsXPCOMPrivate.h"  // for gXPCOMThreadsShutDown
 #include "InputEventStatistics.h"
+#include "MainThreadUtils.h"
 
 using namespace mozilla;
 
 PrioritizedEventQueue::PrioritizedEventQueue(
     already_AddRefed<nsIIdlePeriod>&& aIdlePeriod)
     : mHighQueue(MakeUnique<EventQueue>(EventQueuePriority::High)),
-      mInputQueue(MakeUnique<EventQueueSized<32>>(EventQueuePriority::Input)),
+      mInputQueue(
+          MakeUnique<EventQueueSized<32>>(EventQueuePriority::InputHigh)),
       mMediumHighQueue(MakeUnique<EventQueue>(EventQueuePriority::MediumHigh)),
       mNormalQueue(MakeUnique<EventQueueSized<64>>(EventQueuePriority::Normal)),
       mDeferredTimersQueue(
           MakeUnique<EventQueue>(EventQueuePriority::DeferredTimers)),
-      mIdleQueue(MakeUnique<EventQueue>(EventQueuePriority::Idle)),
-      mIdlePeriodState(std::move(aIdlePeriod)) {}
+      mIdleQueue(MakeUnique<EventQueue>(EventQueuePriority::Idle)) {
+  mInputTaskManager = new InputTaskManager();
+  mIdleTaskManager = new IdleTaskManager(std::move(aIdlePeriod));
+  TaskController::Get()->SetIdleTaskManager(mIdleTaskManager);
+}
 
 PrioritizedEventQueue::~PrioritizedEventQueue() = default;
 
 void PrioritizedEventQueue::PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
                                      EventQueuePriority aPriority,
                                      const MutexAutoLock& aProofOfLock,
                                      mozilla::TimeDuration* aDelay) {
+  RefPtr<nsIRunnable> event(aEvent);
+
+  if (UseTaskController()) {
+    TaskController* tc = TaskController::Get();
+
+    TaskManager* manager = nullptr;
+    if (aPriority == EventQueuePriority::InputHigh) {
+      if (mInputTaskManager->State() == InputTaskManager::STATE_DISABLED) {
+        aPriority = EventQueuePriority::Normal;
+      } else {
+        manager = mInputTaskManager;
+      }
+    } else if (aPriority == EventQueuePriority::DeferredTimers ||
+               aPriority == EventQueuePriority::Idle) {
+      manager = mIdleTaskManager;
+    }
+
+    tc->DispatchRunnable(event.forget(), static_cast<uint32_t>(aPriority),
+                         manager);
+    return;
+  }
+
   // Double check the priority with a QI.
-  RefPtr<nsIRunnable> event(aEvent);
   EventQueuePriority priority = aPriority;
 
-  if (priority == EventQueuePriority::Input &&
-      mInputQueueState == STATE_DISABLED) {
+  if (priority == EventQueuePriority::InputHigh &&
+      mInputTaskManager->State() == InputTaskManager::STATE_DISABLED) {
     priority = EventQueuePriority::Normal;
   } else if (priority == EventQueuePriority::MediumHigh &&
              !StaticPrefs::threads_medium_high_event_queue_enabled()) {
     priority = EventQueuePriority::Normal;
   }
 
   switch (priority) {
     case EventQueuePriority::High:
       mHighQueue->PutEvent(event.forget(), priority, aProofOfLock, aDelay);
       break;
-    case EventQueuePriority::Input:
+    case EventQueuePriority::InputHigh:
       mInputQueue->PutEvent(event.forget(), priority, aProofOfLock, aDelay);
       break;
     case EventQueuePriority::MediumHigh:
       mMediumHighQueue->PutEvent(event.forget(), priority, aProofOfLock,
                                  aDelay);
       break;
     case EventQueuePriority::Normal:
       mNormalQueue->PutEvent(event.forget(), priority, aProofOfLock, aDelay);
       break;
+    case EventQueuePriority::HighLow:
+      MOZ_ASSERT(false, "HighLow is a TaskController's internal priority!");
+      break;
+    case EventQueuePriority::InputLow:
+      MOZ_ASSERT(false, "InputLow is a TaskController's internal priority!");
+      break;
     case EventQueuePriority::DeferredTimers: {
       if (NS_IsMainThread()) {
         mDeferredTimersQueue->PutEvent(event.forget(), priority, aProofOfLock,
                                        aDelay);
       } else {
         // We don't want to touch our idle queues from off the main
         // thread.  Queue it indirectly.
         IndirectlyQueueRunnable(event.forget(), priority, aProofOfLock, aDelay);
@@ -84,74 +116,81 @@ void PrioritizedEventQueue::PutEvent(alr
       break;
   }
 }
 
 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);
+  if (aUpdateState &&
+      mInputTaskManager->State() == InputTaskManager::STATE_ENABLED &&
+      mInputTaskManager->InputHandlingStartTime().IsNull() && inputCount > 0) {
+    mInputTaskManager->SetInputHandlingStartTime(
+        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:
-  // INPUT: if inputCount > 0 && TimeStamp::Now() > mInputHandlingStartTime
-  // MEDIUMHIGH: if medium high pending
-  // NORMAL: if normal pending
+  // INPUT: if inputCount > 0 && TimeStamp::Now() >
+  //        mInputTaskManager->InputHandlingStartTime(...);
+  // 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:
   // 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;
   if (!mHighQueue->IsEmpty(aProofOfLock)) {
     queue = EventQueuePriority::High;
-  } else if (inputCount > 0 && (mInputQueueState == STATE_FLUSHING ||
-                                (mInputQueueState == STATE_ENABLED &&
-                                 !mInputHandlingStartTime.IsNull() &&
-                                 TimeStamp::Now() > mInputHandlingStartTime))) {
-    queue = EventQueuePriority::Input;
+  } else if (inputCount > 0 &&
+             (mInputTaskManager->State() == InputTaskManager::STATE_FLUSHING ||
+              (mInputTaskManager->State() == InputTaskManager::STATE_ENABLED &&
+               !mInputTaskManager->InputHandlingStartTime().IsNull() &&
+               TimeStamp::Now() >
+                   mInputTaskManager->InputHandlingStartTime()))) {
+    queue = EventQueuePriority::InputHigh;
   } else if (!mMediumHighQueue->IsEmpty(aProofOfLock)) {
     MOZ_ASSERT(
-        mInputQueueState != STATE_FLUSHING,
+        mInputTaskManager->State() != InputTaskManager::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,
+    MOZ_ASSERT(mInputTaskManager->State() != InputTaskManager::STATE_FLUSHING,
                "Shouldn't consume normal event when flushing input events");
     queue = EventQueuePriority::Normal;
-  } else if (inputCount > 0 && mInputQueueState != STATE_SUSPEND) {
+  } else if (inputCount > 0 &&
+             mInputTaskManager->State() != InputTaskManager::STATE_SUSPEND) {
     MOZ_ASSERT(
-        mInputQueueState != STATE_DISABLED,
+        mInputTaskManager->State() != InputTaskManager::STATE_DISABLED,
         "Shouldn't consume input events when the input queue is disabled");
-    queue = EventQueuePriority::Input;
+    queue = EventQueuePriority::InputHigh;
   } 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);
+      queue == EventQueuePriority::InputHigh ||
+          queue == EventQueuePriority::InputLow,
+      mInputTaskManager->State() != InputTaskManager::STATE_DISABLED &&
+          mInputTaskManager->State() != InputTaskManager::STATE_SUSPEND);
 
   return queue;
 }
 
 void PrioritizedEventQueue::IndirectlyQueueRunnable(
     already_AddRefed<nsIRunnable>&& aEvent, EventQueuePriority aPriority,
     const MutexAutoLock& aProofOfLock, mozilla::TimeDuration* aDelay) {
   nsCOMPtr<nsIRunnable> event(aEvent);
@@ -201,20 +240,20 @@ already_AddRefed<nsIRunnable> Prioritize
     default:
       MOZ_CRASH();
       break;
 
     case EventQueuePriority::High:
       event = mHighQueue->GetEvent(nullptr, aProofOfLock,
                                    aHypotheticalInputEventDelay);
       MOZ_ASSERT(event);
-      mInputHandlingStartTime = TimeStamp();
+      mInputTaskManager->SetInputHandlingStartTime(TimeStamp());
       break;
 
-    case EventQueuePriority::Input:
+    case EventQueuePriority::InputHigh:
       event = mInputQueue->GetEvent(nullptr, aProofOfLock,
                                     aHypotheticalInputEventDelay);
       MOZ_ASSERT(event);
       break;
 
       // All queue priorities below Input don't add their queuing time to the
       // time an input event will be delayed, so report 0 for time-in-queue
       // if we're below Input; input events will only be delayed by the time
@@ -234,17 +273,18 @@ already_AddRefed<nsIRunnable> Prioritize
       *aHypotheticalInputEventDelay = TimeDuration();
       // If we get here, then all queues except deferredtimers and idle are
       // empty.
 
       if (!HasIdleRunnables(aProofOfLock)) {
         return nullptr;
       }
 
-      TimeStamp idleDeadline = mIdlePeriodState.GetCachedIdleDeadline();
+      TimeStamp idleDeadline =
+          mIdleTaskManager->State().GetCachedIdleDeadline();
       if (!idleDeadline) {
         return nullptr;
       }
 
       event = mDeferredTimersQueue->GetEvent(nullptr, aProofOfLock);
       if (!event) {
         event = mIdleQueue->GetEvent(nullptr, aProofOfLock);
       }
@@ -268,39 +308,39 @@ already_AddRefed<nsIRunnable> Prioritize
 }
 
 void PrioritizedEventQueue::DidRunEvent(const MutexAutoLock& aProofOfLock) {
   if (IsEmpty(aProofOfLock)) {
     // Certainly no more idle tasks.  Unlocking here is OK, because
     // our caller does nothing after this except return, which unlocks
     // its lock anyway.
     MutexAutoUnlock unlock(*mMutex);
-    mIdlePeriodState.RanOutOfTasks(unlock);
+    mIdleTaskManager->State().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) {
-  mIdlePeriodState.ForgetPendingTaskGuarantee();
+  mIdleTaskManager->State().ForgetPendingTaskGuarantee();
 
   EventQueuePriority queue = SelectQueue(false, aProofOfLock);
 
   if (queue == EventQueuePriority::High) {
     return mHighQueue->HasReadyEvent(aProofOfLock);
-  } else if (queue == EventQueuePriority::Input) {
+  } else if (queue == EventQueuePriority::InputHigh) {
     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 ||
@@ -311,66 +351,99 @@ bool PrioritizedEventQueue::HasReadyEven
   if (mDeferredTimersQueue->IsEmpty(aProofOfLock) &&
       mIdleQueue->IsEmpty(aProofOfLock)) {
     return false;
   }
 
   // Temporarily unlock so we can peek our idle deadline.
   {
     MutexAutoUnlock unlock(*mMutex);
-    mIdlePeriodState.CachePeekedIdleDeadline(unlock);
+    mIdleTaskManager->State().CachePeekedIdleDeadline(unlock);
   }
-  TimeStamp idleDeadline = mIdlePeriodState.GetCachedIdleDeadline();
-  mIdlePeriodState.ClearCachedIdleDeadline();
+  TimeStamp idleDeadline = mIdleTaskManager->State().GetCachedIdleDeadline();
+  mIdleTaskManager->State().ClearCachedIdleDeadline();
 
   // Re-check the emptiness of the queues, since we had the lock released for a
   // bit.
   if (idleDeadline && (mDeferredTimersQueue->HasReadyEvent(aProofOfLock) ||
                        mIdleQueue->HasReadyEvent(aProofOfLock))) {
-    mIdlePeriodState.EnforcePendingTaskGuarantee();
+    mIdleTaskManager->State().EnforcePendingTaskGuarantee();
     return true;
   }
 
   return false;
 }
 
 bool PrioritizedEventQueue::HasPendingHighPriorityEvents(
     const MutexAutoLock& aProofOfLock) {
   return !mHighQueue->IsEmpty(aProofOfLock);
 }
 
+bool PrioritizedEventQueue::HasIdleRunnables(
+    const MutexAutoLock& aProofOfLock) const {
+  return !mIdleQueue->IsEmpty(aProofOfLock) ||
+         !mDeferredTimersQueue->IsEmpty(aProofOfLock);
+}
+
 size_t PrioritizedEventQueue::Count(const MutexAutoLock& aProofOfLock) const {
   MOZ_CRASH("unimplemented");
 }
 
-void PrioritizedEventQueue::EnableInputEventPrioritization(
-    const MutexAutoLock& aProofOfLock) {
+void InputTaskManager::EnableInputEventPrioritization() {
+  MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mInputQueueState == STATE_DISABLED);
   mInputQueueState = STATE_ENABLED;
   mInputHandlingStartTime = TimeStamp();
 }
 
-void PrioritizedEventQueue::FlushInputEventPrioritization(
-    const MutexAutoLock& aProofOfLock) {
+void InputTaskManager::FlushInputEventPrioritization() {
+  MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mInputQueueState == STATE_ENABLED ||
              mInputQueueState == STATE_SUSPEND);
   mInputQueueState =
       mInputQueueState == STATE_ENABLED ? STATE_FLUSHING : STATE_SUSPEND;
 }
 
-void PrioritizedEventQueue::SuspendInputEventPrioritization(
-    const MutexAutoLock& aProofOfLock) {
+void InputTaskManager::SuspendInputEventPrioritization() {
+  MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mInputQueueState == STATE_ENABLED ||
              mInputQueueState == STATE_FLUSHING);
   mInputQueueState = STATE_SUSPEND;
 }
 
-void PrioritizedEventQueue::ResumeInputEventPrioritization(
-    const MutexAutoLock& aProofOfLock) {
+void InputTaskManager::ResumeInputEventPrioritization() {
+  MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mInputQueueState == STATE_SUSPEND);
   mInputQueueState = STATE_ENABLED;
 }
 
-bool PrioritizedEventQueue::HasIdleRunnables(
-    const MutexAutoLock& aProofOfLock) const {
-  return !mIdleQueue->IsEmpty(aProofOfLock) ||
-         !mDeferredTimersQueue->IsEmpty(aProofOfLock);
+int32_t InputTaskManager::GetPriorityModifierForEventLoopTurn(
+    const MutexAutoLock& aProofOfLock) {
+  size_t inputCount = PendingTaskCount();
+  if (State() == STATE_ENABLED && InputHandlingStartTime().IsNull() &&
+      inputCount > 0) {
+    SetInputHandlingStartTime(
+        InputEventStatistics::Get().GetInputHandlingStartTime(inputCount));
+  }
+
+  if (inputCount > 0 && (State() == InputTaskManager::STATE_FLUSHING ||
+                         (State() == InputTaskManager::STATE_ENABLED &&
+                          !InputHandlingStartTime().IsNull() &&
+                          TimeStamp::Now() > InputHandlingStartTime()))) {
+    return 0;
+  }
+
+  int32_t modifier = static_cast<int32_t>(EventQueuePriority::InputLow) -
+                     static_cast<int32_t>(EventQueuePriority::MediumHigh);
+  return modifier;
 }
+
+void InputTaskManager::WillRunTask() {
+  TaskManager::WillRunTask();
+  mStartTimes.AppendElement(TimeStamp::Now());
+}
+
+void InputTaskManager::DidRunTask() {
+  TaskManager::DidRunTask();
+  MOZ_ASSERT(!mStartTimes.IsEmpty());
+  TimeStamp start = mStartTimes.PopLastElement();
+  InputEventStatistics::Get().UpdateInputDuration(TimeStamp::Now() - start);
+}
--- a/xpcom/threads/PrioritizedEventQueue.h
+++ b/xpcom/threads/PrioritizedEventQueue.h
@@ -3,30 +3,68 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_PrioritizedEventQueue_h
 #define mozilla_PrioritizedEventQueue_h
 
 #include "mozilla/AbstractEventQueue.h"
+#include "mozilla/Atomics.h"
 #include "mozilla/EventQueue.h"
 #include "mozilla/IdlePeriodState.h"
+#include "mozilla/TaskController.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/UniquePtr.h"
 #include "nsCOMPtr.h"
 
 class nsIIdlePeriod;
 class nsIRunnable;
 
 namespace mozilla {
 namespace ipc {
 class IdleSchedulerChild;
 }
 
+class InputTaskManager : public TaskManager {
+ public:
+  InputTaskManager() : mInputQueueState(STATE_DISABLED) {}
+  int32_t GetPriorityModifierForEventLoopTurn(
+      const MutexAutoLock& aProofOfLock) final;
+  void WillRunTask() final;
+  void DidRunTask() final;
+
+  enum InputEventQueueState {
+    STATE_DISABLED,
+    STATE_FLUSHING,
+    STATE_SUSPEND,
+    STATE_ENABLED
+  };
+
+  void EnableInputEventPrioritization();
+  void FlushInputEventPrioritization();
+  void SuspendInputEventPrioritization();
+  void ResumeInputEventPrioritization();
+
+  InputEventQueueState State() { return mInputQueueState; }
+
+  void SetState(InputEventQueueState aState) { mInputQueueState = aState; }
+
+  TimeStamp InputHandlingStartTime() { return mInputHandlingStartTime; }
+
+  void SetInputHandlingStartTime(TimeStamp aStartTime) {
+    mInputHandlingStartTime = aStartTime;
+  }
+
+ private:
+  TimeStamp mInputHandlingStartTime;
+  Atomic<InputEventQueueState> mInputQueueState;
+  AutoTArray<TimeStamp, 4> mStartTimes;
+};
+
 // 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.
 //
@@ -68,37 +106,46 @@ class PrioritizedEventQueue final : publ
   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
   // 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; }
 
-  void EnableInputEventPrioritization(const MutexAutoLock& aProofOfLock) final;
-  void FlushInputEventPrioritization(const MutexAutoLock& aProofOfLock) final;
-  void SuspendInputEventPrioritization(const MutexAutoLock& aProofOfLock) final;
-  void ResumeInputEventPrioritization(const MutexAutoLock& aProofOfLock) final;
+  void EnableInputEventPrioritization(const MutexAutoLock& aProofOfLock) final {
+    mInputTaskManager->EnableInputEventPrioritization();
+  }
+  void FlushInputEventPrioritization(const MutexAutoLock& aProofOfLock) final {
+    mInputTaskManager->FlushInputEventPrioritization();
+  }
+  void SuspendInputEventPrioritization(
+      const MutexAutoLock& aProofOfLock) final {
+    mInputTaskManager->SuspendInputEventPrioritization();
+  }
+  void ResumeInputEventPrioritization(const MutexAutoLock& aProofOfLock) final {
+    mInputTaskManager->ResumeInputEventPrioritization();
+  }
 
-  IdlePeriodState* GetIdlePeriodState() { return &mIdlePeriodState; }
+  IdlePeriodState* GetIdlePeriodState() { return &mIdleTaskManager->State(); }
 
   bool HasIdleRunnables(const MutexAutoLock& aProofOfLock) const;
 
   size_t SizeOfExcludingThis(
       mozilla::MallocSizeOf aMallocSizeOf) const override {
     size_t n = 0;
 
     n += mHighQueue->SizeOfIncludingThis(aMallocSizeOf);
     n += mInputQueue->SizeOfIncludingThis(aMallocSizeOf);
     n += mMediumHighQueue->SizeOfIncludingThis(aMallocSizeOf);
     n += mNormalQueue->SizeOfIncludingThis(aMallocSizeOf);
     n += mDeferredTimersQueue->SizeOfIncludingThis(aMallocSizeOf);
     n += mIdleQueue->SizeOfIncludingThis(aMallocSizeOf);
 
-    n += mIdlePeriodState.SizeOfExcludingThis(aMallocSizeOf);
+    n += mIdleTaskManager->State().SizeOfExcludingThis(aMallocSizeOf);
 
     return n;
   }
 
  private:
   EventQueuePriority SelectQueue(bool aUpdateState,
                                  const MutexAutoLock& aProofOfLock);
 
@@ -116,25 +163,15 @@ class PrioritizedEventQueue final : publ
 
   // We need to drop the queue mutex when checking the idle deadline, so we keep
   // a pointer to it here.
   Mutex* mMutex = nullptr;
 
   TimeDuration mLastEventDelay;
   TimeStamp mLastEventStart;
 
-  TimeStamp mInputHandlingStartTime;
-
-  enum InputEventQueueState {
-    STATE_DISABLED,
-    STATE_FLUSHING,
-    STATE_SUSPEND,
-    STATE_ENABLED
-  };
-  InputEventQueueState mInputQueueState = STATE_DISABLED;
-
-  // Tracking of our idle state of various sorts.
-  IdlePeriodState mIdlePeriodState;
+  RefPtr<InputTaskManager> mInputTaskManager;
+  RefPtr<IdleTaskManager> mIdleTaskManager;
 };
 
 }  // namespace mozilla
 
 #endif  // mozilla_PrioritizedEventQueue_h
--- a/xpcom/threads/TaskController.cpp
+++ b/xpcom/threads/TaskController.cpp
@@ -252,17 +252,17 @@ bool TaskController::HasMainThreadPendin
     // Return early if there's no tasks at all.
     if (mMainThreadTasks.empty()) {
       return false;
     }
 
     // We can cheaply count how many tasks are suspended.
     uint64_t totalSuspended = 0;
     for (TaskManager* manager : mTaskManagers) {
-      bool modifierChanged =
+      DebugOnly<bool> modifierChanged =
           manager
               ->UpdateCachesForCurrentIterationAndReportPriorityModifierChanged(
                   lock, TaskManager::IterationType::NOT_EVENT_LOOP_TURN);
       MOZ_ASSERT(!modifierChanged);
 
       // The idle manager should be suspended unless we're doing the idle pass.
       MOZ_ASSERT(manager != mIdleTaskManager || manager->mCurrentSuspended ||
                      considerIdle,
--- a/xpcom/threads/ThreadEventQueue.cpp
+++ b/xpcom/threads/ThreadEventQueue.cpp
@@ -8,16 +8,17 @@
 #include "mozilla/EventQueue.h"
 
 #include "LeakRefPtr.h"
 #include "nsComponentManagerUtils.h"
 #include "nsIThreadInternal.h"
 #include "nsThreadUtils.h"
 #include "PrioritizedEventQueue.h"
 #include "ThreadEventTarget.h"
+#include "mozilla/TaskController.h"
 
 using namespace mozilla;
 
 template <class InnerQueueT>
 class ThreadEventQueue<InnerQueueT>::NestedSink : public ThreadTargetSink {
  public:
   NestedSink(EventQueue* aQueue, ThreadEventQueue* aOwner)
       : mQueue(aQueue), mOwner(aOwner) {}
@@ -41,20 +42,24 @@ class ThreadEventQueue<InnerQueueT>::Nes
 
   // This is a non-owning reference. It must live at least until Disconnect is
   // called to clear it out.
   EventQueue* mQueue;
   RefPtr<ThreadEventQueue> mOwner;
 };
 
 template <class InnerQueueT>
-ThreadEventQueue<InnerQueueT>::ThreadEventQueue(UniquePtr<InnerQueueT> aQueue)
+ThreadEventQueue<InnerQueueT>::ThreadEventQueue(UniquePtr<InnerQueueT> aQueue,
+                                                bool aIsMainThread)
     : mBaseQueue(std::move(aQueue)),
       mLock("ThreadEventQueue"),
       mEventsAvailable(mLock, "EventsAvail") {
+  if (aIsMainThread) {
+    TaskController::Get()->SetConditionVariable(&mEventsAvailable);
+  }
   static_assert(std::is_base_of<AbstractEventQueue, InnerQueueT>::value,
                 "InnerQueueT must be an AbstractEventQueue subclass");
 }
 
 template <class InnerQueueT>
 ThreadEventQueue<InnerQueueT>::~ThreadEventQueue() {
   MOZ_ASSERT(mNestedQueues.IsEmpty());
 }
@@ -81,17 +86,17 @@ bool ThreadEventQueue<InnerQueueT>::PutE
     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 = EventQueuePriority::High;
         } else if (prio == nsIRunnablePriority::PRIORITY_INPUT_HIGH) {
-          aPriority = EventQueuePriority::Input;
+          aPriority = EventQueuePriority::InputHigh;
         } else if (prio == nsIRunnablePriority::PRIORITY_MEDIUMHIGH) {
           aPriority = EventQueuePriority::MediumHigh;
         } else if (prio == nsIRunnablePriority::PRIORITY_DEFERRED_TIMERS) {
           aPriority = EventQueuePriority::DeferredTimers;
         } else if (prio == nsIRunnablePriority::PRIORITY_IDLE) {
           aPriority = EventQueuePriority::Idle;
         }
       }
@@ -367,14 +372,17 @@ already_AddRefed<nsIThreadObserver>
 ThreadEventQueue<InnerQueueT>::GetObserverOnThread() {
   return do_AddRef(mObserver);
 }
 
 template <class InnerQueueT>
 void ThreadEventQueue<InnerQueueT>::SetObserver(nsIThreadObserver* aObserver) {
   MutexAutoLock lock(mLock);
   mObserver = aObserver;
+  if (NS_IsMainThread()) {
+    TaskController::Get()->SetThreadObserver(aObserver);
+  }
 }
 
 namespace mozilla {
 template class ThreadEventQueue<EventQueue>;
 template class ThreadEventQueue<PrioritizedEventQueue>;
 }  // namespace mozilla
--- a/xpcom/threads/ThreadEventQueue.h
+++ b/xpcom/threads/ThreadEventQueue.h
@@ -26,17 +26,18 @@ class ThreadEventTarget;
 // A ThreadEventQueue implements normal monitor-style synchronization over the
 // InnerQueueT AbstractEventQueue. It also implements PushEventQueue and
 // PopEventQueue for workers (see the documentation below for an explanation of
 // those). All threads use a ThreadEventQueue as their event queue. InnerQueueT
 // is a template parameter to avoid virtual dispatch overhead.
 template <class InnerQueueT>
 class ThreadEventQueue final : public SynchronizedEventQueue {
  public:
-  explicit ThreadEventQueue(UniquePtr<InnerQueueT> aQueue);
+  explicit ThreadEventQueue(UniquePtr<InnerQueueT> aQueue,
+                            bool aIsMainThread = false);
 
   bool PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
                 EventQueuePriority aPriority) final;
 
   already_AddRefed<nsIRunnable> GetEvent(
       bool aMayWait, EventQueuePriority* aPriority,
       mozilla::TimeDuration* aLastEventDelay = nullptr) final;
   void DidRunEvent() final;
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -26,19 +26,20 @@
 #include "mozilla/Logging.h"
 #include "nsIObserverService.h"
 #include "mozilla/IOInterposer.h"
 #include "mozilla/ipc/MessageChannel.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/SchedulerGroup.h"
 #include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_threads.h"
+#include "mozilla/TaskController.h"
 #include "nsXPCOMPrivate.h"
 #include "mozilla/ChaosMode.h"
-#include "mozilla/SchedulerGroup.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "nsThreadSyncDispatch.h"
 #include "nsServiceManagerUtils.h"
 #include "GeckoProfiler.h"
 #ifdef MOZ_GECKO_PROFILER
@@ -590,16 +591,20 @@ nsThread::nsThread(NotNull<SynchronizedE
       mIsMainThread(aMainThread == MAIN_THREAD),
       mUseHangMonitor(aMainThread == MAIN_THREAD),
       mIsAPoolThreadFree(nullptr),
       mCanInvokeJS(false),
 #ifdef EARLY_BETA_OR_EARLIER
       mLastWakeupCheckTime(TimeStamp::Now()),
 #endif
       mPerformanceCounterState(mNestedEventLoopDepth, mIsMainThread) {
+  if (mIsMainThread) {
+    mozilla::TaskController::Get()->SetPerformanceCounterState(
+        &mPerformanceCounterState);
+  }
 }
 
 nsThread::nsThread()
     : mEvents(nullptr),
       mEventTarget(nullptr),
       mShutdownContext(nullptr),
       mScriptObserver(nullptr),
       mStackSize(0),
@@ -897,17 +902,21 @@ nsThread::Shutdown() {
 }
 
 NS_IMETHODIMP
 nsThread::HasPendingEvents(bool* aResult) {
   if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
     return NS_ERROR_NOT_SAME_THREAD;
   }
 
-  *aResult = mEvents->HasPendingEvent();
+  if (NS_IsMainThread() && UseTaskController() && !mIsInLocalExecutionMode) {
+    *aResult = TaskController::Get()->HasMainThreadPendingTasks();
+  } else {
+    *aResult = mEvents->HasPendingEvent();
+  }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsThread::HasPendingHighPriorityEvents(bool* aResult) {
   if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
     return NS_ERROR_NOT_SAME_THREAD;
   }
@@ -993,17 +1002,17 @@ static bool GetLabeledRunnableName(nsIRu
     MOZ_ALWAYS_TRUE(NS_SUCCEEDED(named->GetName(aName)));
   } else {
     aName.AssignLiteral("non-nsINamed runnable");
   }
   if (aName.IsEmpty()) {
     aName.AssignLiteral("anonymous runnable");
   }
 
-  if (!labeled && aPriority > EventQueuePriority::Input) {
+  if (!labeled && aPriority > EventQueuePriority::InputHigh) {
     aName.AppendLiteral("(unlabeled)");
   }
 
   return labeled;
 }
 #endif
 
 mozilla::PerformanceCounter* nsThread::GetPerformanceCounter(
@@ -1057,24 +1066,23 @@ nsThread::ProcessNextEvent(bool aMayWait
   LOG(("THRD(%p) ProcessNextEvent [%u %u]\n", this, aMayWait,
        mNestedEventLoopDepth));
 
   if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
     return NS_ERROR_NOT_SAME_THREAD;
   }
 
   // The toplevel event loop normally blocks waiting for the next event, but
-  // if we're trying to shut this thread down, we must exit the event loop when
-  // the event queue is empty.
-  // This only applys to the toplevel event loop! Nested event loops (e.g.
-  // during sync dispatch) are waiting for some state change and must be able
-  // to block even if something has requested shutdown of the thread. Otherwise
-  // we'll just busywait as we endlessly look for an event, fail to find one,
-  // and repeat the nested event loop since its state change hasn't happened
-  // yet.
+  // if we're trying to shut this thread down, we must exit the event loop
+  // when the event queue is empty. This only applys to the toplevel event
+  // loop! Nested event loops (e.g. during sync dispatch) are waiting for
+  // some state change and must be able to block even if something has
+  // requested shutdown of the thread. Otherwise we'll just busywait as we
+  // endlessly look for an event, fail to find one, and repeat the nested
+  // event loop since its state change hasn't happened yet.
   bool reallyWait = aMayWait && (mNestedEventLoopDepth > 0 || !ShuttingDown());
 
   if (mIsInLocalExecutionMode) {
     EventQueuePriority priority;
     if (nsCOMPtr<nsIRunnable> event =
             mEvents->GetEvent(reallyWait, &priority)) {
       *aResult = true;
       LogRunnable::Run log(event);
@@ -1093,18 +1101,18 @@ nsThread::ProcessNextEvent(bool aMayWait
   }
 
   if (mIsMainThread) {
     DoMainThreadSpecificProcessing();
   }
 
   ++mNestedEventLoopDepth;
 
-  // We only want to create an AutoNoJSAPI on threads that actually do DOM stuff
-  // (including workers).  Those are exactly the threads that have an
+  // We only want to create an AutoNoJSAPI on threads that actually do DOM
+  // stuff (including workers).  Those are exactly the threads that have an
   // mScriptObserver.
   bool callScriptObserver = !!mScriptObserver;
   if (callScriptObserver) {
     noJSAPI.emplace();
     mScriptObserver->BeforeProcessTask(reallyWait);
   }
 
 #ifdef EARLY_BETA_OR_EARLIER
@@ -1126,19 +1134,27 @@ 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.
-    EventQueuePriority priority;
-    nsCOMPtr<nsIRunnable> event =
-        mEvents->GetEvent(reallyWait, &priority, &mLastEventDelay);
+    EventQueuePriority priority = EventQueuePriority::Normal;
+    nsCOMPtr<nsIRunnable> event;
+    bool usingTaskController = mIsMainThread && UseTaskController();
+    if (usingTaskController) {
+      // XXX should set priority?  Maybe we can just grab the "last task"
+      // priority from the TaskController where we currently grab the
+      // "ranIdleTask" state?
+      event = TaskController::Get()->GetRunnableForMTTask(reallyWait);
+    } else {
+      event = mEvents->GetEvent(reallyWait, &priority, &mLastEventDelay);
+    }
 
     *aResult = (event.get() != nullptr);
 
     if (event) {
 #ifdef EARLY_BETA_OR_EARLIER
       if (mayWaitForWakeup && mThread) {
         ++mWakeupCount;
         if (mWakeupCount == kTelemetryWakeupCountLimit) {
@@ -1172,18 +1188,18 @@ nsThread::ProcessNextEvent(bool aMayWait
 
       mozilla::TimeStamp now = mozilla::TimeStamp::Now();
 
       if (mUseHangMonitor) {
         BackgroundHangMonitor().NotifyActivity();
       }
 
 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
-      // If we're on the main thread, we want to record our current runnable's
-      // name in a static so that BHR can record it.
+      // If we're on the main thread, we want to record our current
+      // runnable's name in a static so that BHR can record it.
       Array<char, kRunnableNameBufSize> restoreRunnableName;
       restoreRunnableName[0] = '\0';
       auto clear = MakeScopeExit([&] {
         if (mIsMainThread) {
           MOZ_ASSERT(NS_IsMainThread());
           sMainThreadRunnableName = restoreRunnableName;
         }
       });
@@ -1197,33 +1213,37 @@ nsThread::ProcessNextEvent(bool aMayWait
         // Copy the name into sMainThreadRunnableName's buffer, and append a
         // 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
+      // Note, with TaskController InputTaskManager handles these updates.
       Maybe<AutoTimeDurationHelper> timeDurationHelper;
-      if (priority == EventQueuePriority::Input) {
+      if (priority == EventQueuePriority::InputHigh) {
         timeDurationHelper.emplace();
       }
 
-      PerformanceCounterState::Snapshot snapshot =
-          mPerformanceCounterState.RunnableWillRun(
-              GetPerformanceCounter(event), now,
-              priority == EventQueuePriority::Idle);
+      Maybe<PerformanceCounterState::Snapshot> snapshot;
+      if (!usingTaskController) {
+        snapshot.emplace(mPerformanceCounterState.RunnableWillRun(
+            GetPerformanceCounter(event), now,
+            priority == EventQueuePriority::Idle));
+      }
 
       mLastEventStart = now;
 
       event->Run();
 
-      mEvents->DidRunEvent();
-
-      mPerformanceCounterState.RunnableDidRun(std::move(snapshot));
+      if (!usingTaskController) {
+        mEvents->DidRunEvent();
+        mPerformanceCounterState.RunnableDidRun(std::move(snapshot.ref()));
+      }
 
       // To cover the event's destructor code inside the LogRunnable span.
       event = nullptr;
     } else {
       mLastEventDelay = TimeDuration();
       mLastEventStart = TimeStamp();
       if (aMayWait) {
         MOZ_ASSERT(ShuttingDown(),
@@ -1267,17 +1287,18 @@ nsThread::SetPriority(int32_t aPriority)
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   // NSPR defines the following four thread priorities:
   //   PR_PRIORITY_LOW
   //   PR_PRIORITY_NORMAL
   //   PR_PRIORITY_HIGH
   //   PR_PRIORITY_URGENT
-  // We map the priority values defined on nsISupportsPriority to these values.
+  // We map the priority values defined on nsISupportsPriority to these
+  // values.
 
   mPriority = aPriority;
 
   PRThreadPriority pri;
   if (mPriority <= PRIORITY_HIGHEST) {
     pri = PR_PRIORITY_URGENT;
   } else if (mPriority < PRIORITY_NORMAL) {
     pri = PR_PRIORITY_HIGH;
@@ -1467,18 +1488,18 @@ void PerformanceCounterState::RunnableDi
   if (mCurrentPerformanceCounter || mIsMainThread) {
     MaybeReportAccumulatedTime(now);
   }
 
   // And now restore the rest of our state.
   mCurrentPerformanceCounter = std::move(aSnapshot.mOldPerformanceCounter);
   mCurrentRunnableIsIdleRunnable = aSnapshot.mOldIsIdleRunnable;
   if (IsNestedRunnable()) {
-    // Reset mCurrentTimeSliceStart to right now, so our parent runnable's next
-    // slice can be properly accounted for.
+    // Reset mCurrentTimeSliceStart to right now, so our parent runnable's
+    // next slice can be properly accounted for.
     mCurrentTimeSliceStart = now;
   } else {
     // We are done at the outermost level; we are no longer in a timeslice.
     mCurrentTimeSliceStart = TimeStamp();
   }
 }
 
 void PerformanceCounterState::MaybeReportAccumulatedTime(TimeStamp aNow) {
--- a/xpcom/threads/nsThread.h
+++ b/xpcom/threads/nsThread.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsThread_h__
 #define nsThread_h__
 
 #include "mozilla/Mutex.h"
+#include "prenv.h"
 #include "nsIThreadInternal.h"
 #include "nsISupportsPriority.h"
 #include "nsThreadUtils.h"
 #include "nsString.h"
 #include "nsTObserverArray.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/IntegerTypeTraits.h"
@@ -37,16 +38,23 @@ class ThreadEventTarget;
 using mozilla::NotNull;
 
 class nsLocalExecutionRecord;
 class nsThreadEnumerator;
 
 // See https://www.w3.org/TR/longtasks
 #define LONGTASK_BUSY_WINDOW_MS 50
 
+static inline bool UseTaskController() {
+  if (PR_GetEnv("MOZ_USE_TASKCONTROLLER")) {
+    return true;
+  }
+  return false;
+}
+
 // A class for managing performance counter state.
 namespace mozilla {
 class PerformanceCounterState {
  public:
   explicit PerformanceCounterState(const uint32_t& aNestedEventLoopDepthRef,
                                    bool aIsMainThread)
       : mNestedEventLoopDepth(aNestedEventLoopDepthRef),
         mIsMainThread(aIsMainThread),
--- a/xpcom/threads/nsThreadManager.cpp
+++ b/xpcom/threads/nsThreadManager.cpp
@@ -17,16 +17,17 @@
 #include "mozilla/EventQueue.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TaskQueue.h"
 #include "mozilla/ThreadEventQueue.h"
 #include "mozilla/ThreadLocal.h"
 #include "PrioritizedEventQueue.h"
+#include "TaskController.h"
 #ifdef MOZ_CANARY
 #  include <fcntl.h>
 #  include <unistd.h>
 #endif
 
 #include "MainThreadIdlePeriod.h"
 #include "InputEventStatistics.h"
 
@@ -361,16 +362,18 @@ nsresult nsThreadManager::Init() {
   const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
   char* env_var_flag = getenv("MOZ_KILL_CANARIES");
   sCanaryOutputFD =
       env_var_flag
           ? (env_var_flag[0] ? open(env_var_flag, flags, mode) : STDERR_FILENO)
           : 0;
 #endif
 
+  TaskController::Initialize();
+
   nsCOMPtr<nsIIdlePeriod> idlePeriod = new MainThreadIdlePeriod();
 
   mMainThread =
       CreateMainThread<ThreadEventQueue<PrioritizedEventQueue>>(idlePeriod);
 
   nsresult rv = mMainThread->InitCurrentThread();
   if (NS_FAILED(rv)) {
     mMainThread = nullptr;